/*
 * Copyright (C) 2015 Freie Universität Berlin
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup     tests
 * @{
 *
 * @file
 * @brief       Tests NDP message handling of gnrc stack.
 *
 * @author      Martine S. Lenders <m.lenders@fu-berlin.de>
 *
 * @}
 */

#include <kernel_defines.h>
#include <stdio.h>

#include "byteorder.h"
#include "embUnit.h"
#include "embUnit/embUnit.h"
#include "msg.h"
#include "net/gnrc/ipv6/nib/conf.h"
#include "net/gnrc/netapi.h"
#include "net/gnrc/netif/hdr.h"
#include "net/gnrc/netif/internal.h"
#include "net/gnrc/netreg.h"
#include "net/gnrc/pktbuf.h"
#include "net/icmpv6.h"
#include "net/ndp.h"
#include "net/netdev_test.h"
#include "net/netopt.h"
#include "sched.h"
#include "test_utils/expect.h"

#include "pktbuf_static.h"

#include "net/gnrc/ndp.h"

#define TEST_CUR_HL             (194U)
#define TEST_LTIME              (894U)
#define TEST_REACH_TIME         (1597495062U)
#define TEST_RETRANS_TIMER      (1819101160U)
#define TEST_PFX_LEN            (42U)
#define TEST_VALID_LTIME        (223526372U)
#define TEST_PREF_LTIME         (2056660713U)
#define TEST_NDP_OPT_SIZE       (14U)
#define TEST_NDP_OPT_TYPE       (142U)
#define TEST_MTU                (669256124U)

#define TEST_ASSERT_ALLOCATION(pkt, s, t) \
    TEST_ASSERT_NULL((pkt)->next); \
    TEST_ASSERT_NOT_NULL((pkt)->data); \
    TEST_ASSERT_EQUAL_INT((s), (pkt)->size); \
    TEST_ASSERT_EQUAL_INT((t), (pkt)->type)

static const ipv6_addr_t test_dst = { { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                        0x73, 0x25, 0x22, 0xc6, 0xdf, 0x05, 0xf2, 0x6b } };
static const ipv6_addr_t test_src = { { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                        0xe5, 0x43, 0xb7, 0x74, 0xd7, 0xa9, 0x30, 0x74 } };
static const ipv6_addr_t test_tgt = { { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                        0x31, 0x78, 0x95, 0x84, 0x71, 0x5f, 0x47, 0x53 } };
static const ipv6_addr_t test_pfx = { { 0x47, 0x25, 0xd9, 0x3b, 0x7f, 0xcc, 0x15, 0x6c,
                                        0x64, 0x3e, 0x76, 0x0d, 0x30, 0x10, 0x0d, 0xc8 } };
static const uint8_t test_src_l2[] = { 0xe7, 0x43, 0xb7, 0x74, 0xd7, 0xa9, 0x30, 0x74 };

static gnrc_netif_t *test_netif = NULL;
static gnrc_netif_t _netif;

static void init_pkt_handler(void);
static inline size_t ceil8(size_t size);

static void set_up(void)
{
    gnrc_pktbuf_init();
}

static void fill_pktbuf(void)
{
    gnrc_pktsnip_t *pkt = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          _align(sizeof(gnrc_pktsnip_t)),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
}

static void test_nbr_sol_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_nbr_sol_build(&test_tgt, NULL));
}

static void test_nbr_sol_build__success(void)
{
    gnrc_pktsnip_t *pkt;
    ndp_nbr_sol_t *nbr_sol;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_nbr_sol_build(&test_tgt, NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, sizeof(ndp_nbr_sol_t), GNRC_NETTYPE_ICMPV6);
    /* check packet content */
    nbr_sol = pkt->data;
    TEST_ASSERT_EQUAL_INT(ICMPV6_NBR_SOL, nbr_sol->type);
    TEST_ASSERT_EQUAL_INT(0, nbr_sol->code);
    TEST_ASSERT_EQUAL_INT(0, nbr_sol->resv.u32);
    TEST_ASSERT_MESSAGE(ipv6_addr_equal(&test_tgt, &nbr_sol->tgt),
                        "nbr_sol->tgt != test_tgt");
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_adv_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_nbr_adv_build(&test_tgt, 0, NULL));
}

static void test_nbr_adv_build__success(uint8_t flags)
{
    gnrc_pktsnip_t *pkt;
    ndp_nbr_adv_t *nbr_adv;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_nbr_adv_build(&test_tgt, flags,
                                                       NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, sizeof(ndp_nbr_adv_t), GNRC_NETTYPE_ICMPV6);
    /* check packet content */
    nbr_adv = pkt->data;
    TEST_ASSERT_EQUAL_INT(ICMPV6_NBR_ADV, nbr_adv->type);
    TEST_ASSERT_EQUAL_INT(0, nbr_adv->code);
    TEST_ASSERT_EQUAL_INT(flags, nbr_adv->flags);
    TEST_ASSERT_EQUAL_INT(0, nbr_adv->resv[0]);
    TEST_ASSERT_EQUAL_INT(0, nbr_adv->resv[1]);
    TEST_ASSERT_EQUAL_INT(0, nbr_adv->resv[2]);
    TEST_ASSERT_MESSAGE(ipv6_addr_equal(&test_tgt, &nbr_adv->tgt),
                        "nbr_adv->tgt != test_tgt");
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_adv_build__success_without_flags(void)
{
    test_nbr_adv_build__success(0);
}

static void test_nbr_adv_build__success_with_flags(void)
{
    test_nbr_adv_build__success(NDP_NBR_ADV_FLAGS_S);
}

static void test_rtr_sol_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_rtr_sol_build(NULL));
}

static void test_rtr_sol_build__success(void)
{
    gnrc_pktsnip_t *pkt;
    ndp_rtr_sol_t *rtr_sol;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_rtr_sol_build(NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, sizeof(ndp_rtr_sol_t), GNRC_NETTYPE_ICMPV6);
    /* check packet content */
    rtr_sol = pkt->data;
    TEST_ASSERT_EQUAL_INT(ICMPV6_RTR_SOL, rtr_sol->type);
    TEST_ASSERT_EQUAL_INT(0, rtr_sol->code);
    TEST_ASSERT_EQUAL_INT(0, rtr_sol->resv.u32);
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_adv_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_rtr_adv_build(TEST_CUR_HL, 0, TEST_LTIME,
                                            TEST_REACH_TIME,
                                            TEST_RETRANS_TIMER, NULL));
}

static void test_rtr_adv_build__success(uint8_t flags)
{
    gnrc_pktsnip_t *pkt;
    ndp_rtr_adv_t *rtr_adv;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_rtr_adv_build(TEST_CUR_HL, flags,
                                                       TEST_LTIME,
                                                       TEST_REACH_TIME,
                                                       TEST_RETRANS_TIMER,
                                                       NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, sizeof(ndp_rtr_adv_t), GNRC_NETTYPE_ICMPV6);
    /* check packet content */
    rtr_adv = pkt->data;
    TEST_ASSERT_EQUAL_INT(ICMPV6_RTR_ADV, rtr_adv->type);
    TEST_ASSERT_EQUAL_INT(0, rtr_adv->code);
    TEST_ASSERT_EQUAL_INT(TEST_CUR_HL, rtr_adv->cur_hl);
    TEST_ASSERT_EQUAL_INT(flags, rtr_adv->flags);
    TEST_ASSERT_EQUAL_INT(TEST_LTIME, byteorder_ntohs(rtr_adv->ltime));
    TEST_ASSERT_EQUAL_INT(TEST_REACH_TIME,
                          byteorder_ntohl(rtr_adv->reach_time));
    TEST_ASSERT_EQUAL_INT(TEST_RETRANS_TIMER,
                          byteorder_ntohl(rtr_adv->retrans_timer));
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_adv_build__success_without_flags(void)
{
    test_rtr_adv_build__success(0);
}

static void test_rtr_adv_build__success_with_flags(void)
{
    test_rtr_adv_build__success(NDP_RTR_ADV_FLAGS_M);
}

static void test_opt_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_opt_build(TEST_NDP_OPT_TYPE, TEST_NDP_OPT_SIZE,
                                        NULL));
}

static void test_opt_build__success(void)
{
    gnrc_pktsnip_t *pkt;
    ndp_opt_t *opt;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_opt_build(TEST_NDP_OPT_TYPE,
                                                   TEST_NDP_OPT_SIZE,
                                                   NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, ceil8(TEST_NDP_OPT_SIZE), GNRC_NETTYPE_UNDEF);
    /* check packet content */
    opt = pkt->data;
    TEST_ASSERT_EQUAL_INT(TEST_NDP_OPT_TYPE, opt->type);
    TEST_ASSERT_EQUAL_INT(ceil8(TEST_NDP_OPT_SIZE) / 8, opt->len);
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_opt_sl2a_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_opt_sl2a_build(test_src_l2, sizeof(test_src_l2),
                                             NULL));
}

static void test_opt_sl2a_build__success(void)
{
    gnrc_pktsnip_t *pkt;
    ndp_opt_t *opt;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_opt_sl2a_build(test_src_l2,
                                                        sizeof(test_src_l2),
                                                        NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, ceil8(sizeof(ndp_opt_t) + sizeof(test_src_l2)),
                           GNRC_NETTYPE_UNDEF);
    /* check packet content */
    opt = pkt->data;
    TEST_ASSERT_EQUAL_INT(NDP_OPT_SL2A, opt->type);
    TEST_ASSERT_EQUAL_INT(ceil8(sizeof(ndp_opt_t) + sizeof(test_src_l2)) / 8,
                          opt->len);
    TEST_ASSERT_MESSAGE(memcmp(test_src_l2, opt + 1, sizeof(test_src_l2)) == 0,
                        "opt->l2addr != test_src_l2");
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_opt_tl2a_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_opt_tl2a_build(test_src_l2, sizeof(test_src_l2),
                                             NULL));
}

static void test_opt_tl2a_build__success(void)
{
    gnrc_pktsnip_t *pkt;
    ndp_opt_t *opt;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_opt_tl2a_build(test_src_l2,
                                                        sizeof(test_src_l2),
                                                        NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, ceil8(sizeof(ndp_opt_t) + sizeof(test_src_l2)),
                           GNRC_NETTYPE_UNDEF);
    /* check packet content */
    opt = pkt->data;
    TEST_ASSERT_EQUAL_INT(NDP_OPT_TL2A, opt->type);
    TEST_ASSERT_EQUAL_INT(ceil8(sizeof(ndp_opt_t) + sizeof(test_src_l2)) / 8,
                          opt->len);
    TEST_ASSERT_MESSAGE(memcmp(test_src_l2, opt + 1, sizeof(test_src_l2)) == 0,
                        "opt->l2addr != test_src_l2");
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_opt_pi_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_opt_pi_build(&test_pfx, TEST_PFX_LEN,
                                           TEST_VALID_LTIME, TEST_PREF_LTIME,
                                           0, NULL));
}

static void test_opt_pi_build__success(uint8_t flags)
{
    gnrc_pktsnip_t *pkt;
    ndp_opt_pi_t *opt;
    ipv6_addr_t exp_pfx = IPV6_ADDR_UNSPECIFIED;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_opt_pi_build(&test_pfx, TEST_PFX_LEN,
                                                      TEST_VALID_LTIME,
                                                      TEST_PREF_LTIME, flags,
                                                      NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, ceil8(sizeof(ndp_opt_pi_t)),
                           GNRC_NETTYPE_UNDEF);
    /* check packet content */
    /* prepare expected prefix (the function MUST remove all the garbage after
     * the prefix) */
    ipv6_addr_init_prefix(&exp_pfx, &test_pfx, TEST_PFX_LEN);
    opt = pkt->data;
    TEST_ASSERT_EQUAL_INT(NDP_OPT_PI, opt->type);
    TEST_ASSERT_EQUAL_INT(NDP_OPT_PI_LEN, opt->len);
    TEST_ASSERT_EQUAL_INT(TEST_PFX_LEN, opt->prefix_len);
    TEST_ASSERT_EQUAL_INT(flags, opt->flags);
    TEST_ASSERT_EQUAL_INT(TEST_VALID_LTIME,
                          byteorder_ntohl(opt->valid_ltime));
    TEST_ASSERT_EQUAL_INT(TEST_PREF_LTIME,
                          byteorder_ntohl(opt->pref_ltime));
    TEST_ASSERT_EQUAL_INT(0, opt->resv.u32);
    TEST_ASSERT_MESSAGE(ipv6_addr_equal(&exp_pfx, &opt->prefix),
                        "opt->prefix != exp_pfx");
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_opt_pi_build__success_without_flags(void)
{
    test_opt_pi_build__success(0);
}

static void test_opt_pi_build__success_with_flags(void)
{
    test_opt_pi_build__success(NDP_OPT_PI_FLAGS_L);
}

static void test_opt_mtu_build__pktbuf_full(void)
{
    fill_pktbuf();
    TEST_ASSERT_NULL(gnrc_ndp_opt_mtu_build(TEST_MTU, NULL));
}

static void test_opt_mtu_build__success(void)
{
    gnrc_pktsnip_t *pkt;
    ndp_opt_mtu_t *opt;

    TEST_ASSERT(gnrc_pktbuf_is_empty());
    TEST_ASSERT_NOT_NULL((pkt = gnrc_ndp_opt_mtu_build(TEST_MTU, NULL)));
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    /* check packet meta-data */
    TEST_ASSERT_ALLOCATION(pkt, ceil8(sizeof(ndp_opt_mtu_t)), GNRC_NETTYPE_UNDEF);
    /* check packet content */
    opt = pkt->data;
    TEST_ASSERT_EQUAL_INT(NDP_OPT_MTU, opt->type);
    TEST_ASSERT_EQUAL_INT(NDP_OPT_MTU_LEN, opt->len);
    TEST_ASSERT_EQUAL_INT(0, opt->resv.u16);
    TEST_ASSERT_EQUAL_INT(TEST_MTU, byteorder_ntohl(opt->mtu));
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static inline kernel_pid_t _get_iface(gnrc_netif_hdr_t *hdr)
{
    return hdr->if_pid;
}

static inline ipv6_addr_t *_get_ipv6_src(ipv6_hdr_t *hdr)
{
    return &hdr->src;
}

static inline ipv6_addr_t *_get_ipv6_dst(ipv6_hdr_t *hdr)
{
    return &hdr->dst;
}

#define ASSERT_NETIF_HDR(netif, pkt) \
    TEST_ASSERT_NOT_NULL(pkt); \
    TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_NETIF, pkt->type); \
    TEST_ASSERT_NOT_NULL(pkt->data); \
    TEST_ASSERT_EQUAL_INT(netif->pid, _get_iface(pkt->data))

#define ASSERT_IPV6_HDR(src, dst, pkt) \
    TEST_ASSERT_NOT_NULL(pkt); \
    TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_IPV6, pkt->type); \
    TEST_ASSERT_NOT_NULL(pkt->data); \
    TEST_ASSERT_MESSAGE(memcmp(src, _get_ipv6_src(pkt->data), \
                        sizeof(ipv6_addr_t)) == 0, "src != pkt->src"); \
    TEST_ASSERT_MESSAGE(memcmp(dst, _get_ipv6_dst(pkt->data), \
                        sizeof(ipv6_addr_t)) == 0, "dst != pkt->dst")

#define ASSERT_ICMPV6_HDR(pkt) \
    TEST_ASSERT_NOT_NULL(pkt); \
    TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_ICMPV6, pkt->type); \
    TEST_ASSERT_NOT_NULL(pkt->data)

static void test_nbr_sol_send(const ipv6_addr_t *src)
{
    msg_t msg;
    gnrc_pktsnip_t *pkt;
    ndp_nbr_sol_t *nbr_sol;

    while (msg_try_receive(&msg) == 1) {
        /* empty message queue */
    }
    TEST_ASSERT_NOT_NULL(test_netif);
    gnrc_ndp_nbr_sol_send(&test_tgt, test_netif, src, &test_dst, NULL);
    msg_receive(&msg);
    TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type);
    pkt = msg.content.ptr;
    /* check packet */
    ASSERT_NETIF_HDR(test_netif, pkt);
    if ((src != NULL) && ipv6_addr_is_unspecified(src)) {
        ASSERT_IPV6_HDR(&ipv6_addr_unspecified, &test_dst, pkt->next);
    }
    else {
        ASSERT_IPV6_HDR(&test_src, &test_dst, pkt->next);
    }
    ASSERT_ICMPV6_HDR(pkt->next->next);
    TEST_ASSERT_EQUAL_INT(sizeof(ndp_nbr_sol_t), pkt->next->next->size);
    nbr_sol = pkt->next->next->data;
    TEST_ASSERT_EQUAL_INT(ICMPV6_NBR_SOL, nbr_sol->type);
    TEST_ASSERT_MESSAGE(memcmp(&test_tgt, &nbr_sol->tgt, sizeof(ipv6_addr_t)) == 0,
                        "tgt != nbr_sol->tgt");
    if ((src != NULL) && ipv6_addr_is_unspecified(src)) {
        TEST_ASSERT_NULL(pkt->next->next->next);
    }
    else {
        TEST_ASSERT_NOT_NULL(pkt->next->next->next);
        TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_UNDEF, pkt->next->next->next->type);
        TEST_ASSERT_NOT_NULL(pkt->next->next->next->data);
        ndp_opt_t *opt = pkt->next->next->next->data;
        TEST_ASSERT_EQUAL_INT(NDP_OPT_SL2A, opt->type);
        TEST_ASSERT_MESSAGE(memcmp(&test_src_l2, opt + 1, sizeof(test_src_l2)) == 0,
                            "src_l2 != pkt->l2");
        TEST_ASSERT_NULL(pkt->next->next->next->next);
    }
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_sol_send__src_NULL(void)
{
    test_nbr_sol_send(NULL);
}

static void test_nbr_sol_send__src_unspecified(void)
{
    test_nbr_sol_send(&ipv6_addr_unspecified);
}

static void test_nbr_sol_send__src_NOT_NULL(void)
{
    test_nbr_sol_send(&test_src);
}

static void test_nbr_sol_send__pktbuf_full1(void)
{
    /* don't be able to fit any more data into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          _align(sizeof(gnrc_pktsnip_t)),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_nbr_sol_send(&test_tgt, test_netif, &test_src, &test_dst, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_sol_send__pktbuf_full2(void)
{
    /* just be able to fit the SLLAO into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (2 * _align(sizeof(gnrc_pktsnip_t))) - 16,
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_nbr_sol_send(&test_tgt, test_netif, &test_src, &test_dst, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_sol_send__pktbuf_full3(void)
{
    /* just be able to fit the SLLAO and NS into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (3 * _align(sizeof(gnrc_pktsnip_t))) - 16 -
                                          sizeof(ndp_nbr_sol_t),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_nbr_sol_send(&test_tgt, test_netif, &test_src, &test_dst, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_sol_send__pktbuf_full4(void)
{
    /* just be able to fit the SLLAO, NS, and IPv6 header into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (4 * _align(sizeof(gnrc_pktsnip_t))) - 16 -
                                          sizeof(ndp_nbr_sol_t) -
                                          sizeof(ipv6_hdr_t),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_nbr_sol_send(&test_tgt, test_netif, &test_src, &test_dst, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_adv_send(const ipv6_addr_t *tgt, const ipv6_addr_t *dst,
                              bool supply_tl2a, gnrc_pktsnip_t *exp_ext_opts)
{
    msg_t msg;
    gnrc_pktsnip_t *pkt;
    ndp_nbr_adv_t *nbr_adv;

    TEST_ASSERT_NOT_NULL(test_netif);
    gnrc_ndp_nbr_adv_send(tgt, test_netif, dst, supply_tl2a, exp_ext_opts);
    msg_receive(&msg);
    TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type);
    pkt = msg.content.ptr;
    /* check packet */
    ASSERT_NETIF_HDR(test_netif, pkt);
    if (ipv6_addr_is_unspecified(dst)) {
        ASSERT_IPV6_HDR(&ipv6_addr_unspecified, &ipv6_addr_all_nodes_link_local,
                        pkt->next);
    }
    else {
        ASSERT_IPV6_HDR(&ipv6_addr_unspecified, &test_dst, pkt->next);
    }
    ASSERT_ICMPV6_HDR(pkt->next->next);
    TEST_ASSERT_EQUAL_INT(sizeof(ndp_nbr_adv_t), pkt->next->next->size);
    nbr_adv = pkt->next->next->data;
    TEST_ASSERT_EQUAL_INT(ICMPV6_NBR_ADV, nbr_adv->type);
    TEST_ASSERT_MESSAGE(memcmp(tgt, &nbr_adv->tgt, sizeof(ipv6_addr_t)) == 0,
                        "tgt != nbr_adv->tgt");
    if (supply_tl2a || ipv6_addr_is_unspecified(dst) || (exp_ext_opts != NULL)) {
        gnrc_pktsnip_t *ext_opts = NULL;

        TEST_ASSERT_NOT_NULL(pkt->next->next->next);
        TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_UNDEF, pkt->next->next->next->type);
        TEST_ASSERT_NOT_NULL(pkt->next->next->next->data);
        /* implicitly this is the same */
        if (supply_tl2a || ipv6_addr_is_unspecified(dst)) {
            ndp_opt_t *opt = pkt->next->next->next->data;
            TEST_ASSERT_EQUAL_INT(NDP_OPT_TL2A, opt->type);
            TEST_ASSERT_MESSAGE(memcmp(&test_src_l2, opt + 1, sizeof(test_src_l2)) == 0,
                                "src_l2 != pkt->l2");
            if (exp_ext_opts == NULL) {
                TEST_ASSERT_NULL(pkt->next->next->next->next);
            }
            else {
                /* extra option comes after TL2AO */
                ext_opts = pkt->next->next->next->next;
            }
        }
        else {
            /* extra option comes directly after neighbor advertisement */
            ext_opts = pkt->next->next->next;
        }
        TEST_ASSERT(exp_ext_opts == ext_opts);
    }
    else {
        TEST_ASSERT_NULL(pkt->next->next->next);
    }
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_adv_send__foreign_tgt_unspecified_dst_no_supply_tl2a_no_ext_opts(void)
{
    test_nbr_adv_send(&test_src, &ipv6_addr_unspecified, false, NULL);
}

static void test_nbr_adv_send__foreign_tgt_unspecified_dst_no_supply_tl2a_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_nbr_adv_send(&test_src, &ipv6_addr_unspecified, false, ext_opts);
}

static void test_nbr_adv_send__foreign_tgt_unspecified_dst_supply_tl2a_no_ext_opts(void)
{
    test_nbr_adv_send(&test_src, &ipv6_addr_unspecified, true, NULL);
}

static void test_nbr_adv_send__foreign_tgt_unspecified_dst_supply_tl2a_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_nbr_adv_send(&test_src, &ipv6_addr_unspecified, true, ext_opts);
}

static void test_nbr_adv_send__foreign_tgt_specified_dst_no_supply_tl2a_no_ext_opts(void)
{
    test_nbr_adv_send(&test_src, &test_dst, false, NULL);
}

static void test_nbr_adv_send__foreign_tgt_specified_dst_no_supply_tl2a_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_nbr_adv_send(&test_src, &test_dst, false, ext_opts);
}

static void test_nbr_adv_send__foreign_tgt_specified_dst_supply_tl2a_no_ext_opts(void)
{
    test_nbr_adv_send(&test_src, &test_dst, true, NULL);
}

static void test_nbr_adv_send__foreign_tgt_specified_dst_supply_tl2a_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_nbr_adv_send(&test_src, &test_dst, true, ext_opts);
}

static void test_nbr_adv_send__src_tgt_unspecified_dst_no_supply_tl2a_no_ext_opts(void)
{
    test_nbr_adv_send(&test_src, &ipv6_addr_unspecified, false, NULL);
}

static void test_nbr_adv_send__src_tgt_unspecified_dst_no_supply_tl2a_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_nbr_adv_send(&test_src, &ipv6_addr_unspecified, false, ext_opts);
}

static void test_nbr_adv_send__src_tgt_unspecified_dst_supply_tl2a_no_ext_opts(void)
{
    test_nbr_adv_send(&test_src, &ipv6_addr_unspecified, true, NULL);
}

static void test_nbr_adv_send__src_tgt_unspecified_dst_supply_tl2a_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_nbr_adv_send(&test_src, &ipv6_addr_unspecified, true, ext_opts);
}

static void test_nbr_adv_send__src_tgt_specified_dst_no_supply_tl2a_no_ext_opts(void)
{
    test_nbr_adv_send(&test_src, &test_dst, false, NULL);
}

static void test_nbr_adv_send__src_tgt_specified_dst_no_supply_tl2a_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_nbr_adv_send(&test_src, &test_dst, false, ext_opts);
}

static void test_nbr_adv_send__src_tgt_specified_dst_supply_tl2a_no_ext_opts(void)
{
    test_nbr_adv_send(&test_src, &test_dst, true, NULL);
}

static void test_nbr_adv_send__src_tgt_specified_dst_supply_tl2a_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_nbr_adv_send(&test_src, &test_dst, true, ext_opts);
}

static void test_nbr_adv_send__pktbuf_full1(void)
{
    /* don't be able to fit any more data into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          _align(sizeof(gnrc_pktsnip_t)),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_nbr_adv_send(&test_src, test_netif, &test_dst, true, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_adv_send__pktbuf_full2(void)
{
    /* just be able to fit the TLLAO into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of TLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (2 * _align(sizeof(gnrc_pktsnip_t))) - 16,
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_nbr_adv_send(&test_src, test_netif, &test_dst, true, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_adv_send__pktbuf_full3(void)
{
    /* just be able to fit the TLLAO and NA into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of TLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (3 * _align(sizeof(gnrc_pktsnip_t))) - 16 -
                                          sizeof(ndp_nbr_adv_t),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_nbr_adv_send(&test_src, test_netif, &test_dst, true, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_nbr_adv_send__pktbuf_full4(void)
{
    /* just be able to fit the TLLAO, NA, and IPv6 header into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of TLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (4 * _align(sizeof(gnrc_pktsnip_t))) - 16 -
                                          sizeof(ndp_nbr_adv_t) -
                                          sizeof(ipv6_hdr_t),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_nbr_adv_send(&test_src, test_netif, &test_dst, true, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_sol_send(const ipv6_addr_t *dst)
{
    msg_t msg;
    gnrc_pktsnip_t *pkt;
    ndp_rtr_sol_t *rtr_sol;

    TEST_ASSERT_NOT_NULL(test_netif);
    gnrc_ndp_rtr_sol_send(test_netif, dst);
    msg_receive(&msg);
    TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type);
    pkt = msg.content.ptr;
    /* check packet */
    ASSERT_NETIF_HDR(test_netif, pkt);
    if (dst != NULL) {
        ASSERT_IPV6_HDR(&test_src, dst, pkt->next);
    }
    else {
        ASSERT_IPV6_HDR(&test_src, &ipv6_addr_all_routers_link_local,
                        pkt->next);
    }
    ASSERT_ICMPV6_HDR(pkt->next->next);
    TEST_ASSERT_EQUAL_INT(sizeof(ndp_rtr_sol_t), pkt->next->next->size);
    rtr_sol = pkt->next->next->data;
    TEST_ASSERT_EQUAL_INT(ICMPV6_RTR_SOL, rtr_sol->type);
    if ((dst == NULL) || ipv6_addr_is_link_local(dst)) {
        TEST_ASSERT_NOT_NULL(pkt->next->next->next);
        TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_UNDEF, pkt->next->next->next->type);
        TEST_ASSERT_NOT_NULL(pkt->next->next->next->data);
        ndp_opt_t *opt = pkt->next->next->next->data;
        TEST_ASSERT_EQUAL_INT(NDP_OPT_SL2A, opt->type);
        TEST_ASSERT_MESSAGE(memcmp(&test_src_l2, opt + 1, sizeof(test_src_l2)) == 0,
                            "src_l2 != pkt->l2");
        TEST_ASSERT_NULL(pkt->next->next->next->next);
    }
    else {
        TEST_ASSERT_NULL(pkt->next->next->next);
    }
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_sol_send__dst_NULL(void)
{
    test_rtr_sol_send(NULL);
}

static void test_rtr_sol_send__dst_local(void)
{
    test_rtr_sol_send(&test_dst);
}

static void test_rtr_sol_send__dst_global(void)
{
    ipv6_addr_t dst;

    memcpy(&dst, &test_dst, sizeof(dst));
    ipv6_addr_init_prefix(&dst, &test_pfx, 64);
    test_rtr_sol_send(&test_dst);
}

static void test_rtr_sol_send__pktbuf_full1(void)
{
    /* don't be able to fit any more data into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          _align(sizeof(gnrc_pktsnip_t)),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_rtr_sol_send(test_netif, &test_dst);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_sol_send__pktbuf_full2(void)
{
    /* just be able to fit the SLLAO into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (2 * _align(sizeof(gnrc_pktsnip_t))) - 16,
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_rtr_sol_send(test_netif, &test_dst);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_sol_send__pktbuf_full3(void)
{
    /* just be able to fit the SLLAO and RS into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (3 * _align(sizeof(gnrc_pktsnip_t))) - 16 -
                                          sizeof(ndp_rtr_sol_t),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_rtr_sol_send(test_netif, &test_dst);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_sol_send__pktbuf_full4(void)
{
    /* just be able to fit the SLLAO, RS, and IPv6 header into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (4 * _align(sizeof(gnrc_pktsnip_t))) - 16 -
                                          sizeof(ndp_rtr_sol_t) -
                                          sizeof(ipv6_hdr_t),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_rtr_sol_send(test_netif, &test_dst);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ROUTER)
static void test_rtr_adv_send(const ipv6_addr_t *src, const ipv6_addr_t *dst,
                              bool fin, gnrc_pktsnip_t *exp_ext_opts)
{
    msg_t msg;
    gnrc_pktsnip_t *pkt;
    ndp_rtr_adv_t *rtr_adv;

    TEST_ASSERT_NOT_NULL(test_netif);
    gnrc_ndp_rtr_adv_send(test_netif, src, dst, fin, exp_ext_opts);
    msg_receive(&msg);
    TEST_ASSERT_EQUAL_INT(GNRC_NETAPI_MSG_TYPE_SND, msg.type);
    pkt = msg.content.ptr;
    /* check packet */
    ASSERT_NETIF_HDR(test_netif, pkt);
    /* testing for unspecified source is complicated so we skip it here and
     * do it in later integration tests */
    if (dst != NULL) {
        ASSERT_IPV6_HDR(&test_src, dst, pkt->next);
    }
    else {
        ASSERT_IPV6_HDR(&test_src, &ipv6_addr_all_nodes_link_local,
                        pkt->next);
    }
    ASSERT_ICMPV6_HDR(pkt->next->next);
    TEST_ASSERT_EQUAL_INT(sizeof(ndp_rtr_adv_t), pkt->next->next->size);
    rtr_adv = pkt->next->next->data;
    TEST_ASSERT_EQUAL_INT(ICMPV6_RTR_ADV, rtr_adv->type);
    if (fin) {
        TEST_ASSERT_EQUAL_INT(0, rtr_adv->ltime.u16);
    }
    else {
        TEST_ASSERT_MESSAGE(rtr_adv->ltime.u16 != 0, "rtr_adv->ltime == 0");
    }
    /* check for SLLAO */
    TEST_ASSERT_NOT_NULL(pkt->next->next->next);
    TEST_ASSERT_EQUAL_INT(GNRC_NETTYPE_UNDEF, pkt->next->next->next->type);
    TEST_ASSERT_NOT_NULL(pkt->next->next->next->data);
    ndp_opt_t *opt = pkt->next->next->next->data;
    TEST_ASSERT_EQUAL_INT(NDP_OPT_SL2A, opt->type);
    TEST_ASSERT_MESSAGE(memcmp(&test_src_l2, opt + 1, sizeof(test_src_l2)) == 0,
                        "src_l2 != pkt->l2");
    TEST_ASSERT_MESSAGE(pkt->next->next->next->next == exp_ext_opts,
                        "ext_opts set wrong");
    gnrc_pktbuf_release(pkt);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_adv_send__src_NULL_dst_NULL_no_fin_no_ext_opts(void)
{
    test_rtr_adv_send(NULL, NULL, false, NULL);
}

static void test_rtr_adv_send__src_NULL_dst_NULL_no_fin_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_rtr_adv_send(NULL, NULL, false, ext_opts);
}

static void test_rtr_adv_send__src_NULL_dst_NULL_fin_no_ext_opts(void)
{
    test_rtr_adv_send(NULL, NULL, true, NULL);
}

static void test_rtr_adv_send__src_NULL_dst_NULL_fin_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_rtr_adv_send(NULL, NULL, true, ext_opts);
}

static void test_rtr_adv_send__src_NULL_dst_no_fin_no_ext_opts(void)
{
    test_rtr_adv_send(NULL, &test_dst, false, NULL);
}

static void test_rtr_adv_send__src_NULL_dst_no_fin_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_rtr_adv_send(NULL, &test_dst, false, ext_opts);
}

static void test_rtr_adv_send__src_NULL_dst_fin_no_ext_opts(void)
{
    test_rtr_adv_send(NULL, &test_dst, true, NULL);
}

static void test_rtr_adv_send__src_NULL_dst_fin_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_rtr_adv_send(NULL, &test_dst, true, ext_opts);
}

static void test_rtr_adv_send__src_dst_NULL_no_fin_no_ext_opts(void)
{
    test_rtr_adv_send(&test_src, NULL, false, NULL);
}

static void test_rtr_adv_send__src_dst_NULL_no_fin_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_rtr_adv_send(&test_src, NULL, false, ext_opts);
}

static void test_rtr_adv_send__src_dst_NULL_fin_no_ext_opts(void)
{
    test_rtr_adv_send(&test_src, NULL, true, NULL);
}

static void test_rtr_adv_send__src_dst_NULL_fin_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_rtr_adv_send(&test_src, NULL, true, ext_opts);
}

static void test_rtr_adv_send__src_dst_no_fin_no_ext_opts(void)
{
    test_rtr_adv_send(&test_src, &test_dst, false, NULL);
}

static void test_rtr_adv_send__src_dst_no_fin_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_rtr_adv_send(&test_src, &test_dst, false, ext_opts);
}

static void test_rtr_adv_send__src_dst_fin_no_ext_opts(void)
{
    test_rtr_adv_send(&test_src, &test_dst, true, NULL);
}

static void test_rtr_adv_send__src_dst_fin_ext_opts(void)
{
    gnrc_pktsnip_t *ext_opts = gnrc_pktbuf_add(NULL, NULL, 8U, GNRC_NETTYPE_UNDEF);
    test_rtr_adv_send(&test_src, &test_dst, true, ext_opts);
}

static void test_rtr_adv_send__pktbuf_full1(void)
{
    /* don't be able to fit any more data into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          _align(sizeof(gnrc_pktsnip_t)),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_rtr_adv_send(test_netif, &test_src, &test_dst, false, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_adv_send__pktbuf_full2(void)
{
    /* just be able to fit the SLLAO into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (2 * _align(sizeof(gnrc_pktsnip_t))) - 16,
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_rtr_adv_send(test_netif, &test_src, &test_dst, false, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_adv_send__pktbuf_full3(void)
{
    /* just be able to fit the SLLAO and RA into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (3 * _align(sizeof(gnrc_pktsnip_t))) - 16 -
                                          sizeof(ndp_rtr_adv_t),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_rtr_adv_send(test_netif, &test_src, &test_dst, false, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}

static void test_rtr_adv_send__pktbuf_full4(void)
{
    /* just be able to fit the SLLAO, RA, and IPv6 header into packet buffer
     * - sizeof(gnrc_pktsnip_t) + pktbuf internal padding
     * - 16 == size of SLLAO for IEEE 802.15.4 */
    gnrc_pktsnip_t *tmp = gnrc_pktbuf_add(NULL, NULL,
                                          CONFIG_GNRC_PKTBUF_SIZE -
                                          (4 * _align(sizeof(gnrc_pktsnip_t))) - 16 -
                                          sizeof(ndp_rtr_adv_t) -
                                          sizeof(ipv6_hdr_t),
                                          GNRC_NETTYPE_UNDEF);
    TEST_ASSERT_NOT_NULL(tmp);
    gnrc_ndp_rtr_adv_send(test_netif, &test_src, &test_dst, false, NULL);
    gnrc_pktbuf_release(tmp);
    TEST_ASSERT(gnrc_pktbuf_is_sane());
    TEST_ASSERT(gnrc_pktbuf_is_empty());
}
#endif

static Test *tests_gnrc_ndp_build(void)
{
    EMB_UNIT_TESTFIXTURES(fixtures) {
        new_TestFixture(test_nbr_sol_build__pktbuf_full),
        new_TestFixture(test_nbr_sol_build__success),
        new_TestFixture(test_nbr_adv_build__pktbuf_full),
        new_TestFixture(test_nbr_adv_build__success_without_flags),
        new_TestFixture(test_nbr_adv_build__success_with_flags),
        new_TestFixture(test_rtr_sol_build__pktbuf_full),
        new_TestFixture(test_rtr_sol_build__success),
        new_TestFixture(test_rtr_adv_build__pktbuf_full),
        new_TestFixture(test_rtr_adv_build__success_without_flags),
        new_TestFixture(test_rtr_adv_build__success_with_flags),
        new_TestFixture(test_opt_build__pktbuf_full),
        new_TestFixture(test_opt_build__success),
        new_TestFixture(test_opt_sl2a_build__pktbuf_full),
        new_TestFixture(test_opt_sl2a_build__success),
        new_TestFixture(test_opt_tl2a_build__pktbuf_full),
        new_TestFixture(test_opt_tl2a_build__success),
        new_TestFixture(test_opt_pi_build__pktbuf_full),
        new_TestFixture(test_opt_pi_build__success_without_flags),
        new_TestFixture(test_opt_pi_build__success_with_flags),
        new_TestFixture(test_opt_mtu_build__pktbuf_full),
        new_TestFixture(test_opt_mtu_build__success),
    };

    EMB_UNIT_TESTCALLER(tests, set_up, NULL, fixtures);

    return (Test *)&tests;
}

static Test *tests_gnrc_ndp_send(void)
{
    EMB_UNIT_TESTFIXTURES(fixtures) {
        new_TestFixture(test_nbr_sol_send__src_NULL),
        new_TestFixture(test_nbr_sol_send__src_unspecified),
        new_TestFixture(test_nbr_sol_send__src_NOT_NULL),
        new_TestFixture(test_nbr_sol_send__pktbuf_full1),
        new_TestFixture(test_nbr_sol_send__pktbuf_full2),
        new_TestFixture(test_nbr_sol_send__pktbuf_full3),
        new_TestFixture(test_nbr_sol_send__pktbuf_full4),
        new_TestFixture(test_nbr_adv_send__foreign_tgt_unspecified_dst_no_supply_tl2a_no_ext_opts),
        new_TestFixture(test_nbr_adv_send__foreign_tgt_unspecified_dst_no_supply_tl2a_ext_opts),
        new_TestFixture(test_nbr_adv_send__foreign_tgt_unspecified_dst_supply_tl2a_no_ext_opts),
        new_TestFixture(test_nbr_adv_send__foreign_tgt_unspecified_dst_supply_tl2a_ext_opts),
        new_TestFixture(test_nbr_adv_send__foreign_tgt_specified_dst_no_supply_tl2a_no_ext_opts),
        new_TestFixture(test_nbr_adv_send__foreign_tgt_specified_dst_no_supply_tl2a_ext_opts),
        new_TestFixture(test_nbr_adv_send__foreign_tgt_specified_dst_supply_tl2a_no_ext_opts),
        new_TestFixture(test_nbr_adv_send__foreign_tgt_specified_dst_supply_tl2a_ext_opts),
        new_TestFixture(test_nbr_adv_send__src_tgt_unspecified_dst_no_supply_tl2a_no_ext_opts),
        new_TestFixture(test_nbr_adv_send__src_tgt_unspecified_dst_no_supply_tl2a_ext_opts),
        new_TestFixture(test_nbr_adv_send__src_tgt_unspecified_dst_supply_tl2a_no_ext_opts),
        new_TestFixture(test_nbr_adv_send__src_tgt_unspecified_dst_supply_tl2a_ext_opts),
        new_TestFixture(test_nbr_adv_send__src_tgt_specified_dst_no_supply_tl2a_no_ext_opts),
        new_TestFixture(test_nbr_adv_send__src_tgt_specified_dst_no_supply_tl2a_ext_opts),
        new_TestFixture(test_nbr_adv_send__src_tgt_specified_dst_supply_tl2a_no_ext_opts),
        new_TestFixture(test_nbr_adv_send__src_tgt_specified_dst_supply_tl2a_ext_opts),
        new_TestFixture(test_nbr_adv_send__pktbuf_full1),
        new_TestFixture(test_nbr_adv_send__pktbuf_full2),
        new_TestFixture(test_nbr_adv_send__pktbuf_full3),
        new_TestFixture(test_nbr_adv_send__pktbuf_full4),
        new_TestFixture(test_rtr_sol_send__dst_NULL),
        new_TestFixture(test_rtr_sol_send__dst_local),
        new_TestFixture(test_rtr_sol_send__dst_global),
        new_TestFixture(test_rtr_sol_send__pktbuf_full1),
        new_TestFixture(test_rtr_sol_send__pktbuf_full2),
        new_TestFixture(test_rtr_sol_send__pktbuf_full3),
        new_TestFixture(test_rtr_sol_send__pktbuf_full4),
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ROUTER)
        new_TestFixture(test_rtr_adv_send__src_NULL_dst_NULL_no_fin_no_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_NULL_dst_NULL_no_fin_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_NULL_dst_NULL_fin_no_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_NULL_dst_NULL_fin_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_NULL_dst_no_fin_no_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_NULL_dst_no_fin_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_NULL_dst_fin_no_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_NULL_dst_fin_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_dst_NULL_no_fin_no_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_dst_NULL_no_fin_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_dst_NULL_fin_no_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_dst_NULL_fin_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_dst_no_fin_no_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_dst_no_fin_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_dst_fin_no_ext_opts),
        new_TestFixture(test_rtr_adv_send__src_dst_fin_ext_opts),
        new_TestFixture(test_rtr_adv_send__pktbuf_full1),
        new_TestFixture(test_rtr_adv_send__pktbuf_full2),
        new_TestFixture(test_rtr_adv_send__pktbuf_full3),
        new_TestFixture(test_rtr_adv_send__pktbuf_full4),
#endif
    };

    EMB_UNIT_TESTCALLER(tests, set_up, NULL, fixtures);
    return (Test *)&tests;
}

int main(void)
{
    TESTS_START();
    TESTS_RUN(tests_gnrc_ndp_build());
    init_pkt_handler();
    TESTS_RUN(tests_gnrc_ndp_send());
    TESTS_END();

    return 0;
}

#define MSG_QUEUE_SIZE          (2)

static char test_netif_stack[THREAD_STACKSIZE_DEFAULT];
static msg_t msg_queue_main[MSG_QUEUE_SIZE];
static gnrc_netreg_entry_t netreg_entry;
static netdev_test_t dev;

static int _test_netif_send(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt)
{
    (void)netif;
    gnrc_pktbuf_release(pkt);
    return 0;
}

static gnrc_pktsnip_t *_test_netif_recv(gnrc_netif_t *netif)
{
    (void)netif;
    return NULL;
}

static int _test_netif_set(gnrc_netif_t *netif, const gnrc_netapi_opt_t *opt)
{
    (void)netif;
    (void)opt;
    return -ENOTSUP;
}

static const gnrc_netif_ops_t _test_netif_ops = {
    .init = gnrc_netif_default_init,
    .send = _test_netif_send,
    .recv = _test_netif_recv,
    .get = gnrc_netif_get_from_netdev,
    .set = _test_netif_set,
};

static int _netdev_test_address_long(netdev_t *dev, void *value, size_t max_len)
{
    (void)dev;
    expect(max_len >= sizeof(test_src_l2));
    memcpy(value, test_src_l2, sizeof(test_src_l2));
    return sizeof(test_src_l2);
}

static int _netdev_test_proto(netdev_t *dev, void *value, size_t max_len)
{
    (void)dev;
     expect(max_len == sizeof(gnrc_nettype_t));
     *((gnrc_nettype_t *)value) = GNRC_NETTYPE_UNDEF;
     return sizeof(gnrc_nettype_t);
}

static int _netdev_test_src_len(netdev_t *dev, void *value, size_t max_len)
{
    (void)dev;
     expect(max_len == sizeof(uint16_t));
     *((uint16_t *)value) = sizeof(test_src_l2);
     return sizeof(uint16_t);
}

static int _netdev_test_max_pdu_size(netdev_t *dev, void *value, size_t max_len)
{
    (void)dev;
     expect(max_len == sizeof(uint16_t));
     *((uint16_t *)value) = 100U;
     return sizeof(uint16_t);
}

static int _netdev_test_device_type(netdev_t *dev, void *value, size_t max_len)
{
    (void)dev;
     expect(max_len == sizeof(uint16_t));
     *((uint16_t *)value) = NETDEV_TYPE_IEEE802154;
     return sizeof(uint16_t);
}

static void init_pkt_handler(void)
{
    msg_init_queue(msg_queue_main, MSG_QUEUE_SIZE);
    gnrc_netreg_entry_init_pid(&netreg_entry, GNRC_NETREG_DEMUX_CTX_ALL,
                               thread_getpid());
    gnrc_netreg_register(GNRC_NETTYPE_NDP, &netreg_entry);
    netdev_test_setup(&dev, NULL);
    netdev_test_set_get_cb(&dev, NETOPT_ADDRESS_LONG,
                           _netdev_test_address_long);
    netdev_test_set_get_cb(&dev, NETOPT_PROTO, _netdev_test_proto);
    netdev_test_set_get_cb(&dev, NETOPT_SRC_LEN, _netdev_test_src_len);
    netdev_test_set_get_cb(&dev, NETOPT_MAX_PDU_SIZE,
                           _netdev_test_max_pdu_size);
    netdev_test_set_get_cb(&dev, NETOPT_DEVICE_TYPE, _netdev_test_device_type);
    int res = gnrc_netif_create(&_netif, test_netif_stack, sizeof(test_netif_stack),
                                   GNRC_NETIF_PRIO, "test-netif",
                                   &dev.netdev.netdev, &_test_netif_ops);
    test_netif = &_netif;
    TEST_ASSERT_MESSAGE(res == 0,
                        "Unable to start test interface");
    memcpy(&test_netif->ipv6.addrs[0], &test_src,
           sizeof(test_netif->ipv6.addrs[0]));
    test_netif->ipv6.addrs_flags[0] = GNRC_NETIF_IPV6_ADDRS_FLAGS_STATE_VALID;
    memcpy(test_netif->l2addr, test_src_l2, sizeof(test_netif->l2addr));
    test_netif->l2addr_len = sizeof(test_src_l2);
}

static inline size_t ceil8(size_t size)
{
    if (size % 8) {
        return ((size / 8) + 1) * 8;
    }
    else {
        return size;
    }
}
