summaryrefslogtreecommitdiff
path: root/test/unit
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-10-16 08:10:14 -0600
committerTom Rini <trini@konsulko.com>2024-10-16 08:10:14 -0600
commitf3f86fd1fe0fb288356bff78f8a6fa2edf89e3fc (patch)
treef0a99ea87d92f63895a6d053e3185838ebecf2d0 /test/unit
Squashed 'lib/lwip/lwip/' content from commit 0a0452b2c39b
git-subtree-dir: lib/lwip/lwip git-subtree-split: 0a0452b2c39bdd91e252aef045c115f88f6ca773
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/Filelists.cmake38
-rw-r--r--test/unit/Filelists.mk54
-rw-r--r--test/unit/Makefile11
-rw-r--r--test/unit/api/test_sockets.c852
-rw-r--r--test/unit/api/test_sockets.h8
-rw-r--r--test/unit/arch/sys_arch.c387
-rw-r--r--test/unit/arch/sys_arch.h79
-rw-r--r--test/unit/core/test_def.c84
-rw-r--r--test/unit/core/test_def.h8
-rw-r--r--test/unit/core/test_dns.c52
-rw-r--r--test/unit/core/test_dns.h8
-rw-r--r--test/unit/core/test_mem.c221
-rw-r--r--test/unit/core/test_mem.h8
-rw-r--r--test/unit/core/test_netif.c285
-rw-r--r--test/unit/core/test_netif.h8
-rw-r--r--test/unit/core/test_pbuf.c359
-rw-r--r--test/unit/core/test_pbuf.h8
-rw-r--r--test/unit/core/test_timers.c233
-rw-r--r--test/unit/core/test_timers.h8
-rw-r--r--test/unit/dhcp/test_dhcp.c1103
-rw-r--r--test/unit/dhcp/test_dhcp.h8
-rw-r--r--test/unit/etharp/test_etharp.c273
-rw-r--r--test/unit/etharp/test_etharp.h8
-rw-r--r--test/unit/ip4/test_ip4.c342
-rw-r--r--test/unit/ip4/test_ip4.h8
-rw-r--r--test/unit/ip6/test_ip6.c469
-rw-r--r--test/unit/ip6/test_ip6.h8
-rw-r--r--test/unit/lwip_check.h52
-rw-r--r--test/unit/lwip_unittests.c125
-rw-r--r--test/unit/lwipopts.h94
-rw-r--r--test/unit/mdns/test_mdns.c916
-rw-r--r--test/unit/mdns/test_mdns.h8
-rw-r--r--test/unit/mqtt/test_mqtt.c115
-rw-r--r--test/unit/mqtt/test_mqtt.h8
-rw-r--r--test/unit/ppp/test_pppos.c67
-rw-r--r--test/unit/ppp/test_pppos.h13
-rw-r--r--test/unit/tcp/tcp_helper.c325
-rw-r--r--test/unit/tcp/tcp_helper.h58
-rw-r--r--test/unit/tcp/test_tcp.c1700
-rw-r--r--test/unit/tcp/test_tcp.h8
-rw-r--r--test/unit/tcp/test_tcp_oos.c1018
-rw-r--r--test/unit/tcp/test_tcp_oos.h8
-rw-r--r--test/unit/tcp/test_tcp_state.c665
-rw-r--r--test/unit/tcp/test_tcp_state.h8
-rw-r--r--test/unit/udp/test_udp.c472
-rw-r--r--test/unit/udp/test_udp.h8
46 files changed, 10598 insertions, 0 deletions
diff --git a/test/unit/Filelists.cmake b/test/unit/Filelists.cmake
new file mode 100644
index 00000000000..b3db893c73b
--- /dev/null
+++ b/test/unit/Filelists.cmake
@@ -0,0 +1,38 @@
+# This file is indended to be included in end-user CMakeLists.txt
+# include(/path/to/Filelists.cmake)
+# It assumes the variable LWIP_DIR is defined pointing to the
+# root path of lwIP sources.
+#
+# This file is NOT designed (on purpose) to be used as cmake
+# subdir via add_subdirectory()
+# The intention is to provide greater flexibility to users to
+# create their own targets using the *_SRCS variables.
+
+if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
+ include_guard(GLOBAL)
+endif()
+
+set(LWIP_TESTDIR ${LWIP_DIR}/test/unit)
+set(LWIP_TESTFILES
+ ${LWIP_TESTDIR}/lwip_unittests.c
+ ${LWIP_TESTDIR}/api/test_sockets.c
+ ${LWIP_TESTDIR}/arch/sys_arch.c
+ ${LWIP_TESTDIR}/core/test_def.c
+ ${LWIP_TESTDIR}/core/test_dns.c
+ ${LWIP_TESTDIR}/core/test_mem.c
+ ${LWIP_TESTDIR}/core/test_netif.c
+ ${LWIP_TESTDIR}/core/test_pbuf.c
+ ${LWIP_TESTDIR}/core/test_timers.c
+ ${LWIP_TESTDIR}/dhcp/test_dhcp.c
+ ${LWIP_TESTDIR}/etharp/test_etharp.c
+ ${LWIP_TESTDIR}/ip4/test_ip4.c
+ ${LWIP_TESTDIR}/ip6/test_ip6.c
+ ${LWIP_TESTDIR}/mdns/test_mdns.c
+ ${LWIP_TESTDIR}/mqtt/test_mqtt.c
+ ${LWIP_TESTDIR}/tcp/tcp_helper.c
+ ${LWIP_TESTDIR}/tcp/test_tcp_oos.c
+ ${LWIP_TESTDIR}/tcp/test_tcp_state.c
+ ${LWIP_TESTDIR}/tcp/test_tcp.c
+ ${LWIP_TESTDIR}/udp/test_udp.c
+ ${LWIP_TESTDIR}/ppp/test_pppos.c
+)
diff --git a/test/unit/Filelists.mk b/test/unit/Filelists.mk
new file mode 100644
index 00000000000..c33e1b04f0c
--- /dev/null
+++ b/test/unit/Filelists.mk
@@ -0,0 +1,54 @@
+#
+# Copyright (c) 2001, 2002 Swedish Institute of Computer Science.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# 3. The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+# OF SUCH DAMAGE.
+#
+# This file is part of the lwIP TCP/IP stack.
+#
+# Author: Adam Dunkels <adam@sics.se>
+#
+
+TESTDIR=$(LWIPDIR)/../test/unit
+TESTFILES=$(TESTDIR)/lwip_unittests.c \
+ $(TESTDIR)/api/test_sockets.c \
+ $(TESTDIR)/arch/sys_arch.c \
+ $(TESTDIR)/core/test_def.c \
+ $(TESTDIR)/core/test_dns.c \
+ $(TESTDIR)/core/test_mem.c \
+ $(TESTDIR)/core/test_netif.c \
+ $(TESTDIR)/core/test_pbuf.c \
+ $(TESTDIR)/core/test_timers.c \
+ $(TESTDIR)/dhcp/test_dhcp.c \
+ $(TESTDIR)/etharp/test_etharp.c \
+ $(TESTDIR)/ip4/test_ip4.c \
+ $(TESTDIR)/ip6/test_ip6.c \
+ $(TESTDIR)/mdns/test_mdns.c \
+ $(TESTDIR)/mqtt/test_mqtt.c \
+ $(TESTDIR)/tcp/tcp_helper.c \
+ $(TESTDIR)/tcp/test_tcp_oos.c \
+ $(TESTDIR)/tcp/test_tcp_state.c \
+ $(TESTDIR)/tcp/test_tcp.c \
+ $(TESTDIR)/udp/test_udp.c \
+ $(TESTDIR)/ppp/test_pppos.c
+
diff --git a/test/unit/Makefile b/test/unit/Makefile
new file mode 100644
index 00000000000..a27c72ce57a
--- /dev/null
+++ b/test/unit/Makefile
@@ -0,0 +1,11 @@
+# Shortcuts to building and running tests on unix platforms.
+# Output files will be written to the directory listed below.
+
+all:
+ cd ../../contrib/ports/unix/check/ && $(MAKE)
+
+check:
+ cd ../../contrib/ports/unix/check/ && $(MAKE) check
+
+clean:
+ cd ../../contrib/ports/unix/check/ && $(MAKE) clean
diff --git a/test/unit/api/test_sockets.c b/test/unit/api/test_sockets.c
new file mode 100644
index 00000000000..3faa5ff9f18
--- /dev/null
+++ b/test/unit/api/test_sockets.c
@@ -0,0 +1,852 @@
+#include "test_sockets.h"
+
+#include "lwip/mem.h"
+#include "lwip/opt.h"
+#include "lwip/sockets.h"
+#include "lwip/priv/sockets_priv.h"
+#include "lwip/stats.h"
+
+#include "lwip/tcpip.h"
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/api.h"
+
+
+static int
+test_sockets_get_used_count(void)
+{
+ int used = 0;
+ int i;
+
+ for (i = 0; i < NUM_SOCKETS; i++) {
+ struct lwip_sock* s = lwip_socket_dbg_get_socket(i);
+ if (s != NULL) {
+ if (s->fd_used) {
+ used++;
+ }
+ }
+ }
+ return used;
+}
+
+
+/* Setups/teardown functions */
+
+static void
+sockets_setup(void)
+{
+ /* expect full free heap */
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+sockets_teardown(void)
+{
+ fail_unless(test_sockets_get_used_count() == 0);
+ /* poll until all memory is released... */
+ tcpip_thread_poll_one();
+ while (tcp_tw_pcbs) {
+ tcp_abort(tcp_tw_pcbs);
+ tcpip_thread_poll_one();
+ }
+ tcpip_thread_poll_one();
+ /* ensure full free heap */
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+#ifndef NUM_SOCKETS
+#define NUM_SOCKETS MEMP_NUM_NETCONN
+#endif
+
+#if LWIP_SOCKET
+static int
+test_sockets_alloc_socket_nonblocking(int domain, int type)
+{
+ int s = lwip_socket(domain, type, 0);
+ if (s >= 0) {
+ int ret = lwip_fcntl(s, F_SETFL, O_NONBLOCK);
+ fail_unless(ret == 0);
+ }
+ return s;
+}
+
+/* Verify basic sockets functionality
+ */
+START_TEST(test_sockets_basics)
+{
+ int s, i, ret;
+ int s2[NUM_SOCKETS];
+ LWIP_UNUSED_ARG(_i);
+
+ s = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s >= 0);
+ lwip_close(s);
+
+ for (i = 0; i < NUM_SOCKETS; i++) {
+ s2[i] = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s2[i] >= 0);
+ }
+
+ /* all sockets used, now it should fail */
+ s = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s == -1);
+ /* close one socket */
+ ret = lwip_close(s2[0]);
+ fail_unless(ret == 0);
+ /* now it should succeed */
+ s2[0] = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s2[0] >= 0);
+
+ /* close all sockets */
+ for (i = 0; i < NUM_SOCKETS; i++) {
+ ret = lwip_close(s2[i]);
+ fail_unless(ret == 0);
+ }
+}
+END_TEST
+
+static void test_sockets_allfunctions_basic_domain(int domain)
+{
+ int s, s2, s3, ret;
+ struct sockaddr_storage addr, addr2;
+ socklen_t addrlen, addr2len;
+ char buf[4];
+ /* listen socket */
+ s = lwip_socket(domain, SOCK_STREAM, 0);
+ fail_unless(s >= 0);
+
+ ret = lwip_listen(s, 0);
+ fail_unless(ret == 0);
+
+ addrlen = sizeof(addr);
+ ret = lwip_getsockname(s, (struct sockaddr*)&addr, &addrlen);
+ fail_unless(ret == 0);
+
+ s2 = test_sockets_alloc_socket_nonblocking(domain, SOCK_STREAM);
+ fail_unless(s2 >= 0);
+ /* nonblocking connect s2 to s (but use loopback address) */
+ if (domain == AF_INET) {
+#if LWIP_IPV4
+ struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
+ addr4->sin_addr.s_addr = PP_HTONL(INADDR_LOOPBACK);
+#endif
+ } else {
+#if LWIP_IPV6
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&addr;
+ struct in6_addr lo6 = IN6ADDR_LOOPBACK_INIT;
+ addr6->sin6_addr = lo6;
+#endif
+ }
+ ret = lwip_connect(s2, (struct sockaddr*)&addr, addrlen);
+ fail_unless(ret == -1);
+ fail_unless(errno == EINPROGRESS);
+ ret = lwip_connect(s2, (struct sockaddr*)&addr, addrlen);
+ fail_unless(ret == -1);
+ fail_unless(errno == EALREADY);
+
+ while(tcpip_thread_poll_one());
+
+ s3 = lwip_accept(s, (struct sockaddr*)&addr2, &addr2len);
+ fail_unless(s3 >= 0);
+
+ ret = lwip_connect(s2, (struct sockaddr*)&addr, addrlen);
+ fail_unless(ret == -1);
+ fail_unless(errno == EISCONN);
+
+ /* write from server to client */
+ ret = lwip_write(s3, "test", 4);
+ fail_unless(ret == 4);
+
+ ret = lwip_shutdown(s3, SHUT_WR);
+ fail_unless(ret == 0);
+
+ while(tcpip_thread_poll_one());
+
+ ret = lwip_recv(s2, buf, 3, MSG_PEEK);
+ fail_unless(ret == 3);
+
+ ret = lwip_recv(s2, buf, 3, MSG_PEEK);
+ fail_unless(ret == 3);
+
+ ret = lwip_read(s2, buf, 4);
+ fail_unless(ret == 4);
+
+ ret = lwip_read(s2, buf, 1);
+ fail_unless(ret == 0);
+
+ ret = lwip_read(s2, buf, 1);
+ fail_unless(ret == -1);
+
+ ret = lwip_write(s2, "foo", 3);
+ fail_unless(ret == 3);
+
+ ret = lwip_close(s2);
+ fail_unless(ret == 0);
+
+ while(tcpip_thread_poll_one());
+
+ /* read one byte more than available to check handling FIN */
+ ret = lwip_read(s3, buf, 4);
+ fail_unless(ret == 3);
+
+ ret = lwip_read(s3, buf, 1);
+ fail_unless(ret == 0);
+
+ ret = lwip_read(s3, buf, 1);
+ fail_unless(ret == -1);
+
+ while(tcpip_thread_poll_one());
+
+ ret = lwip_close(s);
+ fail_unless(ret == 0);
+ ret = lwip_close(s3);
+ fail_unless(ret == 0);
+}
+
+/* Try to step through all sockets functions once...
+ */
+START_TEST(test_sockets_allfunctions_basic)
+{
+ LWIP_UNUSED_ARG(_i);
+#if LWIP_IPV4
+ test_sockets_allfunctions_basic_domain(AF_INET);
+#endif
+#if LWIP_IPV6
+ test_sockets_allfunctions_basic_domain(AF_INET6);
+#endif
+}
+END_TEST
+
+static void test_sockets_init_loopback_addr(int domain, struct sockaddr_storage *addr_st, socklen_t *sz)
+{
+ memset(addr_st, 0, sizeof(*addr_st));
+ switch(domain) {
+#if LWIP_IPV6
+ case AF_INET6: {
+ struct sockaddr_in6 *addr = (struct sockaddr_in6*)addr_st;
+ struct in6_addr lo6 = IN6ADDR_LOOPBACK_INIT;
+ addr->sin6_family = AF_INET6;
+ addr->sin6_port = 0; /* use ephemeral port */
+ addr->sin6_addr = lo6;
+ *sz = sizeof(*addr);
+ }
+ break;
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4
+ case AF_INET: {
+ struct sockaddr_in *addr = (struct sockaddr_in*)addr_st;
+ addr->sin_family = AF_INET;
+ addr->sin_port = 0; /* use ephemeral port */
+ addr->sin_addr.s_addr = PP_HTONL(INADDR_LOOPBACK);
+ *sz = sizeof(*addr);
+ }
+ break;
+#endif /* LWIP_IPV4 */
+ default:
+ *sz = 0;
+ fail();
+ break;
+ }
+}
+
+static void test_sockets_msgapi_update_iovs(struct msghdr *msg, size_t bytes)
+{
+ msg_iovlen_t i;
+
+ /* note: this modifies the underyling iov_base and iov_len for a partial
+ read for an individual vector. This updates the msg->msg_iov pointer
+ to skip fully consumed vectors */
+
+ /* process fully consumed vectors */
+ for (i = 0; i < msg->msg_iovlen; i++) {
+ if (msg->msg_iov[i].iov_len <= bytes) {
+ /* reduce bytes by amount of this vector */
+ bytes -= msg->msg_iov[i].iov_len;
+ } else {
+ break; /* iov not fully consumed */
+ }
+ }
+
+ /* slide down over fully consumed vectors */
+ msg->msg_iov = &msg->msg_iov[i];
+ msg->msg_iovlen -= i;
+
+ /* update new first vector with any remaining amount */
+ msg->msg_iov[0].iov_base = ((u8_t *)msg->msg_iov[0].iov_base + bytes);
+ msg->msg_iov[0].iov_len -= bytes;
+}
+
+static void test_sockets_msgapi_tcp(int domain)
+{
+ #define BUF_SZ (TCP_SND_BUF/4)
+ #define TOTAL_DATA_SZ (BUF_SZ*8) /* ~(TCP_SND_BUF*2) that accounts for integer rounding */
+ #define NEED_TRAILER (BUF_SZ % 4 != 0)
+ int listnr, s1, s2, i, ret, opt;
+ int bytes_written, bytes_read;
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_size;
+ struct iovec siovs[8];
+ struct msghdr smsg;
+ u8_t * snd_buf;
+ struct iovec riovs[5];
+ struct iovec riovs_tmp[5];
+ struct msghdr rmsg;
+ u8_t * rcv_buf;
+ int rcv_off;
+ int rcv_trailer = 0;
+ u8_t val;
+
+ test_sockets_init_loopback_addr(domain, &addr_storage, &addr_size);
+
+ listnr = test_sockets_alloc_socket_nonblocking(domain, SOCK_STREAM);
+ fail_unless(listnr >= 0);
+ s1 = test_sockets_alloc_socket_nonblocking(domain, SOCK_STREAM);
+ fail_unless(s1 >= 0);
+
+ /* setup a listener socket on loopback with ephemeral port */
+ ret = lwip_bind(listnr, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == 0);
+ ret = lwip_listen(listnr, 0);
+ fail_unless(ret == 0);
+
+ /* update address with ephemeral port */
+ ret = lwip_getsockname(listnr, (struct sockaddr*)&addr_storage, &addr_size);
+ fail_unless(ret == 0);
+
+ /* connect, won't complete until we accept it */
+ ret = lwip_connect(s1, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == -1);
+ fail_unless(errno == EINPROGRESS);
+
+ while (tcpip_thread_poll_one());
+
+ /* accept, creating the other side of the connection */
+ s2 = lwip_accept(listnr, NULL, NULL);
+ fail_unless(s2 >= 0);
+
+ /* double check s1 is connected */
+ ret = lwip_connect(s1, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == -1);
+ fail_unless(errno == EISCONN);
+
+ /* set s2 to non-blocking, not inherited from listener */
+ opt = lwip_fcntl(s2, F_GETFL, 0);
+ fail_unless(opt == O_RDWR);
+ opt = O_NONBLOCK;
+ ret = lwip_fcntl(s2, F_SETFL, opt);
+ fail_unless(ret == 0);
+
+ /* we are done with listener, close it */
+ ret = lwip_close(listnr);
+ fail_unless(ret == 0);
+
+ /* allocate a buffer for a stream of incrementing hex (0x00..0xFF) which we will use
+ to create an input vector set that is larger than the TCP's send buffer. This will
+ force execution of the partial IO vector send case */
+ snd_buf = (u8_t*)mem_malloc(BUF_SZ);
+ val = 0x00;
+ fail_unless(snd_buf != NULL);
+ for (i = 0; i < BUF_SZ; i++,val++) {
+ snd_buf[i] = val;
+ }
+
+ /* send the buffer 8 times in one message, equating to TOTAL_DATA_SZ */
+ for (i = 0; i < 8; i++) {
+ siovs[i].iov_base = snd_buf;
+ siovs[i].iov_len = BUF_SZ;
+ }
+
+ /* allocate a receive buffer, same size as snd_buf for easy verification */
+ rcv_buf = (u8_t*)mem_calloc(1, BUF_SZ);
+ fail_unless(rcv_buf != NULL);
+ /* split across iovs */
+ for (i = 0; i < 4; i++) {
+ riovs[i].iov_base = &rcv_buf[i*(BUF_SZ/4)];
+ riovs[i].iov_len = BUF_SZ/4;
+ }
+ /* handling trailing bytes if buffer doesn't evenly divide by 4 */
+#if NEED_TRAILER
+ if ((BUF_SZ % 4) != 0) {
+ riovs[5].iov_base = &rcv_buf[4*(BUF_SZ/4)];
+ riovs[5].iov_len = BUF_SZ - (4*(BUF_SZ/4));
+ rcv_trailer = 1;
+ }
+#endif /* NEED_TRAILER */
+
+ /* we use a copy of riovs since we'll be modifying base and len during
+ receiving. This gives us an easy way to reset the iovs for next recvmsg */
+ memcpy(riovs_tmp, riovs, sizeof(riovs));
+
+ memset(&smsg, 0, sizeof(smsg));
+ smsg.msg_iov = siovs;
+ smsg.msg_iovlen = 8;
+
+ memset(&rmsg, 0, sizeof(rmsg));
+ rmsg.msg_iov = riovs_tmp;
+ rmsg.msg_iovlen = (rcv_trailer ? 5 : 4);
+
+ bytes_written = 0;
+ bytes_read = 0;
+ rcv_off = 0;
+
+ while (bytes_written < TOTAL_DATA_SZ && (bytes_read < TOTAL_DATA_SZ)) {
+ /* send data */
+ if (bytes_written < TOTAL_DATA_SZ) {
+ ret = lwip_sendmsg(s1, &smsg, 0);
+ /* note: since we always receive after sending, there will be open
+ space in the send buffer */
+ fail_unless(ret > 0);
+
+ bytes_written += ret;
+ if (bytes_written < TOTAL_DATA_SZ) {
+ test_sockets_msgapi_update_iovs(&smsg, (size_t)ret);
+ }
+ }
+
+ while (tcpip_thread_poll_one());
+
+ /* receive and verify data */
+ do {
+ if (bytes_read < TOTAL_DATA_SZ) {
+ ret = lwip_recvmsg(s2, &rmsg, 0);
+ fail_unless(ret > 0 || (ret == -1 && errno == EWOULDBLOCK));
+
+ if (ret > 0) {
+ rcv_off += ret;
+ /* we have received a full buffer */
+ if (rcv_off == BUF_SZ) {
+ /* note: since iovs are just pointers, compare underlying buf */
+ fail_unless(!memcmp(snd_buf, rcv_buf, BUF_SZ));
+ bytes_read += BUF_SZ;
+ /* reset receive state for next buffer */
+ rcv_off = 0;
+ memset(rcv_buf, 0, BUF_SZ);
+ memcpy(riovs_tmp, riovs, sizeof(riovs));
+ rmsg.msg_iov = riovs_tmp;
+ rmsg.msg_iovlen = (rcv_trailer ? 5 : 4);
+ } else { /* partial read */
+ test_sockets_msgapi_update_iovs(&rmsg, (size_t)ret);
+ }
+ }
+ } else {
+ break;
+ }
+ } while(ret > 0);
+ }
+
+ ret = lwip_close(s1);
+ fail_unless(ret == 0);
+ ret = lwip_close(s2);
+ fail_unless(ret == 0);
+ mem_free(snd_buf);
+ mem_free(rcv_buf);
+}
+
+static void test_sockets_msgapi_udp_send_recv_loop(int s, struct msghdr *smsg, struct msghdr *rmsg)
+{
+ int i, ret;
+
+ /* send/receive our datagram of IO vectors 10 times */
+ for (i = 0; i < 10; i++) {
+ ret = lwip_sendmsg(s, smsg, 0);
+ fail_unless(ret == 4);
+
+ while (tcpip_thread_poll_one());
+
+ /* receive the datagram split across 4 buffers */
+ ret = lwip_recvmsg(s, rmsg, 0);
+ fail_unless(ret == 4);
+
+ /* verify data */
+ fail_unless(*((u8_t*)rmsg->msg_iov[0].iov_base) == 0xDE);
+ fail_unless(*((u8_t*)rmsg->msg_iov[1].iov_base) == 0xAD);
+ fail_unless(*((u8_t*)rmsg->msg_iov[2].iov_base) == 0xBE);
+ fail_unless(*((u8_t*)rmsg->msg_iov[3].iov_base) == 0xEF);
+
+ /* clear rcv_buf to ensure no data is being skipped */
+ *((u8_t*)rmsg->msg_iov[0].iov_base) = 0x00;
+ *((u8_t*)rmsg->msg_iov[1].iov_base) = 0x00;
+ *((u8_t*)rmsg->msg_iov[2].iov_base) = 0x00;
+ *((u8_t*)rmsg->msg_iov[3].iov_base) = 0x00;
+ }
+}
+
+static void test_sockets_msgapi_udp(int domain)
+{
+ int s, i, ret;
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_size;
+ struct iovec riovs[4];
+ struct msghdr rmsg;
+ u8_t rcv_buf[4];
+ struct iovec siovs[4];
+ struct msghdr smsg;
+ u8_t snd_buf[4] = {0xDE, 0xAD, 0xBE, 0xEF};
+
+ /* initialize IO vectors with data */
+ for (i = 0; i < 4; i++) {
+ siovs[i].iov_base = &snd_buf[i];
+ siovs[i].iov_len = sizeof(u8_t);
+ riovs[i].iov_base = &rcv_buf[i];
+ riovs[i].iov_len = sizeof(u8_t);
+ }
+
+ test_sockets_init_loopback_addr(domain, &addr_storage, &addr_size);
+
+ s = test_sockets_alloc_socket_nonblocking(domain, SOCK_DGRAM);
+ fail_unless(s >= 0);
+
+ ret = lwip_bind(s, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == 0);
+
+ /* Update addr with epehermal port */
+ ret = lwip_getsockname(s, (struct sockaddr*)&addr_storage, &addr_size);
+ fail_unless(ret == 0);
+ switch(domain) {
+#if LWIP_IPV6
+ case AF_INET6:
+ fail_unless(addr_size == sizeof(struct sockaddr_in6));
+ break;
+#endif /* LWIP_IPV6 */
+#if LWIP_IPV4
+ case AF_INET:
+ fail_unless(addr_size == sizeof(struct sockaddr_in));
+ break;
+#endif /* LWIP_IPV6 */
+ default:
+ fail();
+ break;
+ }
+
+ /* send and receive the datagram in 4 pieces */
+ memset(&smsg, 0, sizeof(smsg));
+ smsg.msg_iov = siovs;
+ smsg.msg_iovlen = 4;
+ memset(&rmsg, 0, sizeof(rmsg));
+ rmsg.msg_iov = riovs;
+ rmsg.msg_iovlen = 4;
+
+ /* perform a sendmsg with remote host (self) */
+ smsg.msg_name = &addr_storage;
+ smsg.msg_namelen = addr_size;
+
+ test_sockets_msgapi_udp_send_recv_loop(s, &smsg, &rmsg);
+
+ /* Connect to self, allowing us to not pass message name */
+ ret = lwip_connect(s, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == 0);
+
+ smsg.msg_name = NULL;
+ smsg.msg_namelen = 0;
+
+ test_sockets_msgapi_udp_send_recv_loop(s, &smsg, &rmsg);
+
+ ret = lwip_close(s);
+ fail_unless(ret == 0);
+}
+
+#if LWIP_IPV4
+static void test_sockets_msgapi_cmsg(int domain)
+{
+ int s, ret, enable;
+ struct sockaddr_storage addr_storage;
+ socklen_t addr_size;
+ struct iovec iov;
+ struct msghdr msg;
+ struct cmsghdr *cmsg;
+ struct in_pktinfo *pktinfo;
+ u8_t rcv_buf[4];
+ u8_t snd_buf[4] = {0xDE, 0xAD, 0xBE, 0xEF};
+ u8_t cmsg_buf[CMSG_SPACE(sizeof(struct in_pktinfo))];
+
+ test_sockets_init_loopback_addr(domain, &addr_storage, &addr_size);
+
+ s = test_sockets_alloc_socket_nonblocking(domain, SOCK_DGRAM);
+ fail_unless(s >= 0);
+
+ ret = lwip_bind(s, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == 0);
+
+ /* Update addr with epehermal port */
+ ret = lwip_getsockname(s, (struct sockaddr*)&addr_storage, &addr_size);
+ fail_unless(ret == 0);
+
+ enable = 1;
+ ret = lwip_setsockopt(s, IPPROTO_IP, IP_PKTINFO, &enable, sizeof(enable));
+ fail_unless(ret == 0);
+
+ /* Receive full message, including control message */
+ iov.iov_base = rcv_buf;
+ iov.iov_len = sizeof(rcv_buf);
+ msg.msg_control = cmsg_buf;
+ msg.msg_controllen = sizeof(cmsg_buf);
+ msg.msg_flags = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+
+ memset(rcv_buf, 0, sizeof(rcv_buf));
+ ret = lwip_sendto(s, snd_buf, sizeof(snd_buf), 0, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == sizeof(snd_buf));
+
+ tcpip_thread_poll_one();
+
+ ret = lwip_recvmsg(s, &msg, 0);
+ fail_unless(ret == sizeof(rcv_buf));
+ fail_unless(!memcmp(rcv_buf, snd_buf, sizeof(rcv_buf)));
+
+ /* Verify message header */
+ cmsg = CMSG_FIRSTHDR(&msg);
+ fail_unless(cmsg != NULL);
+ fail_unless(cmsg->cmsg_len > 0);
+ fail_unless(cmsg->cmsg_level == IPPROTO_IP);
+ fail_unless(cmsg->cmsg_type == IP_PKTINFO);
+
+ /* Verify message data */
+ pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
+ /* We only have loopback interface enabled */
+ fail_unless(pktinfo->ipi_ifindex == 1);
+ fail_unless(pktinfo->ipi_addr.s_addr == PP_HTONL(INADDR_LOOPBACK));
+
+ /* Verify there are no additional messages */
+ cmsg = CMSG_NXTHDR(&msg, cmsg);
+ fail_unless(cmsg == NULL);
+
+ /* Send datagram again, testing truncation */
+ memset(rcv_buf, 0, sizeof(rcv_buf));
+ ret = lwip_sendto(s, snd_buf, sizeof(snd_buf), 0, (struct sockaddr*)&addr_storage, addr_size);
+ fail_unless(ret == sizeof(snd_buf));
+
+ tcpip_thread_poll_one();
+
+ msg.msg_controllen = 1;
+ msg.msg_flags = 0;
+ ret = lwip_recvmsg(s, &msg, 0);
+ fail_unless(ret == sizeof(rcv_buf));
+ fail_unless(!memcmp(rcv_buf, snd_buf, sizeof(rcv_buf)));
+ /* Ensure truncation was returned */
+ fail_unless(msg.msg_flags & MSG_CTRUNC);
+ /* Ensure no control messages were returned */
+ fail_unless(msg.msg_controllen == 0);
+
+ ret = lwip_close(s);
+ fail_unless(ret == 0);
+}
+#endif /* LWIP_IPV4 */
+
+START_TEST(test_sockets_msgapis)
+{
+ LWIP_UNUSED_ARG(_i);
+#if LWIP_IPV4
+ test_sockets_msgapi_udp(AF_INET);
+ test_sockets_msgapi_tcp(AF_INET);
+ test_sockets_msgapi_cmsg(AF_INET);
+#endif
+#if LWIP_IPV6
+ test_sockets_msgapi_udp(AF_INET6);
+ test_sockets_msgapi_tcp(AF_INET6);
+#endif
+}
+END_TEST
+
+START_TEST(test_sockets_select)
+{
+#if LWIP_SOCKET_SELECT
+ int s;
+ int ret;
+ fd_set readset;
+ fd_set writeset;
+ fd_set errset;
+ struct timeval tv;
+
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ s = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(s >= 0);
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ FD_ZERO(&readset);
+ FD_SET(s, &readset);
+ FD_ZERO(&writeset);
+ FD_SET(s, &writeset);
+ FD_ZERO(&errset);
+ FD_SET(s, &errset);
+
+ tv.tv_sec = tv.tv_usec = 0;
+ ret = lwip_select(s + 1, &readset, &writeset, &errset, &tv);
+ fail_unless(ret == 0);
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ ret = lwip_close(s);
+ fail_unless(ret == 0);
+
+#endif
+ LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+START_TEST(test_sockets_recv_after_rst)
+{
+ int sl, sact;
+ int spass = -1;
+ int ret;
+ struct sockaddr_in sa_listen;
+ const u16_t port = 1234;
+ int arg;
+ const char txbuf[] = "something";
+ char rxbuf[16];
+ struct lwip_sock *sact_sock;
+ int err;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ memset(&sa_listen, 0, sizeof(sa_listen));
+ sa_listen.sin_family = AF_INET;
+ sa_listen.sin_port = PP_HTONS(port);
+ sa_listen.sin_addr.s_addr = PP_HTONL(INADDR_LOOPBACK);
+
+ /* set up the listener */
+ sl = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(sl >= 0);
+ fail_unless(test_sockets_get_used_count() == 0);
+
+ ret = lwip_bind(sl, (struct sockaddr *)&sa_listen, sizeof(sa_listen));
+ fail_unless(ret == 0);
+ ret = lwip_listen(sl, 0);
+ fail_unless(ret == 0);
+
+ /* set up the client */
+ sact = lwip_socket(AF_INET, SOCK_STREAM, 0);
+ fail_unless(sact >= 0);
+ fail_unless(test_sockets_get_used_count() == 0);
+ /* set the client to nonblocking to simplify this test */
+ arg = 1;
+ ret = lwip_ioctl(sact, FIONBIO, &arg);
+ fail_unless(ret == 0);
+ /* connect */
+ do {
+ ret = lwip_connect(sact, (struct sockaddr *)&sa_listen, sizeof(sa_listen));
+ err = errno;
+ fail_unless((ret == 0) || (ret == -1));
+ if (ret != 0) {
+ if (err == EISCONN) {
+ /* Although this is not valid, use EISCONN as an indicator for successful connection.
+ This marks us as "connect phase is done". On error, we would either have a different
+ errno code or "send" fails later... -> good enough for this test. */
+ ret = 0;
+ } else {
+ fail_unless(err == EINPROGRESS);
+ if (err != EINPROGRESS) {
+ goto cleanup;
+ }
+ /* we're in progress: little side check: test for EALREADY */
+ ret = lwip_connect(sact, (struct sockaddr *)&sa_listen, sizeof(sa_listen));
+ err = errno;
+ fail_unless(ret == -1);
+ fail_unless(err == EALREADY);
+ if ((ret != -1) || (err != EALREADY)) {
+ goto cleanup;
+ }
+ }
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+ }
+ } while (ret != 0);
+ fail_unless(ret == 0);
+
+ /* accept the server connection part */
+ spass = lwip_accept(sl, NULL, NULL);
+ fail_unless(spass >= 0);
+
+ /* write data from client */
+ ret = lwip_send(sact, txbuf, sizeof(txbuf), 0);
+ fail_unless(ret == sizeof(txbuf));
+
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* issue RST (This is a HACK, don't try this in your own app!) */
+ sact_sock = lwip_socket_dbg_get_socket(sact);
+ fail_unless(sact_sock != NULL);
+ if (sact_sock != NULL) {
+ struct netconn *sact_conn = sact_sock->conn;
+ fail_unless(sact_conn != NULL);
+ if (sact_conn != NULL) {
+ struct tcp_pcb *pcb = sact_conn->pcb.tcp;
+ fail_unless(pcb != NULL);
+ if (pcb != NULL) {
+ tcp_rst(pcb, pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
+ pcb->local_port, pcb->remote_port);
+ }
+ }
+ }
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* expect to receive data first */
+ ret = lwip_recv(spass, rxbuf, sizeof(rxbuf), 0);
+ fail_unless(ret > 0);
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* expect to receive RST indication */
+ ret = lwip_recv(spass, rxbuf, sizeof(rxbuf), 0);
+ fail_unless(ret == -1);
+ err = errno;
+ fail_unless(err == ECONNRESET);
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* expect to receive ENOTCONN indication */
+ ret = lwip_recv(spass, rxbuf, sizeof(rxbuf), 0);
+ fail_unless(ret == -1);
+ err = errno;
+ fail_unless(err == ENOTCONN);
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+ /* expect to receive ENOTCONN indication */
+ ret = lwip_recv(spass, rxbuf, sizeof(rxbuf), 0);
+ fail_unless(ret == -1);
+ err = errno;
+ fail_unless(err == ENOTCONN);
+ tcpip_thread_poll_one();
+ tcpip_thread_poll_one();
+
+cleanup:
+ ret = lwip_close(sl);
+ fail_unless(ret == 0);
+ ret = lwip_close(sact);
+ fail_unless(ret == 0);
+ if (spass >= 0) {
+ ret = lwip_close(spass);
+ fail_unless(ret == 0);
+ }
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+sockets_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_sockets_basics),
+ TESTFUNC(test_sockets_allfunctions_basic),
+ TESTFUNC(test_sockets_msgapis),
+ TESTFUNC(test_sockets_select),
+ TESTFUNC(test_sockets_recv_after_rst),
+ };
+ return create_suite("SOCKETS", tests, sizeof(tests)/sizeof(testfunc), sockets_setup, sockets_teardown);
+}
+
+#else /* LWIP_SOCKET */
+
+Suite *
+sockets_suite(void)
+{
+ return create_suite("SOCKETS", NULL, 0, NULL, NULL);
+}
+#endif /* LWIP_SOCKET */
diff --git a/test/unit/api/test_sockets.h b/test/unit/api/test_sockets.h
new file mode 100644
index 00000000000..0abb7a3057a
--- /dev/null
+++ b/test/unit/api/test_sockets.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_SOCKETS_H
+#define LWIP_HDR_TEST_SOCKETS_H
+
+#include "../lwip_check.h"
+
+Suite *sockets_suite(void);
+
+#endif
diff --git a/test/unit/arch/sys_arch.c b/test/unit/arch/sys_arch.c
new file mode 100644
index 00000000000..ed0e34f08c8
--- /dev/null
+++ b/test/unit/arch/sys_arch.c
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 2017 Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt
+ *
+ */
+
+
+#include <lwip/opt.h>
+#include <lwip/arch.h>
+#if !NO_SYS
+#include "sys_arch.h"
+#endif
+#include <lwip/stats.h>
+#include <lwip/debug.h>
+#include <lwip/sys.h>
+
+#include <string.h>
+
+u32_t lwip_sys_now;
+
+u32_t
+sys_jiffies(void)
+{
+ return lwip_sys_now;
+}
+
+u32_t
+sys_now(void)
+{
+ return lwip_sys_now;
+}
+
+void
+sys_init(void)
+{
+}
+
+#if !NO_SYS
+
+test_sys_arch_waiting_fn the_waiting_fn;
+
+void
+test_sys_arch_wait_callback(test_sys_arch_waiting_fn waiting_fn)
+{
+ the_waiting_fn = waiting_fn;
+}
+
+err_t
+sys_sem_new(sys_sem_t *sem, u8_t count)
+{
+ LWIP_ASSERT("sem != NULL", sem != NULL);
+ *sem = count + 1;
+ return ERR_OK;
+}
+
+void
+sys_sem_free(sys_sem_t *sem)
+{
+ LWIP_ASSERT("sem != NULL", sem != NULL);
+ *sem = 0;
+}
+
+void
+sys_sem_set_invalid(sys_sem_t *sem)
+{
+ LWIP_ASSERT("sem != NULL", sem != NULL);
+ *sem = 0;
+}
+
+/* semaphores are 1-based because RAM is initialized as 0, which would be valid */
+u32_t
+sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
+{
+ u32_t ret = 0;
+ LWIP_ASSERT("sem != NULL", sem != NULL);
+ LWIP_ASSERT("*sem > 0", *sem > 0);
+ if (*sem == 1) {
+ /* need to wait */
+ if(!timeout)
+ {
+ /* wait infinite */
+ LWIP_ASSERT("cannot wait without waiting callback", the_waiting_fn != NULL);
+ do {
+ int expectSomething = the_waiting_fn(sem, NULL);
+ LWIP_ASSERT("*sem > 0", *sem > 0);
+ LWIP_ASSERT("expecting a semaphore count but it's 0", !expectSomething || (*sem > 1));
+ ret++;
+ if (ret == SYS_ARCH_TIMEOUT) {
+ ret--;
+ }
+ } while(*sem == 1);
+ }
+ else
+ {
+ if (the_waiting_fn) {
+ int expectSomething = the_waiting_fn(sem, NULL);
+ LWIP_ASSERT("expecting a semaphore count but it's 0", !expectSomething || (*sem > 1));
+ }
+ LWIP_ASSERT("*sem > 0", *sem > 0);
+ if (*sem == 1) {
+ return SYS_ARCH_TIMEOUT;
+ }
+ ret = 1;
+ }
+ }
+ LWIP_ASSERT("*sem > 0", *sem > 0);
+ (*sem)--;
+ LWIP_ASSERT("*sem > 0", *sem > 0);
+ /* return the time we waited for the sem */
+ return ret;
+}
+
+void
+sys_sem_signal(sys_sem_t *sem)
+{
+ LWIP_ASSERT("sem != NULL", sem != NULL);
+ LWIP_ASSERT("*sem > 0", *sem > 0);
+ (*sem)++;
+ LWIP_ASSERT("*sem > 0", *sem > 0);
+}
+
+err_t
+sys_mutex_new(sys_mutex_t *mutex)
+{
+ LWIP_ASSERT("mutex != NULL", mutex != NULL);
+ *mutex = 1; /* 1 allocated */
+ return ERR_OK;
+}
+
+void
+sys_mutex_free(sys_mutex_t *mutex)
+{
+ /* parameter check */
+ LWIP_ASSERT("mutex != NULL", mutex != NULL);
+ LWIP_ASSERT("*mutex >= 1", *mutex >= 1);
+ *mutex = 0;
+}
+
+void
+sys_mutex_set_invalid(sys_mutex_t *mutex)
+{
+ LWIP_ASSERT("mutex != NULL", mutex != NULL);
+ *mutex = 0;
+}
+
+void
+sys_mutex_lock(sys_mutex_t *mutex)
+{
+ /* nothing to do, no multithreading supported */
+ LWIP_ASSERT("mutex != NULL", mutex != NULL);
+ /* check that the mutext is valid and unlocked (no nested locking) */
+ LWIP_ASSERT("*mutex >= 1", *mutex == 1);
+ /* we count up just to check the correct pairing of lock/unlock */
+ (*mutex)++;
+ LWIP_ASSERT("*mutex >= 1", *mutex >= 1);
+}
+
+void
+sys_mutex_unlock(sys_mutex_t *mutex)
+{
+ /* nothing to do, no multithreading supported */
+ LWIP_ASSERT("mutex != NULL", mutex != NULL);
+ LWIP_ASSERT("*mutex >= 1", *mutex >= 1);
+ /* we count down just to check the correct pairing of lock/unlock */
+ (*mutex)--;
+ LWIP_ASSERT("*mutex >= 1", *mutex >= 1);
+}
+
+
+sys_thread_t
+sys_thread_new(const char *name, lwip_thread_fn function, void *arg, int stacksize, int prio)
+{
+ LWIP_UNUSED_ARG(name);
+ LWIP_UNUSED_ARG(function);
+ LWIP_UNUSED_ARG(arg);
+ LWIP_UNUSED_ARG(stacksize);
+ LWIP_UNUSED_ARG(prio);
+ /* threads not supported */
+ return 0;
+}
+
+err_t
+sys_mbox_new(sys_mbox_t *mbox, int size)
+{
+ int mboxsize = size;
+ LWIP_ASSERT("mbox != NULL", mbox != NULL);
+ LWIP_ASSERT("size >= 0", size >= 0);
+ if (size == 0) {
+ mboxsize = 1024;
+ }
+ mbox->head = mbox->tail = 0;
+ mbox->sem = mbox; /* just point to something for sys_mbox_valid() */
+ mbox->q_mem = (void**)malloc(sizeof(void*)*mboxsize);
+ mbox->size = mboxsize;
+ mbox->used = 0;
+
+ memset(mbox->q_mem, 0, sizeof(void*)*mboxsize);
+ return ERR_OK;
+}
+
+void
+sys_mbox_free(sys_mbox_t *mbox)
+{
+ /* parameter check */
+ LWIP_ASSERT("mbox != NULL", mbox != NULL);
+ LWIP_ASSERT("mbox->sem != NULL", mbox->sem != NULL);
+ LWIP_ASSERT("mbox->sem == mbox", mbox->sem == mbox);
+ LWIP_ASSERT("mbox->q_mem != NULL", mbox->q_mem != NULL);
+ mbox->sem = NULL;
+ free(mbox->q_mem);
+ mbox->q_mem = NULL;
+}
+
+void
+sys_mbox_set_invalid(sys_mbox_t *mbox)
+{
+ LWIP_ASSERT("mbox != NULL", mbox != NULL);
+ LWIP_ASSERT("mbox->q_mem == NULL", mbox->q_mem == NULL);
+ mbox->sem = NULL;
+ mbox->q_mem = NULL;
+}
+
+void
+sys_mbox_post(sys_mbox_t *q, void *msg)
+{
+ LWIP_ASSERT("q != SYS_MBOX_NULL", q != SYS_MBOX_NULL);
+ LWIP_ASSERT("q->sem == q", q->sem == q);
+ LWIP_ASSERT("q->q_mem != NULL", q->q_mem != NULL);
+ LWIP_ASSERT("q->used >= 0", q->used >= 0);
+ LWIP_ASSERT("q->size > 0", q->size > 0);
+
+ LWIP_ASSERT("mbox already full", q->used < q->size);
+
+ q->q_mem[q->head] = msg;
+ q->head++;
+ if (q->head >= (unsigned int)q->size) {
+ q->head = 0;
+ }
+ LWIP_ASSERT("mbox is full!", q->head != q->tail);
+ q->used++;
+}
+
+err_t
+sys_mbox_trypost(sys_mbox_t *q, void *msg)
+{
+ LWIP_ASSERT("q != SYS_MBOX_NULL", q != SYS_MBOX_NULL);
+ LWIP_ASSERT("q->sem == q", q->sem == q);
+ LWIP_ASSERT("q->q_mem != NULL", q->q_mem != NULL);
+ LWIP_ASSERT("q->used >= 0", q->used >= 0);
+ LWIP_ASSERT("q->size > 0", q->size > 0);
+ LWIP_ASSERT("q->used <= q->size", q->used <= q->size);
+
+ if (q->used == q->size) {
+ return ERR_MEM;
+ }
+ sys_mbox_post(q, msg);
+ return ERR_OK;
+}
+
+err_t
+sys_mbox_trypost_fromisr(sys_mbox_t *q, void *msg)
+{
+ return sys_mbox_trypost(q, msg);
+}
+
+u32_t
+sys_arch_mbox_fetch(sys_mbox_t *q, void **msg, u32_t timeout)
+{
+ u32_t ret = 0;
+ u32_t ret2;
+ LWIP_ASSERT("q != SYS_MBOX_NULL", q != SYS_MBOX_NULL);
+ LWIP_ASSERT("q->sem == q", q->sem == q);
+ LWIP_ASSERT("q->q_mem != NULL", q->q_mem != NULL);
+ LWIP_ASSERT("q->used >= 0", q->used >= 0);
+ LWIP_ASSERT("q->size > 0", q->size > 0);
+
+ if (q->used == 0) {
+ /* need to wait */
+ /* need to wait */
+ if(!timeout)
+ {
+ /* wait infinite */
+ LWIP_ASSERT("cannot wait without waiting callback", the_waiting_fn != NULL);
+ do {
+ int expectSomething = the_waiting_fn(NULL, q);
+ LWIP_ASSERT("q->used >= 0", q->used >= 0);
+ LWIP_ASSERT("expecting item available but it's 0", !expectSomething || (q->used > 0));
+ ret++;
+ if (ret == SYS_ARCH_TIMEOUT) {
+ ret--;
+ }
+ } while(q->used == 0);
+ }
+ else
+ {
+ if (the_waiting_fn) {
+ int expectSomething = the_waiting_fn(NULL, q);
+ LWIP_ASSERT("expecting item available count but it's 0", !expectSomething || (q->used > 0));
+ }
+ LWIP_ASSERT("q->used >= 0", q->used >= 0);
+ if (q->used == 0) {
+ if(msg) {
+ *msg = NULL;
+ }
+ return SYS_ARCH_TIMEOUT;
+ }
+ ret = 1;
+ }
+ }
+ LWIP_ASSERT("q->used > 0", q->used > 0);
+ ret2 = sys_arch_mbox_tryfetch(q, msg);
+ LWIP_ASSERT("got no message", ret2 == 0);
+ return ret;
+}
+
+u32_t
+sys_arch_mbox_tryfetch(sys_mbox_t *q, void **msg)
+{
+ LWIP_ASSERT("q != SYS_MBOX_NULL", q != SYS_MBOX_NULL);
+ LWIP_ASSERT("q->sem == q", q->sem == q);
+ LWIP_ASSERT("q->q_mem != NULL", q->q_mem != NULL);
+ LWIP_ASSERT("q->used >= 0", q->used >= 0);
+ LWIP_ASSERT("q->size > 0", q->size > 0);
+
+ if (!q->used) {
+ return SYS_ARCH_TIMEOUT;
+ }
+ if(msg) {
+ *msg = q->q_mem[q->tail];
+ }
+
+ q->tail++;
+ if (q->tail >= (unsigned int)q->size) {
+ q->tail = 0;
+ }
+ q->used--;
+ LWIP_ASSERT("q->used >= 0", q->used >= 0);
+ return 0;
+}
+
+#if LWIP_NETCONN_SEM_PER_THREAD
+/* Simple implementation of this: unit tests only support one thread */
+static sys_sem_t global_netconn_sem;
+
+sys_sem_t* sys_arch_netconn_sem_get(void)
+{
+ return &global_netconn_sem;
+}
+
+void sys_arch_netconn_sem_alloc(void)
+{
+ sys_sem_new(&global_netconn_sem, 0);
+}
+
+void sys_arch_netconn_sem_free(void)
+{
+ sys_sem_free(&global_netconn_sem);
+}
+#endif /* LWIP_NETCONN_SEM_PER_THREAD */
+
+#endif /* !NO_SYS */
diff --git a/test/unit/arch/sys_arch.h b/test/unit/arch/sys_arch.h
new file mode 100644
index 00000000000..9157b6a8597
--- /dev/null
+++ b/test/unit/arch/sys_arch.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2017 Simon Goldschmidt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt
+ *
+ */
+#ifndef LWIP_HDR_TEST_SYS_ARCH_H
+#define LWIP_HDR_TEST_SYS_ARCH_H
+
+typedef int sys_sem_t;
+#define sys_sem_valid(sema) ((sema) != NULL)
+
+typedef int sys_mutex_t;
+#define sys_mutex_valid(mutex) (((mutex) != NULL)
+
+struct lwip_mbox {
+ void* sem;
+ void** q_mem;
+ unsigned int head, tail;
+ int size;
+ int used;
+};
+typedef struct lwip_mbox sys_mbox_t;
+#define SYS_MBOX_NULL NULL
+#define sys_mbox_valid(mbox) ((mbox != NULL) && ((mbox)->sem != NULL) && ((mbox)->sem != (void*)-1))
+#define sys_mbox_valid_val(mbox) (((mbox).sem != NULL) && ((mbox).sem != (void*)-1))
+
+/* DWORD (thread id) is used for sys_thread_t but we won't include windows.h */
+typedef u32_t sys_thread_t;
+
+#define SYS_ARCH_DECL_PROTECT(lev)
+#define SYS_ARCH_PROTECT(lev)
+#define SYS_ARCH_UNPROTECT(lev)
+
+/* to implement doing something while blocking on an mbox or semaphore:
+ * pass a function to test_sys_arch_wait_callback() that returns
+ * '0' if waiting again and
+ * '1' if now there should be something to do (used for asserting)
+ */
+typedef int (*test_sys_arch_waiting_fn)(sys_sem_t* wait_sem, sys_mbox_t* wait_mbox);
+void test_sys_arch_wait_callback(test_sys_arch_waiting_fn waiting_fn);
+
+/* current time */
+extern u32_t lwip_sys_now;
+
+sys_sem_t* sys_arch_netconn_sem_get(void);
+void sys_arch_netconn_sem_alloc(void);
+void sys_arch_netconn_sem_free(void);
+#define LWIP_NETCONN_THREAD_SEM_GET() sys_arch_netconn_sem_get()
+#define LWIP_NETCONN_THREAD_SEM_ALLOC() sys_arch_netconn_sem_alloc()
+#define LWIP_NETCONN_THREAD_SEM_FREE() sys_arch_netconn_sem_free()
+
+#endif /* LWIP_HDR_TEST_SYS_ARCH_H */
+
diff --git a/test/unit/core/test_def.c b/test/unit/core/test_def.c
new file mode 100644
index 00000000000..0ae2e9c1e49
--- /dev/null
+++ b/test/unit/core/test_def.c
@@ -0,0 +1,84 @@
+#include "test_def.h"
+
+#include "lwip/def.h"
+
+#define MAGIC_UNTOUCHED_BYTE 0x7a
+#define TEST_BUFSIZE 32
+#define GUARD_SIZE 4
+
+/* Setups/teardown functions */
+
+static void
+def_setup(void)
+{
+}
+
+static void
+def_teardown(void)
+{
+}
+
+static void
+def_check_range_untouched(const char *buf, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ fail_unless(buf[i] == (char)MAGIC_UNTOUCHED_BYTE);
+ }
+}
+
+static void test_def_itoa(int number, const char *expected)
+{
+ char buf[TEST_BUFSIZE];
+ char *test_buf = &buf[GUARD_SIZE];
+
+ size_t exp_len = strlen(expected);
+ fail_unless(exp_len + 4 < (TEST_BUFSIZE - (2 * GUARD_SIZE)));
+
+ memset(buf, MAGIC_UNTOUCHED_BYTE, sizeof(buf));
+ lwip_itoa(test_buf, exp_len + 1, number);
+ def_check_range_untouched(buf, GUARD_SIZE);
+ fail_unless(test_buf[exp_len] == 0);
+ fail_unless(!memcmp(test_buf, expected, exp_len));
+ def_check_range_untouched(&test_buf[exp_len + 1], TEST_BUFSIZE - GUARD_SIZE - exp_len - 1);
+
+ /* check with too small buffer */
+ memset(buf, MAGIC_UNTOUCHED_BYTE, sizeof(buf));
+ lwip_itoa(test_buf, exp_len, number);
+ def_check_range_untouched(buf, GUARD_SIZE);
+ def_check_range_untouched(&test_buf[exp_len + 1], TEST_BUFSIZE - GUARD_SIZE - exp_len - 1);
+
+ /* check with too large buffer */
+ memset(buf, MAGIC_UNTOUCHED_BYTE, sizeof(buf));
+ lwip_itoa(test_buf, exp_len + 4, number);
+ def_check_range_untouched(buf, GUARD_SIZE);
+ fail_unless(test_buf[exp_len] == 0);
+ fail_unless(!memcmp(test_buf, expected, exp_len));
+ def_check_range_untouched(&test_buf[exp_len + 4], TEST_BUFSIZE - GUARD_SIZE - exp_len - 4);
+}
+
+START_TEST(test_def_lwip_itoa)
+{
+ LWIP_UNUSED_ARG(_i);
+
+ test_def_itoa(0, "0");
+ test_def_itoa(1, "1");
+ test_def_itoa(-1, "-1");
+ test_def_itoa(15, "15");
+ test_def_itoa(-15, "-15");
+ test_def_itoa(156, "156");
+ test_def_itoa(1192, "1192");
+ test_def_itoa(-156, "-156");
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+def_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_def_lwip_itoa)
+ };
+ return create_suite("DEF", tests, sizeof(tests)/sizeof(testfunc), def_setup, def_teardown);
+}
diff --git a/test/unit/core/test_def.h b/test/unit/core/test_def.h
new file mode 100644
index 00000000000..7316051217e
--- /dev/null
+++ b/test/unit/core/test_def.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_DEF_H
+#define LWIP_HDR_TEST_DEF_H
+
+#include "../lwip_check.h"
+
+Suite *def_suite(void);
+
+#endif
diff --git a/test/unit/core/test_dns.c b/test/unit/core/test_dns.c
new file mode 100644
index 00000000000..6789d244109
--- /dev/null
+++ b/test/unit/core/test_dns.c
@@ -0,0 +1,52 @@
+#include "test_dns.h"
+
+#include "lwip/dns.h"
+
+/* Setups/teardown functions */
+
+static void
+dns_setup(void)
+{
+}
+
+static void
+dns_teardown(void)
+{
+}
+
+/* Test functions */
+
+START_TEST(test_dns_set_get_server)
+{
+ int n;
+ LWIP_UNUSED_ARG(_i);
+
+ for (n = 0; n < 256; n++) {
+ u8_t i = (u8_t)n;
+ ip_addr_t server;
+ /* Should return a zeroed address for any index */
+ fail_unless(dns_getserver(i));
+ fail_unless(ip_addr_isany(dns_getserver(i)));
+
+ /* Should accept setting address for any index, and ignore if out of range */
+ IP_ADDR4(&server, 10, 0, 0, i);
+ dns_setserver(i, &server);
+ fail_unless(dns_getserver(i));
+ if (i < DNS_MAX_SERVERS) {
+ fail_unless(ip_addr_eq(dns_getserver(i), &server) == 1);
+ } else {
+ fail_unless(ip_addr_isany(dns_getserver(i)));
+ }
+ }
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+dns_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_dns_set_get_server)
+ };
+ return create_suite("DNS", tests, sizeof(tests)/sizeof(testfunc), dns_setup, dns_teardown);
+}
diff --git a/test/unit/core/test_dns.h b/test/unit/core/test_dns.h
new file mode 100644
index 00000000000..eaad0ca01b7
--- /dev/null
+++ b/test/unit/core/test_dns.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_DNS_H
+#define LWIP_HDR_TEST_DNS_H
+
+#include "../lwip_check.h"
+
+Suite *dns_suite(void);
+
+#endif
diff --git a/test/unit/core/test_mem.c b/test/unit/core/test_mem.c
new file mode 100644
index 00000000000..601bfc7d485
--- /dev/null
+++ b/test/unit/core/test_mem.c
@@ -0,0 +1,221 @@
+#include "test_mem.h"
+
+#include "lwip/mem.h"
+#include "lwip/stats.h"
+
+#if !LWIP_STATS || !MEM_STATS
+#error "This tests needs MEM-statistics enabled"
+#endif
+
+/* Setups/teardown functions */
+
+static void
+mem_setup(void)
+{
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+mem_teardown(void)
+{
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+
+/* Test functions */
+
+/** Call mem_malloc, mem_free and mem_trim and check stats */
+START_TEST(test_mem_one)
+{
+#define SIZE1 16
+#define SIZE1_2 12
+#define SIZE2 16
+ void *p1, *p2;
+ mem_size_t s1, s2;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(lwip_stats.mem.used == 0);
+
+ p1 = mem_malloc(SIZE1);
+ fail_unless(p1 != NULL);
+ fail_unless(lwip_stats.mem.used >= SIZE1);
+ s1 = lwip_stats.mem.used;
+
+ p2 = mem_malloc(SIZE2);
+ fail_unless(p2 != NULL);
+ fail_unless(lwip_stats.mem.used >= SIZE2 + s1);
+ s2 = lwip_stats.mem.used;
+
+ mem_trim(p1, SIZE1_2);
+
+ mem_free(p2);
+ fail_unless(lwip_stats.mem.used <= s2 - SIZE2);
+
+ mem_free(p1);
+ fail_unless(lwip_stats.mem.used == 0);
+}
+END_TEST
+
+static void malloc_keep_x(int x, int num, int size, int freestep)
+{
+ int i;
+ void* p[16];
+ LWIP_ASSERT("invalid size", size >= 0 && size < (mem_size_t)-1);
+ memset(p, 0, sizeof(p));
+ for(i = 0; i < num && i < 16; i++) {
+ p[i] = mem_malloc((mem_size_t)size);
+ fail_unless(p[i] != NULL);
+ }
+ for(i = 0; i < num && i < 16; i += freestep) {
+ if (i == x) {
+ continue;
+ }
+ mem_free(p[i]);
+ p[i] = NULL;
+ }
+ for(i = 0; i < num && i < 16; i++) {
+ if (i == x) {
+ continue;
+ }
+ if (p[i] != NULL) {
+ mem_free(p[i]);
+ p[i] = NULL;
+ }
+ }
+ fail_unless(p[x] != NULL);
+ mem_free(p[x]);
+}
+
+START_TEST(test_mem_random)
+{
+ const int num = 16;
+ int x;
+ int size;
+ int freestep;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(lwip_stats.mem.used == 0);
+
+ for (x = 0; x < num; x++) {
+ for (size = 1; size < 32; size++) {
+ for (freestep = 1; freestep <= 3; freestep++) {
+ fail_unless(lwip_stats.mem.used == 0);
+ malloc_keep_x(x, num, size, freestep);
+ fail_unless(lwip_stats.mem.used == 0);
+ }
+ }
+ }
+}
+END_TEST
+
+START_TEST(test_mem_invalid_free)
+{
+ u8_t *ptr, *ptr_low, *ptr_high;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(lwip_stats.mem.used == 0);
+ fail_unless(lwip_stats.mem.illegal == 0);
+
+ ptr = (u8_t *)mem_malloc(1);
+ fail_unless(ptr != NULL);
+ fail_unless(lwip_stats.mem.used != 0);
+
+ ptr_low = ptr - 0x10;
+ mem_free(ptr_low);
+ fail_unless(lwip_stats.mem.illegal == 1);
+ lwip_stats.mem.illegal = 0;
+
+ ptr_high = ptr + (MEM_SIZE * 2);
+ mem_free(ptr_high);
+ fail_unless(lwip_stats.mem.illegal == 1);
+ lwip_stats.mem.illegal = 0;
+
+ mem_free(ptr);
+ fail_unless(lwip_stats.mem.illegal == 0);
+ fail_unless(lwip_stats.mem.used == 0);
+}
+END_TEST
+
+START_TEST(test_mem_double_free)
+{
+ u8_t *ptr1b, *ptr1, *ptr2, *ptr3;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(lwip_stats.mem.used == 0);
+ fail_unless(lwip_stats.mem.illegal == 0);
+
+ ptr1 = (u8_t *)mem_malloc(1);
+ fail_unless(ptr1 != NULL);
+ fail_unless(lwip_stats.mem.used != 0);
+
+ ptr2 = (u8_t *)mem_malloc(1);
+ fail_unless(ptr2 != NULL);
+ fail_unless(lwip_stats.mem.used != 0);
+
+ ptr3 = (u8_t *)mem_malloc(1);
+ fail_unless(ptr3 != NULL);
+ fail_unless(lwip_stats.mem.used != 0);
+
+ /* free the middle mem */
+ mem_free(ptr2);
+ fail_unless(lwip_stats.mem.illegal == 0);
+
+ /* double-free of middle mem: should fail */
+ mem_free(ptr2);
+ fail_unless(lwip_stats.mem.illegal == 1);
+ lwip_stats.mem.illegal = 0;
+
+ /* free upper memory and try again */
+ mem_free(ptr3);
+ fail_unless(lwip_stats.mem.illegal == 0);
+
+ mem_free(ptr2);
+ fail_unless(lwip_stats.mem.illegal == 1);
+ lwip_stats.mem.illegal = 0;
+
+ /* free lower memory and try again */
+ mem_free(ptr1);
+ fail_unless(lwip_stats.mem.illegal == 0);
+ fail_unless(lwip_stats.mem.used == 0);
+
+ mem_free(ptr2);
+ fail_unless(lwip_stats.mem.illegal == 1);
+ fail_unless(lwip_stats.mem.used == 0);
+ lwip_stats.mem.illegal = 0;
+
+ /* reallocate lowest memory, now overlapping already freed ptr2 */
+#ifndef MIN_SIZE
+#define MIN_SIZE 12
+#endif
+ ptr1b = (u8_t *)mem_malloc(MIN_SIZE * 2);
+ fail_unless(ptr1b != NULL);
+ fail_unless(lwip_stats.mem.used != 0);
+
+ mem_free(ptr2);
+ fail_unless(lwip_stats.mem.illegal == 1);
+ lwip_stats.mem.illegal = 0;
+
+ memset(ptr1b, 1, MIN_SIZE * 2);
+
+ mem_free(ptr2);
+ fail_unless(lwip_stats.mem.illegal == 1);
+ lwip_stats.mem.illegal = 0;
+
+ mem_free(ptr1b);
+ fail_unless(lwip_stats.mem.illegal == 0);
+ fail_unless(lwip_stats.mem.used == 0);
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+mem_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_mem_one),
+ TESTFUNC(test_mem_random),
+ TESTFUNC(test_mem_invalid_free),
+ TESTFUNC(test_mem_double_free)
+ };
+ return create_suite("MEM", tests, sizeof(tests)/sizeof(testfunc), mem_setup, mem_teardown);
+}
diff --git a/test/unit/core/test_mem.h b/test/unit/core/test_mem.h
new file mode 100644
index 00000000000..325134c30a5
--- /dev/null
+++ b/test/unit/core/test_mem.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_MEM_H
+#define LWIP_HDR_TEST_MEM_H
+
+#include "../lwip_check.h"
+
+Suite *mem_suite(void);
+
+#endif
diff --git a/test/unit/core/test_netif.c b/test/unit/core/test_netif.c
new file mode 100644
index 00000000000..a51a4792e59
--- /dev/null
+++ b/test/unit/core/test_netif.c
@@ -0,0 +1,285 @@
+#include "test_netif.h"
+
+#include "lwip/netif.h"
+#include "lwip/stats.h"
+#include "lwip/etharp.h"
+#include "netif/ethernet.h"
+
+#if !LWIP_NETIF_EXT_STATUS_CALLBACK
+#error "This tests needs LWIP_NETIF_EXT_STATUS_CALLBACK enabled"
+#endif
+
+static struct netif net_test;
+
+
+/* Setups/teardown functions */
+
+static void
+netif_setup(void)
+{
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+netif_teardown(void)
+{
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+/* test helper functions */
+
+static err_t
+testif_tx_func(struct netif *netif, struct pbuf *p)
+{
+ LWIP_UNUSED_ARG(netif);
+ LWIP_UNUSED_ARG(p);
+ return ERR_OK;
+}
+
+static err_t
+testif_init(struct netif *netif)
+{
+ netif->name[0] = 'c';
+ netif->name[1] = 'h';
+ netif->output = etharp_output;
+ netif->linkoutput = testif_tx_func;
+ netif->mtu = 1500;
+ netif->hwaddr_len = 6;
+ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6;
+
+ netif->hwaddr[0] = 0x02;
+ netif->hwaddr[1] = 0x03;
+ netif->hwaddr[2] = 0x04;
+ netif->hwaddr[3] = 0x05;
+ netif->hwaddr[4] = 0x06;
+ netif->hwaddr[5] = 0x07;
+
+ return ERR_OK;
+}
+
+#define MAX_NSC_REASON_IDX 10
+static netif_nsc_reason_t expected_reasons;
+static int callback_ctr;
+
+static int dummy_active;
+
+static void
+test_netif_ext_callback_dummy(struct netif* netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t* args)
+{
+ LWIP_UNUSED_ARG(netif);
+ LWIP_UNUSED_ARG(reason);
+ LWIP_UNUSED_ARG(args);
+
+ fail_unless(dummy_active);
+}
+
+static void
+test_netif_ext_callback(struct netif* netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t* args)
+{
+ LWIP_UNUSED_ARG(args); /* @todo */
+ callback_ctr++;
+
+ fail_unless(netif == &net_test);
+
+ fail_unless(expected_reasons == reason);
+}
+
+/* Test functions */
+
+NETIF_DECLARE_EXT_CALLBACK(netif_callback_1)
+NETIF_DECLARE_EXT_CALLBACK(netif_callback_2)
+NETIF_DECLARE_EXT_CALLBACK(netif_callback_3)
+
+START_TEST(test_netif_extcallbacks)
+{
+ ip4_addr_t addr;
+ ip4_addr_t netmask;
+ ip4_addr_t gw;
+ LWIP_UNUSED_ARG(_i);
+
+ IP4_ADDR(&addr, 0, 0, 0, 0);
+ IP4_ADDR(&netmask, 0, 0, 0, 0);
+ IP4_ADDR(&gw, 0, 0, 0, 0);
+
+ netif_add_ext_callback(&netif_callback_3, test_netif_ext_callback_dummy);
+ netif_add_ext_callback(&netif_callback_2, test_netif_ext_callback);
+ netif_add_ext_callback(&netif_callback_1, test_netif_ext_callback_dummy);
+
+ dummy_active = 1;
+
+ /* positive tests: check that single events come as expected */
+
+ expected_reasons = LWIP_NSC_NETIF_ADDED;
+ callback_ctr = 0;
+ netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+ fail_unless(callback_ctr == 1);
+
+ expected_reasons = LWIP_NSC_LINK_CHANGED;
+ callback_ctr = 0;
+ netif_set_link_up(&net_test);
+ fail_unless(callback_ctr == 1);
+
+ expected_reasons = LWIP_NSC_STATUS_CHANGED;
+ callback_ctr = 0;
+ netif_set_up(&net_test);
+ fail_unless(callback_ctr == 1);
+
+ IP4_ADDR(&addr, 1, 2, 3, 4);
+ expected_reasons = LWIP_NSC_IPV4_ADDRESS_CHANGED;
+ callback_ctr = 0;
+ netif_set_ipaddr(&net_test, &addr);
+ fail_unless(callback_ctr == 1);
+
+ IP4_ADDR(&netmask, 255, 255, 255, 0);
+ expected_reasons = LWIP_NSC_IPV4_NETMASK_CHANGED;
+ callback_ctr = 0;
+ netif_set_netmask(&net_test, &netmask);
+ fail_unless(callback_ctr == 1);
+
+ IP4_ADDR(&gw, 1, 2, 3, 254);
+ expected_reasons = LWIP_NSC_IPV4_GATEWAY_CHANGED;
+ callback_ctr = 0;
+ netif_set_gw(&net_test, &gw);
+ fail_unless(callback_ctr == 1);
+
+ IP4_ADDR(&addr, 0, 0, 0, 0);
+ expected_reasons = LWIP_NSC_IPV4_ADDRESS_CHANGED;
+ callback_ctr = 0;
+ netif_set_ipaddr(&net_test, &addr);
+ fail_unless(callback_ctr == 1);
+
+ IP4_ADDR(&netmask, 0, 0, 0, 0);
+ expected_reasons = LWIP_NSC_IPV4_NETMASK_CHANGED;
+ callback_ctr = 0;
+ netif_set_netmask(&net_test, &netmask);
+ fail_unless(callback_ctr == 1);
+
+ IP4_ADDR(&gw, 0, 0, 0, 0);
+ expected_reasons = LWIP_NSC_IPV4_GATEWAY_CHANGED;
+ callback_ctr = 0;
+ netif_set_gw(&net_test, &gw);
+ fail_unless(callback_ctr == 1);
+
+ /* check for multi-events (only one combined callback expected) */
+
+ IP4_ADDR(&addr, 1, 2, 3, 4);
+ IP4_ADDR(&netmask, 255, 255, 255, 0);
+ IP4_ADDR(&gw, 1, 2, 3, 254);
+ expected_reasons = (netif_nsc_reason_t)(LWIP_NSC_IPV4_ADDRESS_CHANGED | LWIP_NSC_IPV4_NETMASK_CHANGED |
+ LWIP_NSC_IPV4_GATEWAY_CHANGED | LWIP_NSC_IPV4_SETTINGS_CHANGED |
+ LWIP_NSC_IPV4_ADDR_VALID);
+ callback_ctr = 0;
+ netif_set_addr(&net_test, &addr, &netmask, &gw);
+ fail_unless(callback_ctr == 1);
+
+ /* check that for no-change, no callback is expected */
+ expected_reasons = LWIP_NSC_NONE;
+ callback_ctr = 0;
+ netif_set_ipaddr(&net_test, &addr);
+ fail_unless(callback_ctr == 0);
+
+ netif_set_netmask(&net_test, &netmask);
+ callback_ctr = 0;
+ fail_unless(callback_ctr == 0);
+
+ callback_ctr = 0;
+ netif_set_gw(&net_test, &gw);
+ fail_unless(callback_ctr == 0);
+
+ /* netif_set_addr() always issues at least LWIP_NSC_IPV4_ADDR_VALID */
+ expected_reasons = LWIP_NSC_IPV4_ADDR_VALID;
+ callback_ctr = 0;
+ netif_set_addr(&net_test, &addr, &netmask, &gw);
+ fail_unless(callback_ctr == 1);
+
+ /* check for single-events */
+ IP4_ADDR(&addr, 1, 2, 3, 5);
+ expected_reasons = (netif_nsc_reason_t)(LWIP_NSC_IPV4_ADDRESS_CHANGED | LWIP_NSC_IPV4_SETTINGS_CHANGED |
+ LWIP_NSC_IPV4_ADDR_VALID);
+ callback_ctr = 0;
+ netif_set_addr(&net_test, &addr, &netmask, &gw);
+ fail_unless(callback_ctr == 1);
+
+ expected_reasons = LWIP_NSC_STATUS_CHANGED;
+ callback_ctr = 0;
+ netif_set_down(&net_test);
+ fail_unless(callback_ctr == 1);
+
+ expected_reasons = LWIP_NSC_NETIF_REMOVED;
+ callback_ctr = 0;
+ netif_remove(&net_test);
+ fail_unless(callback_ctr == 1);
+
+ expected_reasons = LWIP_NSC_NONE;
+
+ netif_remove_ext_callback(&netif_callback_2);
+ netif_remove_ext_callback(&netif_callback_3);
+ netif_remove_ext_callback(&netif_callback_1);
+ dummy_active = 0;
+}
+END_TEST
+
+START_TEST(test_netif_flag_set)
+{
+ ip4_addr_t addr;
+ ip4_addr_t netmask;
+ ip4_addr_t gw;
+ LWIP_UNUSED_ARG(_i);
+
+ IP4_ADDR(&addr, 0, 0, 0, 0);
+ IP4_ADDR(&netmask, 0, 0, 0, 0);
+ IP4_ADDR(&gw, 0, 0, 0, 0);
+
+ netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+
+ fail_if(netif_is_flag_set(&net_test, NETIF_FLAG_UP));
+ fail_unless(netif_is_flag_set(&net_test, NETIF_FLAG_BROADCAST));
+ fail_if(netif_is_flag_set(&net_test, NETIF_FLAG_LINK_UP));
+ fail_unless(netif_is_flag_set(&net_test, NETIF_FLAG_ETHARP));
+ fail_unless(netif_is_flag_set(&net_test, NETIF_FLAG_ETHERNET));
+ fail_unless(netif_is_flag_set(&net_test, NETIF_FLAG_IGMP));
+ fail_unless(netif_is_flag_set(&net_test, NETIF_FLAG_MLD6));
+
+ netif_remove(&net_test);
+}
+END_TEST
+
+START_TEST(test_netif_find)
+{
+ struct netif net0;
+ struct netif net1;
+ LWIP_UNUSED_ARG(_i);
+
+ /* No netifs available */
+ fail_unless(netif_find("ch0") == NULL);
+
+ /* Add netifs with known names */
+ fail_unless(netif_add_noaddr(&net0, NULL, testif_init, ethernet_input) == &net0);
+ net0.num = 0;
+ fail_unless(netif_add_noaddr(&net1, NULL, testif_init, ethernet_input) == &net1);
+ net1.num = 1;
+
+ fail_unless(netif_find("ch0") == &net0);
+ fail_unless(netif_find("CH0") == NULL);
+ fail_unless(netif_find("ch1") == &net1);
+ fail_unless(netif_find("ch3") == NULL);
+ /* atoi failure is not treated as zero */
+ fail_unless(netif_find("chX") == NULL);
+ fail_unless(netif_find("ab0") == NULL);
+
+ netif_remove(&net0);
+ netif_remove(&net1);
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+netif_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_netif_extcallbacks),
+ TESTFUNC(test_netif_flag_set),
+ TESTFUNC(test_netif_find)
+ };
+ return create_suite("NETIF", tests, sizeof(tests)/sizeof(testfunc), netif_setup, netif_teardown);
+}
diff --git a/test/unit/core/test_netif.h b/test/unit/core/test_netif.h
new file mode 100644
index 00000000000..8f2b6b46852
--- /dev/null
+++ b/test/unit/core/test_netif.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_NETIF_H
+#define LWIP_HDR_TEST_NETIF_H
+
+#include "../lwip_check.h"
+
+Suite *netif_suite(void);
+
+#endif
diff --git a/test/unit/core/test_pbuf.c b/test/unit/core/test_pbuf.c
new file mode 100644
index 00000000000..6163e4fabef
--- /dev/null
+++ b/test/unit/core/test_pbuf.c
@@ -0,0 +1,359 @@
+#include "test_pbuf.h"
+
+#include "lwip/pbuf.h"
+#include "lwip/stats.h"
+
+#if !LWIP_STATS || !MEM_STATS ||!MEMP_STATS
+#error "This tests needs MEM- and MEMP-statistics enabled"
+#endif
+#if !LWIP_TCP || !TCP_QUEUE_OOSEQ || !LWIP_WND_SCALE
+#error "This test needs TCP OOSEQ queueing and window scaling enabled"
+#endif
+
+/* Setups/teardown functions */
+
+static void
+pbuf_setup(void)
+{
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+pbuf_teardown(void)
+{
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+
+#define TESTBUFSIZE_1 65535
+#define TESTBUFSIZE_2 65530
+#define TESTBUFSIZE_3 50050
+static u8_t testbuf_1[TESTBUFSIZE_1];
+static u8_t testbuf_1a[TESTBUFSIZE_1];
+static u8_t testbuf_2[TESTBUFSIZE_2];
+static u8_t testbuf_2a[TESTBUFSIZE_2];
+static u8_t testbuf_3[TESTBUFSIZE_3];
+static u8_t testbuf_3a[TESTBUFSIZE_3];
+
+/* Test functions */
+START_TEST(test_pbuf_alloc_zero_pbufs)
+{
+ struct pbuf *p;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, 0, PBUF_ROM);
+ fail_unless(p != NULL);
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+
+ p = pbuf_alloc(PBUF_RAW, 0, PBUF_RAM);
+ fail_unless(p != NULL);
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+
+ p = pbuf_alloc(PBUF_RAW, 0, PBUF_REF);
+ fail_unless(p != NULL);
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+
+ p = pbuf_alloc(PBUF_RAW, 0, PBUF_POOL);
+ fail_unless(p != NULL);
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+}
+END_TEST
+
+/** Call pbuf_copy on a pbuf with zero length */
+START_TEST(test_pbuf_copy_zero_pbuf)
+{
+ struct pbuf *p1, *p2, *p3;
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ p1 = pbuf_alloc(PBUF_RAW, 1024, PBUF_RAM);
+ fail_unless(p1 != NULL);
+ fail_unless(p1->ref == 1);
+
+ p2 = pbuf_alloc(PBUF_RAW, 2, PBUF_POOL);
+ fail_unless(p2 != NULL);
+ fail_unless(p2->ref == 1);
+ p2->len = p2->tot_len = 0;
+
+ pbuf_cat(p1, p2);
+ fail_unless(p1->ref == 1);
+ fail_unless(p2->ref == 1);
+
+ p3 = pbuf_alloc(PBUF_RAW, p1->tot_len, PBUF_POOL);
+ err = pbuf_copy(p3, p1);
+ fail_unless(err == ERR_VAL);
+
+ pbuf_free(p1);
+ pbuf_free(p3);
+}
+END_TEST
+
+/** Call pbuf_copy on pbufs with chains of different sizes */
+START_TEST(test_pbuf_copy_unmatched_chains)
+{
+ uint16_t i, j;
+ err_t err;
+ struct pbuf *source, *dest, *p;
+ LWIP_UNUSED_ARG(_i);
+
+ source = NULL;
+ /* Build source pbuf from linked 16 byte parts,
+ * with payload bytes containing their offset */
+ for (i = 0; i < 8; i++) {
+ p = pbuf_alloc(PBUF_RAW, 16, PBUF_RAM);
+ fail_unless(p != NULL);
+ for (j = 0; j < p->len; j++) {
+ ((u8_t*)p->payload)[j] = (u8_t)((i << 4) | j);
+ }
+ if (source) {
+ pbuf_cat(source, p);
+ } else {
+ source = p;
+ }
+ }
+ for (i = 0; i < source->tot_len; i++) {
+ fail_unless(pbuf_get_at(source, i) == i);
+ }
+
+ /* Build dest pbuf from other lengths */
+ dest = pbuf_alloc(PBUF_RAW, 35, PBUF_RAM);
+ fail_unless(dest != NULL);
+ p = pbuf_alloc(PBUF_RAW, 81, PBUF_RAM);
+ fail_unless(p != NULL);
+ pbuf_cat(dest, p);
+ p = pbuf_alloc(PBUF_RAW, 27, PBUF_RAM);
+ fail_unless(p != NULL);
+ pbuf_cat(dest, p);
+
+ /* Copy contents and verify data */
+ err = pbuf_copy(dest, source);
+ fail_unless(err == ERR_OK);
+ for (i = 0; i < source->tot_len; i++) {
+ fail_unless(pbuf_get_at(dest, i) == i);
+ }
+
+ pbuf_free(source);
+ pbuf_free(dest);
+}
+END_TEST
+
+START_TEST(test_pbuf_copy_partial_pbuf)
+{
+ struct pbuf *a, *b, *dest;
+ char lwip[] = "lwip ";
+ char packet[] = "packet";
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ a = pbuf_alloc(PBUF_RAW, 5, PBUF_REF);
+ fail_unless(a != NULL);
+ a->payload = lwip;
+ b = pbuf_alloc(PBUF_RAW, 7, PBUF_REF);
+ fail_unless(b != NULL);
+ b->payload = packet;
+ pbuf_cat(a, b);
+ dest = pbuf_alloc(PBUF_RAW, 14, PBUF_RAM);
+ memset(dest->payload, 0, dest->len);
+ fail_unless(dest != NULL);
+
+ /* Don't copy if data will not fit */
+ err = pbuf_copy_partial_pbuf(dest, a, a->tot_len, 4);
+ fail_unless(err == ERR_ARG);
+ /* Don't copy if length is longer than source */
+ err = pbuf_copy_partial_pbuf(dest, a, a->tot_len + 1, 0);
+ fail_unless(err == ERR_ARG);
+ /* Normal copy */
+ err = pbuf_copy_partial_pbuf(dest, a, a->tot_len, 0);
+ fail_unless(err == ERR_OK);
+ fail_unless(strcmp("lwip packet", (char*)dest->payload) == 0);
+ /* Copy at offset */
+ err = pbuf_copy_partial_pbuf(dest, a, a->tot_len, 1);
+ fail_unless(err == ERR_OK);
+ fail_unless(strcmp("llwip packet", (char*)dest->payload) == 0);
+ /* Copy at offset with shorter length */
+ err = pbuf_copy_partial_pbuf(dest, a, 6, 6);
+ fail_unless(err == ERR_OK);
+ fail_unless(strcmp("llwip lwip p", (char*)dest->payload) == 0);
+ /* Copy with shorter length */
+ err = pbuf_copy_partial_pbuf(dest, a, 5, 0);
+ fail_unless(err == ERR_OK);
+ fail_unless(strcmp("lwip lwip p", (char*)dest->payload) == 0);
+
+ pbuf_free(dest);
+ pbuf_free(a);
+}
+END_TEST
+
+START_TEST(test_pbuf_split_64k_on_small_pbufs)
+{
+ struct pbuf *p, *rest=NULL;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, 1, PBUF_POOL);
+ pbuf_split_64k(p, &rest);
+ fail_unless(p->tot_len == 1);
+ pbuf_free(p);
+}
+END_TEST
+
+START_TEST(test_pbuf_queueing_bigger_than_64k)
+{
+ int i;
+ err_t err;
+ struct pbuf *p1, *p2, *p3, *rest2=NULL, *rest3=NULL;
+ LWIP_UNUSED_ARG(_i);
+
+ for(i = 0; i < TESTBUFSIZE_1; i++) {
+ testbuf_1[i] = (u8_t)rand();
+ }
+ for(i = 0; i < TESTBUFSIZE_2; i++) {
+ testbuf_2[i] = (u8_t)rand();
+ }
+ for(i = 0; i < TESTBUFSIZE_3; i++) {
+ testbuf_3[i] = (u8_t)rand();
+ }
+
+ p1 = pbuf_alloc(PBUF_RAW, TESTBUFSIZE_1, PBUF_POOL);
+ fail_unless(p1 != NULL);
+ p2 = pbuf_alloc(PBUF_RAW, TESTBUFSIZE_2, PBUF_POOL);
+ fail_unless(p2 != NULL);
+ p3 = pbuf_alloc(PBUF_RAW, TESTBUFSIZE_3, PBUF_POOL);
+ fail_unless(p3 != NULL);
+ err = pbuf_take(p1, testbuf_1, TESTBUFSIZE_1);
+ fail_unless(err == ERR_OK);
+ err = pbuf_take(p2, testbuf_2, TESTBUFSIZE_2);
+ fail_unless(err == ERR_OK);
+ err = pbuf_take(p3, testbuf_3, TESTBUFSIZE_3);
+ fail_unless(err == ERR_OK);
+
+ pbuf_cat(p1, p2);
+ pbuf_cat(p1, p3);
+
+ pbuf_split_64k(p1, &rest2);
+ fail_unless(p1->tot_len == TESTBUFSIZE_1);
+ fail_unless(rest2->tot_len == (u16_t)((TESTBUFSIZE_2+TESTBUFSIZE_3) & 0xFFFF));
+ pbuf_split_64k(rest2, &rest3);
+ fail_unless(rest2->tot_len == TESTBUFSIZE_2);
+ fail_unless(rest3->tot_len == TESTBUFSIZE_3);
+
+ pbuf_copy_partial(p1, testbuf_1a, TESTBUFSIZE_1, 0);
+ pbuf_copy_partial(rest2, testbuf_2a, TESTBUFSIZE_2, 0);
+ pbuf_copy_partial(rest3, testbuf_3a, TESTBUFSIZE_3, 0);
+ fail_if(memcmp(testbuf_1, testbuf_1a, TESTBUFSIZE_1));
+ fail_if(memcmp(testbuf_2, testbuf_2a, TESTBUFSIZE_2));
+ fail_if(memcmp(testbuf_3, testbuf_3a, TESTBUFSIZE_3));
+
+ pbuf_free(p1);
+ pbuf_free(rest2);
+ pbuf_free(rest3);
+}
+END_TEST
+
+/* Test for bug that writing with pbuf_take_at() did nothing
+ * and returned ERR_OK when writing at beginning of a pbuf
+ * in the chain.
+ */
+START_TEST(test_pbuf_take_at_edge)
+{
+ err_t res;
+ u8_t *out;
+ int i;
+ u8_t testdata[] = { 0x01, 0x08, 0x82, 0x02 };
+ struct pbuf *p = pbuf_alloc(PBUF_RAW, 1024, PBUF_POOL);
+ struct pbuf *q = p->next;
+ LWIP_UNUSED_ARG(_i);
+ /* alloc big enough to get a chain of pbufs */
+ fail_if(p->tot_len == p->len);
+ memset(p->payload, 0, p->len);
+ memset(q->payload, 0, q->len);
+
+ /* copy data to the beginning of first pbuf */
+ res = pbuf_take_at(p, &testdata, sizeof(testdata), 0);
+ fail_unless(res == ERR_OK);
+
+ out = (u8_t*)p->payload;
+ for (i = 0; i < (int)sizeof(testdata); i++) {
+ fail_unless(out[i] == testdata[i],
+ "Bad data at pos %d, was %02X, expected %02X", i, out[i], testdata[i]);
+ }
+
+ /* copy data to the just before end of first pbuf */
+ res = pbuf_take_at(p, &testdata, sizeof(testdata), p->len - 1);
+ fail_unless(res == ERR_OK);
+
+ out = (u8_t*)p->payload;
+ fail_unless(out[p->len - 1] == testdata[0],
+ "Bad data at pos %d, was %02X, expected %02X", p->len - 1, out[p->len - 1], testdata[0]);
+ out = (u8_t*)q->payload;
+ for (i = 1; i < (int)sizeof(testdata); i++) {
+ fail_unless(out[i-1] == testdata[i],
+ "Bad data at pos %d, was %02X, expected %02X", p->len - 1 + i, out[i-1], testdata[i]);
+ }
+
+ /* copy data to the beginning of second pbuf */
+ res = pbuf_take_at(p, &testdata, sizeof(testdata), p->len);
+ fail_unless(res == ERR_OK);
+
+ out = (u8_t*)p->payload;
+ for (i = 0; i < (int)sizeof(testdata); i++) {
+ fail_unless(out[i] == testdata[i],
+ "Bad data at pos %d, was %02X, expected %02X", p->len+i, out[i], testdata[i]);
+ }
+ pbuf_free(p);
+}
+END_TEST
+
+/* Verify pbuf_put_at()/pbuf_get_at() when using
+ * offsets equal to beginning of new pbuf in chain
+ */
+START_TEST(test_pbuf_get_put_at_edge)
+{
+ u8_t *out;
+ u8_t testdata = 0x01;
+ u8_t getdata;
+ struct pbuf *p = pbuf_alloc(PBUF_RAW, 1024, PBUF_POOL);
+ struct pbuf *q = p->next;
+ LWIP_UNUSED_ARG(_i);
+ /* alloc big enough to get a chain of pbufs */
+ fail_if(p->tot_len == p->len);
+ memset(p->payload, 0, p->len);
+ memset(q->payload, 0, q->len);
+
+ /* put byte at the beginning of second pbuf */
+ pbuf_put_at(p, p->len, testdata);
+
+ out = (u8_t*)q->payload;
+ fail_unless(*out == testdata,
+ "Bad data at pos %d, was %02X, expected %02X", p->len, *out, testdata);
+
+ getdata = pbuf_get_at(p, p->len);
+ fail_unless(*out == getdata,
+ "pbuf_get_at() returned bad data at pos %d, was %02X, expected %02X", p->len, getdata, *out);
+ pbuf_free(p);
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+pbuf_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_pbuf_alloc_zero_pbufs),
+ TESTFUNC(test_pbuf_copy_zero_pbuf),
+ TESTFUNC(test_pbuf_copy_unmatched_chains),
+ TESTFUNC(test_pbuf_copy_partial_pbuf),
+ TESTFUNC(test_pbuf_split_64k_on_small_pbufs),
+ TESTFUNC(test_pbuf_queueing_bigger_than_64k),
+ TESTFUNC(test_pbuf_take_at_edge),
+ TESTFUNC(test_pbuf_get_put_at_edge)
+ };
+ return create_suite("PBUF", tests, sizeof(tests)/sizeof(testfunc), pbuf_setup, pbuf_teardown);
+}
diff --git a/test/unit/core/test_pbuf.h b/test/unit/core/test_pbuf.h
new file mode 100644
index 00000000000..da7730a4c85
--- /dev/null
+++ b/test/unit/core/test_pbuf.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_PBUF_H
+#define LWIP_HDR_TEST_PBUF_H
+
+#include "../lwip_check.h"
+
+Suite *pbuf_suite(void);
+
+#endif
diff --git a/test/unit/core/test_timers.c b/test/unit/core/test_timers.c
new file mode 100644
index 00000000000..820b718924f
--- /dev/null
+++ b/test/unit/core/test_timers.c
@@ -0,0 +1,233 @@
+#include "test_timers.h"
+
+#include "lwip/def.h"
+#include "lwip/timeouts.h"
+#include "arch/sys_arch.h"
+
+/* Setups/teardown functions */
+
+static struct sys_timeo* old_list_head;
+
+static void
+timers_setup(void)
+{
+ struct sys_timeo** list_head = sys_timeouts_get_next_timeout();
+ old_list_head = *list_head;
+ *list_head = NULL;
+}
+
+static void
+timers_teardown(void)
+{
+ struct sys_timeo** list_head = sys_timeouts_get_next_timeout();
+ *list_head = old_list_head;
+ lwip_sys_now = 0;
+}
+
+static int fired[3];
+static void
+dummy_handler(void* arg)
+{
+ int index = LWIP_PTR_NUMERIC_CAST(int, arg);
+ fired[index] = 1;
+}
+
+#define HANDLER_EXECUTION_TIME 5
+static int cyclic_fired;
+static void
+dummy_cyclic_handler(void)
+{
+ cyclic_fired = 1;
+ lwip_sys_now += HANDLER_EXECUTION_TIME;
+}
+
+struct lwip_cyclic_timer test_cyclic = {10, dummy_cyclic_handler};
+
+static void
+do_test_cyclic_timers(u32_t offset)
+{
+ struct sys_timeo** list_head = sys_timeouts_get_next_timeout();
+
+ /* verify normal timer expiration */
+ lwip_sys_now = offset + 0;
+ sys_timeout(test_cyclic.interval_ms, lwip_cyclic_timer, &test_cyclic);
+
+ cyclic_fired = 0;
+ sys_check_timeouts();
+ fail_unless(cyclic_fired == 0);
+
+ lwip_sys_now = offset + test_cyclic.interval_ms;
+ sys_check_timeouts();
+ fail_unless(cyclic_fired == 1);
+
+ fail_unless((*list_head)->time == (u32_t)(lwip_sys_now + test_cyclic.interval_ms - HANDLER_EXECUTION_TIME));
+
+ sys_untimeout(lwip_cyclic_timer, &test_cyclic);
+
+
+ /* verify "overload" - next cyclic timer execution is already overdue twice */
+ lwip_sys_now = offset + 0;
+ sys_timeout(test_cyclic.interval_ms, lwip_cyclic_timer, &test_cyclic);
+
+ cyclic_fired = 0;
+ sys_check_timeouts();
+ fail_unless(cyclic_fired == 0);
+
+ lwip_sys_now = offset + 2*test_cyclic.interval_ms;
+ sys_check_timeouts();
+ fail_unless(cyclic_fired == 1);
+
+ fail_unless((*list_head)->time == (u32_t)(lwip_sys_now + test_cyclic.interval_ms));
+}
+
+START_TEST(test_cyclic_timers)
+{
+ LWIP_UNUSED_ARG(_i);
+
+ /* check without u32_t wraparound */
+ do_test_cyclic_timers(0);
+
+ /* check with u32_t wraparound */
+ do_test_cyclic_timers(0xfffffff0);
+}
+END_TEST
+
+/* reproduce bug #52748: the bug in timeouts.c */
+START_TEST(test_bug52748)
+{
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&fired, 0, sizeof(fired));
+
+ lwip_sys_now = 50;
+ sys_timeout(20, dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 0));
+ sys_timeout( 5, dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 2));
+
+ lwip_sys_now = 55;
+ sys_check_timeouts();
+ fail_unless(fired[0] == 0);
+ fail_unless(fired[1] == 0);
+ fail_unless(fired[2] == 1);
+
+ lwip_sys_now = 60;
+ sys_timeout(10, dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 1));
+ sys_check_timeouts();
+ fail_unless(fired[0] == 0);
+ fail_unless(fired[1] == 0);
+ fail_unless(fired[2] == 1);
+
+ lwip_sys_now = 70;
+ sys_check_timeouts();
+ fail_unless(fired[0] == 1);
+ fail_unless(fired[1] == 1);
+ fail_unless(fired[2] == 1);
+}
+END_TEST
+
+static void
+do_test_timers(u32_t offset)
+{
+ struct sys_timeo** list_head = sys_timeouts_get_next_timeout();
+
+ lwip_sys_now = offset + 0;
+
+ sys_timeout(10, dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 0));
+ fail_unless(sys_timeouts_sleeptime() == 10);
+ sys_timeout(20, dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 1));
+ fail_unless(sys_timeouts_sleeptime() == 10);
+ sys_timeout( 5, dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 2));
+ fail_unless(sys_timeouts_sleeptime() == 5);
+
+ /* linked list correctly sorted? */
+ fail_unless((*list_head)->time == (u32_t)(lwip_sys_now + 5));
+ fail_unless((*list_head)->next->time == (u32_t)(lwip_sys_now + 10));
+ fail_unless((*list_head)->next->next->time == (u32_t)(lwip_sys_now + 20));
+
+ /* check timers expire in correct order */
+ memset(&fired, 0, sizeof(fired));
+
+ lwip_sys_now += 4;
+ sys_check_timeouts();
+ fail_unless(fired[2] == 0);
+
+ lwip_sys_now += 1;
+ sys_check_timeouts();
+ fail_unless(fired[2] == 1);
+
+ lwip_sys_now += 4;
+ sys_check_timeouts();
+ fail_unless(fired[0] == 0);
+
+ lwip_sys_now += 1;
+ sys_check_timeouts();
+ fail_unless(fired[0] == 1);
+
+ lwip_sys_now += 9;
+ sys_check_timeouts();
+ fail_unless(fired[1] == 0);
+
+ lwip_sys_now += 1;
+ sys_check_timeouts();
+ fail_unless(fired[1] == 1);
+
+ sys_untimeout(dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 0));
+ sys_untimeout(dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 1));
+ sys_untimeout(dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 2));
+}
+
+START_TEST(test_timers)
+{
+ LWIP_UNUSED_ARG(_i);
+
+ /* check without u32_t wraparound */
+ do_test_timers(0);
+
+ /* check with u32_t wraparound */
+ do_test_timers(0xfffffff0);
+}
+END_TEST
+
+START_TEST(test_long_timer)
+{
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&fired, 0, sizeof(fired));
+ lwip_sys_now = 0;
+
+ sys_timeout(LWIP_UINT32_MAX / 4, dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 0));
+ fail_unless(sys_timeouts_sleeptime() == LWIP_UINT32_MAX / 4);
+
+ sys_check_timeouts();
+ fail_unless(fired[0] == 0);
+
+ lwip_sys_now += LWIP_UINT32_MAX / 8;
+
+ sys_check_timeouts();
+ fail_unless(fired[0] == 0);
+
+ lwip_sys_now += LWIP_UINT32_MAX / 8;
+
+ sys_check_timeouts();
+ fail_unless(fired[0] == 0);
+
+ lwip_sys_now += 1;
+
+ sys_check_timeouts();
+ fail_unless(fired[0] == 1);
+
+ sys_untimeout(dummy_handler, LWIP_PTR_NUMERIC_CAST(void*, 0));
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+timers_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_bug52748),
+ TESTFUNC(test_cyclic_timers),
+ TESTFUNC(test_timers),
+ TESTFUNC(test_long_timer),
+ };
+ return create_suite("TIMERS", tests, LWIP_ARRAYSIZE(tests), timers_setup, timers_teardown);
+}
diff --git a/test/unit/core/test_timers.h b/test/unit/core/test_timers.h
new file mode 100644
index 00000000000..b16ab75bf0f
--- /dev/null
+++ b/test/unit/core/test_timers.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_TIMERS_H
+#define LWIP_HDR_TEST_TIMERS_H
+
+#include "../lwip_check.h"
+
+Suite *timers_suite(void);
+
+#endif
diff --git a/test/unit/dhcp/test_dhcp.c b/test/unit/dhcp/test_dhcp.c
new file mode 100644
index 00000000000..ac8a2191cde
--- /dev/null
+++ b/test/unit/dhcp/test_dhcp.c
@@ -0,0 +1,1103 @@
+#include "test_dhcp.h"
+
+#include "lwip/netif.h"
+#include "lwip/dhcp.h"
+#include "lwip/prot/dhcp.h"
+#include "lwip/etharp.h"
+#include "lwip/inet.h"
+#include "netif/ethernet.h"
+
+#if LWIP_ACD
+#if LWIP_DHCP_DOES_ACD_CHECK
+#define DHCP_TEST_NUM_ARP_FRAMES 5
+#else
+#define DHCP_TEST_NUM_ARP_FRAMES 0
+#endif
+#else
+#define DHCP_TEST_NUM_ARP_FRAMES 1
+#endif
+
+
+static struct netif net_test;
+
+static const u8_t broadcast[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+
+static const u8_t magic_cookie[] = { 0x63, 0x82, 0x53, 0x63 };
+
+static u8_t dhcp_offer[] = {
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, /* To unit */
+ 0x00, 0x0F, 0xEE, 0x30, 0xAB, 0x22, /* From Remote host */
+ 0x08, 0x00, /* Protocol: IP */
+ 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x80, 0x11, 0x36, 0xcc, 0xc3, 0xaa, 0xbd, 0xab, 0xc3, 0xaa, 0xbd, 0xc8, /* IP header */
+ 0x00, 0x43, 0x00, 0x44, 0x01, 0x34, 0x00, 0x00, /* UDP header */
+
+ 0x02, /* Type == Boot reply */
+ 0x01, 0x06, /* Hw Ethernet, 6 bytes addrlen */
+ 0x00, /* 0 hops */
+ 0xAA, 0xAA, 0xAA, 0xAA, /* Transaction id, will be overwritten */
+ 0x00, 0x00, /* 0 seconds elapsed */
+ 0x00, 0x00, /* Flags (unicast) */
+ 0x00, 0x00, 0x00, 0x00, /* Client ip */
+ 0xc3, 0xaa, 0xbd, 0xc8, /* Your IP */
+ 0xc3, 0xaa, 0xbd, 0xab, /* DHCP server ip */
+ 0x00, 0x00, 0x00, 0x00, /* relay agent */
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* MAC addr + padding */
+
+ /* Empty server name and boot file name */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x63, 0x82, 0x53, 0x63, /* Magic cookie */
+ 0x35, 0x01, 0x02, /* Message type: Offer */
+ 0x36, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* Server identifier (IP) */
+ 0x33, 0x04, 0x00, 0x00, 0x00, 0x78, /* Lease time 2 minutes */
+ 0x03, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* Router IP */
+ 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, /* Subnet mask */
+ 0xff, /* End option */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Padding */
+};
+
+static u8_t dhcp_ack[] = {
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, /* To unit */
+ 0x00, 0x0f, 0xEE, 0x30, 0xAB, 0x22, /* From remote host */
+ 0x08, 0x00, /* Proto IP */
+ 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x80, 0x11, 0x36, 0xcc, 0xc3, 0xaa, 0xbd, 0xab, 0xc3, 0xaa, 0xbd, 0xc8, /* IP header */
+ 0x00, 0x43, 0x00, 0x44, 0x01, 0x34, 0x00, 0x00, /* UDP header */
+ 0x02, /* Bootp reply */
+ 0x01, 0x06, /* Hw type Eth, len 6 */
+ 0x00, /* 0 hops */
+ 0xAA, 0xAA, 0xAA, 0xAA,
+ 0x00, 0x00, /* 0 seconds elapsed */
+ 0x00, 0x00, /* Flags (unicast) */
+ 0x00, 0x00, 0x00, 0x00, /* Client IP */
+ 0xc3, 0xaa, 0xbd, 0xc8, /* Your IP */
+ 0xc3, 0xaa, 0xbd, 0xab, /* DHCP server IP */
+ 0x00, 0x00, 0x00, 0x00, /* Relay agent */
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Macaddr + padding */
+
+ /* Empty server name and boot file name */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+
+ 0x63, 0x82, 0x53, 0x63, /* Magic cookie */
+ 0x35, 0x01, 0x05, /* Dhcp message type ack */
+ 0x36, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* DHCP server identifier */
+ 0x33, 0x04, 0x00, 0x00, 0x00, 0x78, /* Lease time 2 minutes */
+ 0x03, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* Router IP */
+ 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, /* Netmask */
+ 0xff, /* End marker */
+
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Padding */
+};
+
+static const u8_t arpreply[] = {
+ 0x00, 0x23, 0xC1, 0xDE, 0xD0, 0x0D, /* dst mac */
+ 0x00, 0x32, 0x44, 0x20, 0x01, 0x02, /* src mac */
+ 0x08, 0x06, /* proto arp */
+ 0x00, 0x01, /* hw eth */
+ 0x08, 0x00, /* proto ip */
+ 0x06, /* hw addr len 6 */
+ 0x04, /* proto addr len 4 */
+ 0x00, 0x02, /* arp reply */
+ 0x00, 0x32, 0x44, 0x20, 0x01, 0x02, /* sender mac */
+ 0xc3, 0xaa, 0xbd, 0xc8, /* sender ip */
+ 0x00, 0x23, 0xC1, 0xDE, 0xD0, 0x0D, /* target mac */
+ 0x00, 0x00, 0x00, 0x00, /* target ip */
+};
+
+static int txpacket;
+static enum tcase {
+ TEST_LWIP_DHCP,
+ TEST_LWIP_DHCP_NAK,
+ TEST_LWIP_DHCP_RELAY,
+ TEST_LWIP_DHCP_NAK_NO_ENDMARKER,
+ TEST_LWIP_DHCP_INVALID_OVERLOAD,
+ TEST_NONE
+} tcase;
+
+static int debug = 0;
+static void setdebug(int a) {debug = a;}
+
+static int tick = 0;
+static void tick_lwip(void)
+{
+ tick++;
+#if LWIP_DHCP_DOES_ACD_CHECK
+ acd_tmr();
+#endif
+ if (tick % 5 == 0) {
+ dhcp_fine_tmr();
+ }
+ if (tick % 600 == 0) {
+ dhcp_coarse_tmr();
+ }
+}
+
+static void send_pkt(struct netif *netif, const u8_t *data, size_t len)
+{
+ struct pbuf *p, *q;
+ LWIP_ASSERT("pkt too big", len <= 0xFFFF);
+ p = pbuf_alloc(PBUF_RAW, (u16_t)len, PBUF_POOL);
+
+ if (debug) {
+ /* Dump data */
+ u32_t i;
+ printf("RX data (len %d)", p->tot_len);
+ for (i = 0; i < len; i++) {
+ printf(" %02X", data[i]);
+ }
+ printf("\n");
+ }
+
+ fail_unless(p != NULL);
+ for(q = p; q != NULL; q = q->next) {
+ memcpy(q->payload, data, q->len);
+ data += q->len;
+ }
+ netif->input(p, netif);
+}
+
+static err_t lwip_tx_func(struct netif *netif, struct pbuf *p);
+
+static err_t testif_init(struct netif *netif)
+{
+ netif->name[0] = 'c';
+ netif->name[1] = 'h';
+ netif->output = etharp_output;
+ netif->linkoutput = lwip_tx_func;
+ netif->mtu = 1500;
+ netif->hwaddr_len = 6;
+ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
+
+ netif->hwaddr[0] = 0x00;
+ netif->hwaddr[1] = 0x23;
+ netif->hwaddr[2] = 0xC1;
+ netif->hwaddr[3] = 0xDE;
+ netif->hwaddr[4] = 0xD0;
+ netif->hwaddr[5] = 0x0D;
+
+ return ERR_OK;
+}
+
+static void dhcp_setup(void)
+{
+ txpacket = 0;
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void dhcp_teardown(void)
+{
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void check_pkt(struct pbuf *p, u32_t pos, const u8_t *mem, u32_t len)
+{
+ u8_t *data;
+
+ fail_if((pos + len) > p->tot_len);
+ while (pos > p->len && p->next) {
+ pos -= p->len;
+ p = p->next;
+ }
+ fail_if(p == NULL);
+ fail_unless(pos + len <= p->len); /* All data we seek within same pbuf */
+
+ data = (u8_t*)p->payload;
+ fail_if(memcmp(&data[pos], mem, len), "data at pos %d, len %d in packet %d did not match", pos, len, txpacket);
+}
+
+static void check_pkt_fuzzy(struct pbuf *p, u32_t startpos, const u8_t *mem, u32_t len)
+{
+ int found;
+ u32_t i;
+ u8_t *data;
+
+ fail_if((startpos + len) > p->tot_len);
+ while (startpos > p->len && p->next) {
+ startpos -= p->len;
+ p = p->next;
+ }
+ fail_if(p == NULL);
+ fail_unless(startpos + len <= p->len); /* All data we seek within same pbuf */
+
+ found = 0;
+ data = (u8_t*)p->payload;
+ for (i = startpos; i <= (p->len - len); i++) {
+ if (memcmp(&data[i], mem, len) == 0) {
+ found = 1;
+ break;
+ }
+ }
+ fail_unless(found);
+}
+
+static err_t lwip_tx_func(struct netif *netif, struct pbuf *p)
+{
+ fail_unless(netif == &net_test);
+ txpacket++;
+
+ if (debug) {
+ struct pbuf *pp = p;
+ /* Dump data */
+ printf("TX data (pkt %d, len %d, tick %d)", txpacket, p->tot_len, tick);
+ do {
+ int i;
+ for (i = 0; i < pp->len; i++) {
+ printf(" %02X", ((u8_t *) pp->payload)[i]);
+ }
+ if (pp->next) {
+ pp = pp->next;
+ }
+ } while (pp->next);
+ printf("\n");
+ }
+
+ switch (tcase) {
+ case TEST_LWIP_DHCP:
+ switch (txpacket) {
+ case 1:
+ case 2:
+ {
+ const u8_t ipproto[] = { 0x08, 0x00 };
+ const u8_t bootp_start[] = { 0x01, 0x01, 0x06, 0x00}; /* bootp request, eth, hwaddr len 6, 0 hops */
+ const u8_t ipaddrs[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+ check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+ check_pkt(p, 12, ipproto, sizeof(ipproto)); /* eth level proto: ip */
+
+ check_pkt(p, 42, bootp_start, sizeof(bootp_start));
+
+ check_pkt(p, 53, ipaddrs, sizeof(ipaddrs));
+
+ check_pkt(p, 70, netif->hwaddr, 6); /* mac addr inside bootp */
+
+ check_pkt(p, 278, magic_cookie, sizeof(magic_cookie));
+
+ /* Check dhcp message type, can be at different positions */
+ if (txpacket == 1) {
+ u8_t dhcp_discover_opt[] = { 0x35, 0x01, 0x01 };
+ check_pkt_fuzzy(p, 282, dhcp_discover_opt, sizeof(dhcp_discover_opt));
+ } else if (txpacket == 2) {
+ u8_t dhcp_request_opt[] = { 0x35, 0x01, 0x03 };
+ u8_t requested_ipaddr[] = { 0x32, 0x04, 0xc3, 0xaa, 0xbd, 0xc8 }; /* Ask for offered IP */
+
+ check_pkt_fuzzy(p, 282, dhcp_request_opt, sizeof(dhcp_request_opt));
+ check_pkt_fuzzy(p, 282, requested_ipaddr, sizeof(requested_ipaddr));
+ }
+ break;
+ }
+#if DHCP_TEST_NUM_ARP_FRAMES > 0
+ case 3:
+#if DHCP_TEST_NUM_ARP_FRAMES > 1
+ case 4:
+#if DHCP_TEST_NUM_ARP_FRAMES > 2
+ case 5:
+#if DHCP_TEST_NUM_ARP_FRAMES > 3
+ case 6:
+#if DHCP_TEST_NUM_ARP_FRAMES > 4
+ case 7:
+#endif
+#endif
+#endif
+#endif
+ {
+ const u8_t arpproto[] = { 0x08, 0x06 };
+
+ check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+ check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+ check_pkt(p, 12, arpproto, sizeof(arpproto)); /* eth level proto: ip */
+ break;
+ }
+#endif
+ default:
+ fail();
+ break;
+ }
+ break;
+
+ case TEST_LWIP_DHCP_NAK:
+ {
+ const u8_t ipproto[] = { 0x08, 0x00 };
+ const u8_t bootp_start[] = { 0x01, 0x01, 0x06, 0x00}; /* bootp request, eth, hwaddr len 6, 0 hops */
+ const u8_t ipaddrs[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ const u8_t dhcp_nak_opt[] = { 0x35, 0x01, 0x04 };
+ const u8_t requested_ipaddr[] = { 0x32, 0x04, 0xc3, 0xaa, 0xbd, 0xc8 }; /* offered IP */
+
+ fail_unless(txpacket == 4);
+ check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+ check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+ check_pkt(p, 12, ipproto, sizeof(ipproto)); /* eth level proto: ip */
+
+ check_pkt(p, 42, bootp_start, sizeof(bootp_start));
+
+ check_pkt(p, 53, ipaddrs, sizeof(ipaddrs));
+
+ check_pkt(p, 70, netif->hwaddr, 6); /* mac addr inside bootp */
+
+ check_pkt(p, 278, magic_cookie, sizeof(magic_cookie));
+
+ check_pkt_fuzzy(p, 282, dhcp_nak_opt, sizeof(dhcp_nak_opt)); /* NAK the ack */
+
+ check_pkt_fuzzy(p, 282, requested_ipaddr, sizeof(requested_ipaddr));
+ break;
+ }
+
+ case TEST_LWIP_DHCP_RELAY:
+ switch (txpacket) {
+ case 1:
+ case 2:
+ {
+ const u8_t ipproto[] = { 0x08, 0x00 };
+ const u8_t bootp_start[] = { 0x01, 0x01, 0x06, 0x00}; /* bootp request, eth, hwaddr len 6, 0 hops */
+ const u8_t ipaddrs[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+ check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+ check_pkt(p, 12, ipproto, sizeof(ipproto)); /* eth level proto: ip */
+
+ check_pkt(p, 42, bootp_start, sizeof(bootp_start));
+
+ check_pkt(p, 53, ipaddrs, sizeof(ipaddrs));
+
+ check_pkt(p, 70, netif->hwaddr, 6); /* mac addr inside bootp */
+
+ check_pkt(p, 278, magic_cookie, sizeof(magic_cookie));
+
+ /* Check dhcp message type, can be at different positions */
+ if (txpacket == 1) {
+ u8_t dhcp_discover_opt[] = { 0x35, 0x01, 0x01 };
+ check_pkt_fuzzy(p, 282, dhcp_discover_opt, sizeof(dhcp_discover_opt));
+ } else if (txpacket == 2) {
+ u8_t dhcp_request_opt[] = { 0x35, 0x01, 0x03 };
+ u8_t requested_ipaddr[] = { 0x32, 0x04, 0x4f, 0x8a, 0x33, 0x05 }; /* Ask for offered IP */
+
+ check_pkt_fuzzy(p, 282, dhcp_request_opt, sizeof(dhcp_request_opt));
+ check_pkt_fuzzy(p, 282, requested_ipaddr, sizeof(requested_ipaddr));
+ }
+ break;
+ }
+ case 3:
+#if DHCP_TEST_NUM_ARP_FRAMES > 0
+ case 4:
+#if DHCP_TEST_NUM_ARP_FRAMES > 1
+ case 5:
+#if DHCP_TEST_NUM_ARP_FRAMES > 2
+ case 6:
+#if DHCP_TEST_NUM_ARP_FRAMES > 3
+ case 7:
+#if DHCP_TEST_NUM_ARP_FRAMES > 4
+ case 8:
+#endif
+#endif
+#endif
+#endif
+ {
+ const u8_t arpproto[] = { 0x08, 0x06 };
+
+ check_pkt(p, 0, broadcast, 6); /* eth level dest: broadcast */
+ check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+ check_pkt(p, 12, arpproto, sizeof(arpproto)); /* eth level proto: ip */
+ break;
+ }
+#endif
+ case 4 + DHCP_TEST_NUM_ARP_FRAMES:
+ {
+ const u8_t fake_arp[6] = { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xab };
+ const u8_t ipproto[] = { 0x08, 0x00 };
+ const u8_t bootp_start[] = { 0x01, 0x01, 0x06, 0x00}; /* bootp request, eth, hwaddr len 6, 0 hops */
+ const u8_t ipaddrs[] = { 0x00, 0x4f, 0x8a, 0x33, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+ const u8_t dhcp_request_opt[] = { 0x35, 0x01, 0x03 };
+
+ check_pkt(p, 0, fake_arp, 6); /* eth level dest: broadcast */
+ check_pkt(p, 6, netif->hwaddr, 6); /* eth level src: unit mac */
+
+ check_pkt(p, 12, ipproto, sizeof(ipproto)); /* eth level proto: ip */
+
+ check_pkt(p, 42, bootp_start, sizeof(bootp_start));
+
+ check_pkt(p, 53, ipaddrs, sizeof(ipaddrs));
+
+ check_pkt(p, 70, netif->hwaddr, 6); /* mac addr inside bootp */
+
+ check_pkt(p, 278, magic_cookie, sizeof(magic_cookie));
+
+ /* Check dhcp message type, can be at different positions */
+ check_pkt_fuzzy(p, 282, dhcp_request_opt, sizeof(dhcp_request_opt));
+ break;
+ }
+ default:
+ fail();
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return ERR_OK;
+}
+
+/*
+ * Test basic happy flow DHCP session.
+ * Validate that xid is checked.
+ */
+START_TEST(test_dhcp)
+{
+ ip4_addr_t addr;
+ ip4_addr_t netmask;
+ ip4_addr_t gw;
+ int i;
+ u32_t xid;
+ LWIP_UNUSED_ARG(_i);
+
+ tcase = TEST_LWIP_DHCP;
+ setdebug(0);
+
+ IP4_ADDR(&addr, 0, 0, 0, 0);
+ IP4_ADDR(&netmask, 0, 0, 0, 0);
+ IP4_ADDR(&gw, 0, 0, 0, 0);
+
+ netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+ netif_set_link_up(&net_test);
+ netif_set_up(&net_test);
+
+ dhcp_start(&net_test);
+
+ fail_unless(txpacket == 1); /* DHCP discover sent */
+ xid = netif_dhcp_data(&net_test)->xid; /* Write bad xid, not using htonl! */
+ memcpy(&dhcp_offer[46], &xid, 4);
+ send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+ /* IP addresses should be zero */
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+
+ fail_unless(txpacket == 1, "TX %d packets, expected 1", txpacket); /* Nothing more sent */
+ xid = htonl(netif_dhcp_data(&net_test)->xid);
+ memcpy(&dhcp_offer[46], &xid, 4); /* insert correct transaction id */
+ send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+ fail_unless(txpacket == 2, "TX %d packets, expected 2", txpacket); /* DHCP request sent */
+ xid = netif_dhcp_data(&net_test)->xid; /* Write bad xid, not using htonl! */
+ memcpy(&dhcp_ack[46], &xid, 4);
+ send_pkt(&net_test, dhcp_ack, sizeof(dhcp_ack));
+
+ fail_unless(txpacket == 2, "TX %d packets, still expected 2", txpacket); /* No more sent */
+ xid = htonl(netif_dhcp_data(&net_test)->xid); /* xid updated */
+ memcpy(&dhcp_ack[46], &xid, 4); /* insert transaction id */
+ send_pkt(&net_test, dhcp_ack, sizeof(dhcp_ack));
+
+ fail_unless(txpacket == 2);
+
+ for (i = 0; i < 200; i++) {
+ tick_lwip();
+ }
+ fail_unless(txpacket == (2 + DHCP_TEST_NUM_ARP_FRAMES), "TX %d packets, expected %d", txpacket, (2 + DHCP_TEST_NUM_ARP_FRAMES));
+
+ /* Interface up */
+ fail_unless(netif_is_up(&net_test));
+
+ /* Now it should have taken the IP */
+ IP4_ADDR(&addr, 195, 170, 189, 200);
+ IP4_ADDR(&netmask, 255, 255, 255, 0);
+ IP4_ADDR(&gw, 195, 170, 189, 171);
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+
+ tcase = TEST_NONE;
+ dhcp_stop(&net_test);
+ dhcp_cleanup(&net_test);
+ netif_remove(&net_test);
+}
+END_TEST
+
+/*
+ * Test that IP address is not taken and NAK is sent if someone
+ * replies to ARP requests for the offered address.
+ */
+START_TEST(test_dhcp_nak)
+{
+ ip4_addr_t addr;
+ ip4_addr_t netmask;
+ ip4_addr_t gw;
+ u32_t xid;
+ LWIP_UNUSED_ARG(_i);
+
+ tcase = TEST_LWIP_DHCP;
+ setdebug(0);
+
+ IP4_ADDR(&addr, 0, 0, 0, 0);
+ IP4_ADDR(&netmask, 0, 0, 0, 0);
+ IP4_ADDR(&gw, 0, 0, 0, 0);
+
+ netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+ netif_set_link_up(&net_test);
+ netif_set_up(&net_test);
+
+ dhcp_start(&net_test);
+
+ fail_unless(txpacket == 1); /* DHCP discover sent */
+ xid = netif_dhcp_data(&net_test)->xid; /* Write bad xid, not using htonl! */
+ memcpy(&dhcp_offer[46], &xid, 4);
+ send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+ /* IP addresses should be zero */
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+
+ fail_unless(txpacket == 1); /* Nothing more sent */
+ xid = htonl(netif_dhcp_data(&net_test)->xid);
+ memcpy(&dhcp_offer[46], &xid, 4); /* insert correct transaction id */
+ send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+ fail_unless(txpacket == 2); /* DHCP request sent */
+ xid = netif_dhcp_data(&net_test)->xid; /* Write bad xid, not using htonl! */
+ memcpy(&dhcp_ack[46], &xid, 4);
+ send_pkt(&net_test, dhcp_ack, sizeof(dhcp_ack));
+
+ fail_unless(txpacket == 2); /* No more sent */
+ xid = htonl(netif_dhcp_data(&net_test)->xid); /* xid updated */
+ memcpy(&dhcp_ack[46], &xid, 4); /* insert transaction id */
+ send_pkt(&net_test, dhcp_ack, sizeof(dhcp_ack));
+
+ fail_unless(txpacket == 2); /* ARP request sent */
+
+ while (txpacket == 2) {
+ tick_lwip();
+ }
+ fail_unless(txpacket == 3);
+
+ tcase = TEST_LWIP_DHCP_NAK; /* Switch testcase */
+
+ /* Send arp reply, mark offered IP as taken */
+ send_pkt(&net_test, arpreply, sizeof(arpreply));
+
+ fail_unless(txpacket == 4); /* DHCP nak sent */
+
+ tcase = TEST_NONE;
+ dhcp_stop(&net_test);
+ dhcp_cleanup(&net_test);
+ netif_remove(&net_test);
+}
+END_TEST
+
+/*
+ * Test case based on captured data where
+ * replies are sent from a different IP than the
+ * one the client unicasted to.
+ */
+START_TEST(test_dhcp_relayed)
+{
+ u8_t relay_offer[] = {
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d,
+ 0x00, 0x22, 0x93, 0x5a, 0xf7, 0x60,
+ 0x08, 0x00, 0x45, 0x00,
+ 0x01, 0x38, 0xfd, 0x53, 0x00, 0x00, 0x40, 0x11,
+ 0x78, 0x46, 0x4f, 0x8a, 0x32, 0x02, 0x4f, 0x8a,
+ 0x33, 0x05, 0x00, 0x43, 0x00, 0x44, 0x01, 0x24,
+ 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, 0x51, 0x35,
+ 0xb6, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x4f, 0x8a, 0x33, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x0a, 0xb5, 0x04, 0x01, 0x00, 0x23,
+ 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82,
+ 0x53, 0x63, 0x01, 0x04, 0xff, 0xff, 0xfe, 0x00,
+ 0x03, 0x04, 0x4f, 0x8a, 0x32, 0x01, 0x06, 0x08,
+ 0x4f, 0x8a, 0x00, 0xb4, 0x55, 0x08, 0x1f, 0xd1,
+ 0x1c, 0x04, 0x4f, 0x8a, 0x33, 0xff, 0x33, 0x04,
+ 0x00, 0x00, 0x54, 0x49, 0x35, 0x01, 0x02, 0x36,
+ 0x04, 0x0a, 0xb5, 0x04, 0x01, 0xff
+ };
+
+ u8_t relay_ack1[] = {
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x22,
+ 0x93, 0x5a, 0xf7, 0x60, 0x08, 0x00, 0x45, 0x00,
+ 0x01, 0x38, 0xfd, 0x55, 0x00, 0x00, 0x40, 0x11,
+ 0x78, 0x44, 0x4f, 0x8a, 0x32, 0x02, 0x4f, 0x8a,
+ 0x33, 0x05, 0x00, 0x43, 0x00, 0x44, 0x01, 0x24,
+ 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, 0x51, 0x35,
+ 0xb6, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x4f, 0x8a, 0x33, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x0a, 0xb5, 0x04, 0x01, 0x00, 0x23,
+ 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82,
+ 0x53, 0x63, 0x01, 0x04, 0xff, 0xff, 0xfe, 0x00,
+ 0x03, 0x04, 0x4f, 0x8a, 0x32, 0x01, 0x06, 0x08,
+ 0x4f, 0x8a, 0x00, 0xb4, 0x55, 0x08, 0x1f, 0xd1,
+ 0x1c, 0x04, 0x4f, 0x8a, 0x33, 0xff, 0x33, 0x04,
+ 0x00, 0x00, 0x54, 0x49, 0x35, 0x01, 0x05, 0x36,
+ 0x04, 0x0a, 0xb5, 0x04, 0x01, 0xff
+ };
+
+ u8_t relay_ack2[] = {
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d,
+ 0x00, 0x22, 0x93, 0x5a, 0xf7, 0x60,
+ 0x08, 0x00, 0x45, 0x00,
+ 0x01, 0x38, 0xfa, 0x18, 0x00, 0x00, 0x40, 0x11,
+ 0x7b, 0x81, 0x4f, 0x8a, 0x32, 0x02, 0x4f, 0x8a,
+ 0x33, 0x05, 0x00, 0x43, 0x00, 0x44, 0x01, 0x24,
+ 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, 0x49, 0x8b,
+ 0x6e, 0xab, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x8a,
+ 0x33, 0x05, 0x4f, 0x8a, 0x33, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x0a, 0xb5, 0x04, 0x01, 0x00, 0x23,
+ 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82,
+ 0x53, 0x63, 0x01, 0x04, 0xff, 0xff, 0xfe, 0x00,
+ 0x03, 0x04, 0x4f, 0x8a, 0x32, 0x01, 0x06, 0x08,
+ 0x4f, 0x8a, 0x00, 0xb4, 0x55, 0x08, 0x1f, 0xd1,
+ 0x1c, 0x04, 0x4f, 0x8a, 0x33, 0xff, 0x33, 0x04,
+ 0x00, 0x00, 0x54, 0x60, 0x35, 0x01, 0x05, 0x36,
+ 0x04, 0x0a, 0xb5, 0x04, 0x01, 0xff };
+
+ const u8_t arp_resp[] = {
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, /* DEST */
+ 0x00, 0x22, 0x93, 0x5a, 0xf7, 0x60, /* SRC */
+ 0x08, 0x06, /* Type: ARP */
+ 0x00, 0x01, /* HW: Ethernet */
+ 0x08, 0x00, /* PROTO: IP */
+ 0x06, /* HW size */
+ 0x04, /* PROTO size */
+ 0x00, 0x02, /* OPCODE: Reply */
+
+ 0x12, 0x34, 0x56, 0x78, 0x9a, 0xab, /* Target MAC */
+ 0x4f, 0x8a, 0x32, 0x01, /* Target IP */
+
+ 0x00, 0x23, 0xc1, 0x00, 0x06, 0x50, /* src mac */
+ 0x4f, 0x8a, 0x33, 0x05, /* src ip */
+
+ /* Padding follows.. */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+
+ ip4_addr_t addr;
+ ip4_addr_t netmask;
+ ip4_addr_t gw;
+ int i;
+ u32_t xid;
+ LWIP_UNUSED_ARG(_i);
+
+ tcase = TEST_LWIP_DHCP_RELAY;
+ setdebug(0);
+
+ IP4_ADDR(&addr, 0, 0, 0, 0);
+ IP4_ADDR(&netmask, 0, 0, 0, 0);
+ IP4_ADDR(&gw, 0, 0, 0, 0);
+
+ netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+ netif_set_link_up(&net_test);
+ netif_set_up(&net_test);
+
+ dhcp_start(&net_test);
+
+ fail_unless(txpacket == 1); /* DHCP discover sent */
+
+ /* IP addresses should be zero */
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+
+ fail_unless(txpacket == 1); /* Nothing more sent */
+ xid = htonl(netif_dhcp_data(&net_test)->xid);
+ memcpy(&relay_offer[46], &xid, 4); /* insert correct transaction id */
+ send_pkt(&net_test, relay_offer, sizeof(relay_offer));
+
+ /* request sent? */
+ fail_unless(txpacket == 2, "txpkt = %d, should be 2", txpacket);
+ xid = htonl(netif_dhcp_data(&net_test)->xid); /* xid updated */
+ memcpy(&relay_ack1[46], &xid, 4); /* insert transaction id */
+ send_pkt(&net_test, relay_ack1, sizeof(relay_ack1));
+
+ for (i = 0; i < 200; i++) {
+ tick_lwip();
+ }
+ fail_unless(txpacket == (2 + DHCP_TEST_NUM_ARP_FRAMES), "TX %d packets, expected %d", txpacket, (2 + DHCP_TEST_NUM_ARP_FRAMES));
+
+ /* Interface up */
+ fail_unless(netif_is_up(&net_test));
+
+ /* Now it should have taken the IP */
+ IP4_ADDR(&addr, 79, 138, 51, 5);
+ IP4_ADDR(&netmask, 255, 255, 254, 0);
+ IP4_ADDR(&gw, 79, 138, 50, 1);
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+
+ fail_unless(txpacket == (2 + DHCP_TEST_NUM_ARP_FRAMES), "TX %d packets, expected %d", txpacket, (2 + DHCP_TEST_NUM_ARP_FRAMES));
+
+ for (i = 0; i < 108000 - 25; i++) {
+ tick_lwip();
+ }
+
+ fail_unless(netif_is_up(&net_test));
+ fail_unless(txpacket == (3 + DHCP_TEST_NUM_ARP_FRAMES), "TX %d packets, expected %d", txpacket, (3 + DHCP_TEST_NUM_ARP_FRAMES));
+
+ /* We need to send arp response here.. */
+
+ send_pkt(&net_test, arp_resp, sizeof(arp_resp));
+
+ fail_unless(txpacket == (4 + DHCP_TEST_NUM_ARP_FRAMES), "TX %d packets, expected %d", txpacket, (4 + DHCP_TEST_NUM_ARP_FRAMES));
+ fail_unless(netif_is_up(&net_test));
+
+ xid = htonl(netif_dhcp_data(&net_test)->xid); /* xid updated */
+ memcpy(&relay_ack2[46], &xid, 4); /* insert transaction id */
+ send_pkt(&net_test, relay_ack2, sizeof(relay_ack2));
+
+ for (i = 0; i < 100000; i++) {
+ tick_lwip();
+ }
+
+ fail_unless(txpacket == (4 + DHCP_TEST_NUM_ARP_FRAMES), "TX %d packets, expected %d", txpacket, (5 + DHCP_TEST_NUM_ARP_FRAMES));
+
+ tcase = TEST_NONE;
+ dhcp_stop(&net_test);
+ dhcp_cleanup(&net_test);
+ netif_remove(&net_test);
+
+}
+END_TEST
+
+START_TEST(test_dhcp_nak_no_endmarker)
+{
+ ip4_addr_t addr;
+ ip4_addr_t netmask;
+ ip4_addr_t gw;
+
+ u8_t dhcp_nack_no_endmarker[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x54, 0x75,
+ 0xd0, 0x26, 0xd0, 0x0d, 0x08, 0x00, 0x45, 0x00,
+ 0x01, 0x15, 0x38, 0x86, 0x00, 0x00, 0xff, 0x11,
+ 0xc0, 0xa8, 0xc0, 0xa8, 0x01, 0x01, 0xff, 0xff,
+ 0xff, 0xff, 0x00, 0x43, 0x00, 0x44, 0x01, 0x01,
+ 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, 0x7a, 0xcb,
+ 0xba, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23,
+ 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82,
+ 0x53, 0x63, 0x35, 0x01, 0x06, 0x36, 0x04, 0xc0,
+ 0xa8, 0x01, 0x01, 0x31, 0xef, 0xad, 0x72, 0x31,
+ 0x43, 0x4e, 0x44, 0x30, 0x32, 0x35, 0x30, 0x43,
+ 0x52, 0x47, 0x44, 0x38, 0x35, 0x36, 0x3c, 0x08,
+ 0x4d, 0x53, 0x46, 0x54, 0x20, 0x35, 0x2e, 0x30,
+ 0x37, 0x0d, 0x01, 0x0f, 0x03, 0x06, 0x2c, 0x2e,
+ 0x2f, 0x1f, 0x21, 0x79, 0xf9, 0x2b, 0xfc, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe2, 0x71,
+ 0xf3, 0x5b, 0xe2, 0x71, 0x2e, 0x01, 0x08, 0x03,
+ 0x04, 0xc0, 0xa8, 0x01, 0x01, 0xff, 0xeb, 0x1e,
+ 0x44, 0xec, 0xeb, 0x1e, 0x30, 0x37, 0x0c, 0x01,
+ 0x0f, 0x03, 0x06, 0x2c, 0x2e, 0x2f, 0x1f, 0x21,
+ 0x79, 0xf9, 0x2b, 0xff, 0x25, 0xc0, 0x09, 0xd6,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ u32_t xid;
+ struct dhcp* dhcp;
+ u8_t tries;
+ u16_t request_timeout;
+ LWIP_UNUSED_ARG(_i);
+
+ tcase = TEST_LWIP_DHCP_NAK_NO_ENDMARKER;
+ setdebug(0);
+
+ IP4_ADDR(&addr, 0, 0, 0, 0);
+ IP4_ADDR(&netmask, 0, 0, 0, 0);
+ IP4_ADDR(&gw, 0, 0, 0, 0);
+
+ netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+ netif_set_link_up(&net_test);
+ netif_set_up(&net_test);
+
+ dhcp_start(&net_test);
+ dhcp = netif_dhcp_data(&net_test);
+
+ fail_unless(txpacket == 1); /* DHCP discover sent */
+ xid = dhcp->xid; /* Write bad xid, not using htonl! */
+ memcpy(&dhcp_offer[46], &xid, 4);
+ send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+ /* IP addresses should be zero */
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+
+ fail_unless(txpacket == 1); /* Nothing more sent */
+ xid = htonl(dhcp->xid);
+ memcpy(&dhcp_offer[46], &xid, 4); /* insert correct transaction id */
+ send_pkt(&net_test, dhcp_offer, sizeof(dhcp_offer));
+
+ fail_unless(dhcp->state == DHCP_STATE_REQUESTING);
+
+ fail_unless(txpacket == 2); /* No more sent */
+ xid = htonl(dhcp->xid); /* xid updated */
+ memcpy(&dhcp_nack_no_endmarker[46], &xid, 4); /* insert transaction id */
+ tries = dhcp->tries;
+ request_timeout = dhcp->request_timeout;
+ send_pkt(&net_test, dhcp_nack_no_endmarker, sizeof(dhcp_nack_no_endmarker));
+
+ /* NAK should be ignored */
+ fail_unless(dhcp->state == DHCP_STATE_REQUESTING);
+ fail_unless(txpacket == 2); /* No more sent */
+ fail_unless(xid == htonl(dhcp->xid));
+ fail_unless(tries == dhcp->tries);
+ fail_unless(request_timeout == dhcp->request_timeout);
+
+ tcase = TEST_NONE;
+ dhcp_stop(&net_test);
+ dhcp_cleanup(&net_test);
+ netif_remove(&net_test);
+}
+END_TEST
+
+START_TEST(test_dhcp_invalid_overload)
+{
+ u8_t dhcp_offer_invalid_overload[] = {
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, /* To unit */
+ 0x00, 0x0F, 0xEE, 0x30, 0xAB, 0x22, /* From Remote host */
+ 0x08, 0x00, /* Protocol: IP */
+ 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, 0x80, 0x11, 0x36, 0xcc, 0xc3, 0xaa, 0xbd, 0xab, 0xc3, 0xaa, 0xbd, 0xc8, /* IP header */
+ 0x00, 0x43, 0x00, 0x44, 0x01, 0x34, 0x00, 0x00, /* UDP header */
+
+ 0x02, /* Type == Boot reply */
+ 0x01, 0x06, /* Hw Ethernet, 6 bytes addrlen */
+ 0x00, /* 0 hops */
+ 0xAA, 0xAA, 0xAA, 0xAA, /* Transaction id, will be overwritten */
+ 0x00, 0x00, /* 0 seconds elapsed */
+ 0x00, 0x00, /* Flags (unicast) */
+ 0x00, 0x00, 0x00, 0x00, /* Client ip */
+ 0xc3, 0xaa, 0xbd, 0xc8, /* Your IP */
+ 0xc3, 0xaa, 0xbd, 0xab, /* DHCP server ip */
+ 0x00, 0x00, 0x00, 0x00, /* relay agent */
+ 0x00, 0x23, 0xc1, 0xde, 0xd0, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* MAC addr + padding */
+
+ /* Empty server name */
+ 0x34, 0x01, 0x02, 0xff, /* Overload: SNAME + END */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* Empty boot file name */
+ 0x34, 0x01, 0x01, 0xff, /* Overload FILE + END */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ 0x63, 0x82, 0x53, 0x63, /* Magic cookie */
+ 0x35, 0x01, 0x02, /* Message type: Offer */
+ 0x36, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* Server identifier (IP) */
+ 0x33, 0x04, 0x00, 0x00, 0x00, 0x78, /* Lease time 2 minutes */
+ 0x03, 0x04, 0xc3, 0xaa, 0xbd, 0xab, /* Router IP */
+ 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, /* Subnet mask */
+ 0x34, 0x01, 0x03, /* Overload: FILE + SNAME */
+ 0xff, /* End option */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Padding */
+ };
+ ip4_addr_t addr;
+ ip4_addr_t netmask;
+ ip4_addr_t gw;
+ u32_t xid;
+ LWIP_UNUSED_ARG(_i);
+
+ tcase = TEST_LWIP_DHCP_INVALID_OVERLOAD;
+ setdebug(0);
+
+ IP4_ADDR(&addr, 0, 0, 0, 0);
+ IP4_ADDR(&netmask, 0, 0, 0, 0);
+ IP4_ADDR(&gw, 0, 0, 0, 0);
+
+ netif_add(&net_test, &addr, &netmask, &gw, &net_test, testif_init, ethernet_input);
+ netif_set_link_up(&net_test);
+ netif_set_up(&net_test);
+
+ dhcp_start(&net_test);
+
+ fail_unless(txpacket == 1); /* DHCP discover sent */
+ xid = htonl(netif_dhcp_data(&net_test)->xid);
+ memcpy(&dhcp_offer_invalid_overload[46], &xid, 4); /* insert correct transaction id */
+ dhcp_offer_invalid_overload[311] = 3;
+ send_pkt(&net_test, dhcp_offer_invalid_overload, sizeof(dhcp_offer_invalid_overload));
+ /* IP addresses should be zero */
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+ fail_unless(txpacket == 1); /* Nothing more sent */
+
+ dhcp_offer_invalid_overload[311] = 2;
+ send_pkt(&net_test, dhcp_offer_invalid_overload, sizeof(dhcp_offer_invalid_overload));
+ /* IP addresses should be zero */
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+ fail_unless(txpacket == 1); /* Nothing more sent */
+
+ dhcp_offer_invalid_overload[311] = 1;
+ send_pkt(&net_test, dhcp_offer_invalid_overload, sizeof(dhcp_offer_invalid_overload));
+ /* IP addresses should be zero */
+ fail_if(memcmp(&addr, &net_test.ip_addr, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&netmask, &net_test.netmask, sizeof(ip4_addr_t)));
+ fail_if(memcmp(&gw, &net_test.gw, sizeof(ip4_addr_t)));
+ fail_unless(txpacket == 1); /* Nothing more sent */
+
+ dhcp_offer_invalid_overload[311] = 0;
+ send_pkt(&net_test, dhcp_offer_invalid_overload, sizeof(dhcp_offer));
+
+ fail_unless(netif_dhcp_data(&net_test)->state == DHCP_STATE_REQUESTING);
+
+ fail_unless(txpacket == 2); /* No more sent */
+ xid = htonl(netif_dhcp_data(&net_test)->xid); /* xid updated */
+
+ tcase = TEST_NONE;
+ dhcp_stop(&net_test);
+ dhcp_cleanup(&net_test);
+ netif_remove(&net_test);
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+dhcp_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_dhcp),
+ TESTFUNC(test_dhcp_nak),
+ TESTFUNC(test_dhcp_relayed),
+ TESTFUNC(test_dhcp_nak_no_endmarker),
+ TESTFUNC(test_dhcp_invalid_overload)
+ };
+ return create_suite("DHCP", tests, sizeof(tests)/sizeof(testfunc), dhcp_setup, dhcp_teardown);
+}
diff --git a/test/unit/dhcp/test_dhcp.h b/test/unit/dhcp/test_dhcp.h
new file mode 100644
index 00000000000..0d88fa1b695
--- /dev/null
+++ b/test/unit/dhcp/test_dhcp.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_DHCP_H
+#define LWIP_HDR_TEST_DHCP_H
+
+#include "../lwip_check.h"
+
+Suite* dhcp_suite(void);
+
+#endif
diff --git a/test/unit/etharp/test_etharp.c b/test/unit/etharp/test_etharp.c
new file mode 100644
index 00000000000..aeaacb20015
--- /dev/null
+++ b/test/unit/etharp/test_etharp.c
@@ -0,0 +1,273 @@
+#include "test_etharp.h"
+
+#include "lwip/udp.h"
+#include "lwip/etharp.h"
+#include "lwip/inet.h"
+#include "netif/ethernet.h"
+#include "lwip/stats.h"
+#include "lwip/prot/iana.h"
+
+#if !LWIP_STATS || !UDP_STATS || !MEMP_STATS || !ETHARP_STATS
+#error "This tests needs UDP-, MEMP- and ETHARP-statistics enabled"
+#endif
+#if !ETHARP_SUPPORT_STATIC_ENTRIES
+#error "This test needs ETHARP_SUPPORT_STATIC_ENTRIES enabled"
+#endif
+
+static struct netif test_netif;
+static ip4_addr_t test_ipaddr, test_netmask, test_gw;
+struct eth_addr test_ethaddr = {{1,1,1,1,1,1}};
+struct eth_addr test_ethaddr2 = {{1,1,1,1,1,2}};
+struct eth_addr test_ethaddr3 = {{1,1,1,1,1,3}};
+struct eth_addr test_ethaddr4 = {{1,1,1,1,1,4}};
+static int linkoutput_ctr;
+
+/* Helper functions */
+static void
+etharp_remove_all(void)
+{
+ int i;
+ /* call etharp_tmr often enough to have all entries cleaned */
+ for(i = 0; i < 0xff; i++) {
+ etharp_tmr();
+ }
+}
+
+static err_t
+default_netif_linkoutput(struct netif *netif, struct pbuf *p)
+{
+ fail_unless(netif == &test_netif);
+ fail_unless(p != NULL);
+ linkoutput_ctr++;
+ return ERR_OK;
+}
+
+static err_t
+default_netif_init(struct netif *netif)
+{
+ fail_unless(netif != NULL);
+ netif->linkoutput = default_netif_linkoutput;
+ netif->output = etharp_output;
+ netif->mtu = 1500;
+ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
+ netif->hwaddr_len = ETHARP_HWADDR_LEN;
+ return ERR_OK;
+}
+
+static void
+default_netif_add(void)
+{
+ IP4_ADDR(&test_gw, 192,168,0,1);
+ IP4_ADDR(&test_ipaddr, 192,168,0,1);
+ IP4_ADDR(&test_netmask, 255,255,0,0);
+
+ fail_unless(netif_default == NULL);
+ netif_set_default(netif_add(&test_netif, &test_ipaddr, &test_netmask,
+ &test_gw, NULL, default_netif_init, NULL));
+ netif_set_up(&test_netif);
+}
+
+static void
+default_netif_remove(void)
+{
+ fail_unless(netif_default == &test_netif);
+ netif_remove(&test_netif);
+}
+
+static void
+create_arp_response(ip4_addr_t *adr)
+{
+ int k;
+ struct eth_hdr *ethhdr;
+ struct etharp_hdr *etharphdr;
+ struct pbuf *p = pbuf_alloc(PBUF_RAW, sizeof(struct eth_hdr) + sizeof(struct etharp_hdr), PBUF_RAM);
+ if(p == NULL) {
+ FAIL_RET();
+ }
+ ethhdr = (struct eth_hdr*)p->payload;
+ etharphdr = (struct etharp_hdr*)(ethhdr + 1);
+
+ ethhdr->dest = test_ethaddr;
+ ethhdr->src = test_ethaddr2;
+ ethhdr->type = htons(ETHTYPE_ARP);
+
+ etharphdr->hwtype = htons(LWIP_IANA_HWTYPE_ETHERNET);
+ etharphdr->proto = htons(ETHTYPE_IP);
+ etharphdr->hwlen = ETHARP_HWADDR_LEN;
+ etharphdr->protolen = sizeof(ip4_addr_t);
+ etharphdr->opcode = htons(ARP_REPLY);
+
+ SMEMCPY(&etharphdr->sipaddr, adr, sizeof(ip4_addr_t));
+ SMEMCPY(&etharphdr->dipaddr, &test_ipaddr, sizeof(ip4_addr_t));
+
+ k = 6;
+ while(k > 0) {
+ k--;
+ /* Write the ARP MAC-Addresses */
+ etharphdr->shwaddr.addr[k] = test_ethaddr2.addr[k];
+ etharphdr->dhwaddr.addr[k] = test_ethaddr.addr[k];
+ /* Write the Ethernet MAC-Addresses */
+ ethhdr->dest.addr[k] = test_ethaddr.addr[k];
+ ethhdr->src.addr[k] = test_ethaddr2.addr[k];
+ }
+
+ ethernet_input(p, &test_netif);
+}
+
+/* Setups/teardown functions */
+
+static void
+etharp_setup(void)
+{
+ etharp_remove_all();
+ default_netif_add();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+etharp_teardown(void)
+{
+ etharp_remove_all();
+ default_netif_remove();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+
+/* Test functions */
+
+START_TEST(test_etharp_table)
+{
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+ err_t err;
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+ ssize_t idx;
+ const ip4_addr_t *unused_ipaddr;
+ struct eth_addr *unused_ethaddr;
+ struct udp_pcb* pcb;
+ LWIP_UNUSED_ARG(_i);
+
+ if (netif_default != &test_netif) {
+ fail("This test needs a default netif");
+ }
+
+ linkoutput_ctr = 0;
+
+ pcb = udp_new();
+ fail_unless(pcb != NULL);
+ if (pcb != NULL) {
+ ip4_addr_t adrs[ARP_TABLE_SIZE + 2];
+ int i;
+ for(i = 0; i < ARP_TABLE_SIZE + 2; i++) {
+ IP4_ADDR(&adrs[i], 192,168,0,i+2);
+ }
+ /* fill ARP-table with dynamic entries */
+ for(i = 0; i < ARP_TABLE_SIZE; i++) {
+ struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, 10, PBUF_RAM);
+ fail_unless(p != NULL);
+ if (p != NULL) {
+ err_t err2;
+ ip_addr_t dst;
+ ip_addr_copy_from_ip4(dst, adrs[i]);
+ err2 = udp_sendto(pcb, p, &dst, 123);
+ fail_unless(err2 == ERR_OK);
+ /* etharp request sent? */
+ fail_unless(linkoutput_ctr == (2*i) + 1);
+ pbuf_free(p);
+
+ /* create an ARP response */
+ create_arp_response(&adrs[i]);
+ /* queued UDP packet sent? */
+ fail_unless(linkoutput_ctr == (2*i) + 2);
+
+ idx = etharp_find_addr(NULL, &adrs[i], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == i);
+ etharp_tmr();
+ }
+ }
+ linkoutput_ctr = 0;
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+ /* create one static entry */
+ err = etharp_add_static_entry(&adrs[ARP_TABLE_SIZE], &test_ethaddr3);
+ fail_unless(err == ERR_OK);
+ idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == 0);
+ fail_unless(linkoutput_ctr == 0);
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+ linkoutput_ctr = 0;
+ /* fill ARP-table with dynamic entries */
+ for(i = 0; i < ARP_TABLE_SIZE; i++) {
+ struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, 10, PBUF_RAM);
+ fail_unless(p != NULL);
+ if (p != NULL) {
+ err_t err2;
+ ip_addr_t dst;
+ ip_addr_copy_from_ip4(dst, adrs[i]);
+ err2 = udp_sendto(pcb, p, &dst, 123);
+ fail_unless(err2 == ERR_OK);
+ /* etharp request sent? */
+ fail_unless(linkoutput_ctr == (2*i) + 1);
+ pbuf_free(p);
+
+ /* create an ARP response */
+ create_arp_response(&adrs[i]);
+ /* queued UDP packet sent? */
+ fail_unless(linkoutput_ctr == (2*i) + 2);
+
+ idx = etharp_find_addr(NULL, &adrs[i], &unused_ethaddr, &unused_ipaddr);
+ if (i < ARP_TABLE_SIZE - 1) {
+ fail_unless(idx == i+1);
+ } else {
+ /* the last entry must not overwrite the static entry! */
+ fail_unless(idx == 1);
+ }
+ etharp_tmr();
+ }
+ }
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+ /* create a second static entry */
+ err = etharp_add_static_entry(&adrs[ARP_TABLE_SIZE+1], &test_ethaddr4);
+ fail_unless(err == ERR_OK);
+ idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == 0);
+ idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE+1], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == 2);
+ /* and remove it again */
+ err = etharp_remove_static_entry(&adrs[ARP_TABLE_SIZE+1]);
+ fail_unless(err == ERR_OK);
+ idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == 0);
+ idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE+1], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == -1);
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+ /* check that static entries don't time out */
+ etharp_remove_all();
+ idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == 0);
+
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+ /* remove the first static entry */
+ err = etharp_remove_static_entry(&adrs[ARP_TABLE_SIZE]);
+ fail_unless(err == ERR_OK);
+ idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == -1);
+ idx = etharp_find_addr(NULL, &adrs[ARP_TABLE_SIZE+1], &unused_ethaddr, &unused_ipaddr);
+ fail_unless(idx == -1);
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+
+ udp_remove(pcb);
+ }
+}
+END_TEST
+
+
+/** Create the suite including all tests for this module */
+Suite *
+etharp_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_etharp_table)
+ };
+ return create_suite("ETHARP", tests, sizeof(tests)/sizeof(testfunc), etharp_setup, etharp_teardown);
+}
diff --git a/test/unit/etharp/test_etharp.h b/test/unit/etharp/test_etharp.h
new file mode 100644
index 00000000000..2dd772d8d0d
--- /dev/null
+++ b/test/unit/etharp/test_etharp.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_ETHARP_H
+#define LWIP_HDR_TEST_ETHARP_H
+
+#include "../lwip_check.h"
+
+Suite* etharp_suite(void);
+
+#endif
diff --git a/test/unit/ip4/test_ip4.c b/test/unit/ip4/test_ip4.c
new file mode 100644
index 00000000000..af715daf7aa
--- /dev/null
+++ b/test/unit/ip4/test_ip4.c
@@ -0,0 +1,342 @@
+#include "test_ip4.h"
+
+#include "lwip/icmp.h"
+#include "lwip/ip4.h"
+#include "lwip/etharp.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/stats.h"
+#include "lwip/prot/ip.h"
+#include "lwip/prot/ip4.h"
+
+#include "lwip/tcpip.h"
+
+#if !LWIP_IPV4 || !IP_REASSEMBLY || !MIB2_STATS || !IPFRAG_STATS
+#error "This tests needs LWIP_IPV4, IP_REASSEMBLY; MIB2- and IPFRAG-statistics enabled"
+#endif
+
+static struct netif test_netif;
+static ip4_addr_t test_ipaddr, test_netmask, test_gw;
+static int linkoutput_ctr;
+static int linkoutput_byte_ctr;
+static u16_t linkoutput_pkt_len;
+static u8_t linkoutput_pkt[100];
+
+/* reference internal lwip variable in netif.c */
+
+static err_t
+test_netif_linkoutput(struct netif *netif, struct pbuf *p)
+{
+ fail_unless(netif == &test_netif);
+ fail_unless(p != NULL);
+ linkoutput_ctr++;
+ linkoutput_byte_ctr += p->tot_len;
+ /* Copy start of packet into buffer */
+ linkoutput_pkt_len = pbuf_copy_partial(p, linkoutput_pkt, sizeof(linkoutput_pkt), 0);
+ return ERR_OK;
+}
+
+static err_t
+test_netif_init(struct netif *netif)
+{
+ fail_unless(netif != NULL);
+ netif->linkoutput = test_netif_linkoutput;
+ netif->output = etharp_output;
+ netif->mtu = 1500;
+ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
+ netif->hwaddr_len = ETHARP_HWADDR_LEN;
+ return ERR_OK;
+}
+
+static void
+test_netif_add(void)
+{
+ IP4_ADDR(&test_gw, 192,168,0,1);
+ IP4_ADDR(&test_ipaddr, 192,168,0,1);
+ IP4_ADDR(&test_netmask, 255,255,0,0);
+
+ fail_unless(netif_default == NULL);
+ netif_add(&test_netif, &test_ipaddr, &test_netmask, &test_gw,
+ NULL, test_netif_init, NULL);
+ netif_set_default(&test_netif);
+ netif_set_up(&test_netif);
+}
+
+static void
+test_netif_remove(void)
+{
+ if (netif_default == &test_netif) {
+ netif_remove(&test_netif);
+ }
+}
+
+/* Helper functions */
+static void
+create_ip4_input_fragment(u16_t ip_id, u16_t start, u16_t len, int last)
+{
+ struct pbuf *p;
+ struct netif *input_netif = netif_list; /* just use any netif */
+ fail_unless((start & 7) == 0);
+ fail_unless(((len & 7) == 0) || last);
+ fail_unless(input_netif != NULL);
+
+ p = pbuf_alloc(PBUF_RAW, len + sizeof(struct ip_hdr), PBUF_RAM);
+ fail_unless(p != NULL);
+ if (p != NULL) {
+ err_t err;
+ struct ip_hdr *iphdr = (struct ip_hdr *)p->payload;
+ IPH_VHL_SET(iphdr, 4, sizeof(struct ip_hdr) / 4);
+ IPH_TOS_SET(iphdr, 0);
+ IPH_LEN_SET(iphdr, lwip_htons(p->tot_len));
+ IPH_ID_SET(iphdr, lwip_htons(ip_id));
+ if (last) {
+ IPH_OFFSET_SET(iphdr, lwip_htons(start / 8));
+ } else {
+ IPH_OFFSET_SET(iphdr, lwip_htons((start / 8) | IP_MF));
+ }
+ IPH_TTL_SET(iphdr, 5);
+ IPH_PROTO_SET(iphdr, IP_PROTO_UDP);
+ IPH_CHKSUM_SET(iphdr, 0);
+ ip4_addr_copy(iphdr->src, *netif_ip4_addr(input_netif));
+ iphdr->src.addr = lwip_htonl(lwip_htonl(iphdr->src.addr) + 1);
+ ip4_addr_copy(iphdr->dest, *netif_ip4_addr(input_netif));
+ IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, sizeof(struct ip_hdr)));
+
+ err = ip4_input(p, input_netif);
+ if (err != ERR_OK) {
+ pbuf_free(p);
+ }
+ fail_unless(err == ERR_OK);
+ }
+}
+
+static err_t arpless_output(struct netif *netif, struct pbuf *p,
+ const ip4_addr_t *ipaddr) {
+ LWIP_UNUSED_ARG(ipaddr);
+ return netif->linkoutput(netif, p);
+}
+
+/* Setups/teardown functions */
+
+static void
+ip4_setup(void)
+{
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+ip4_teardown(void)
+{
+ if (netif_list->loop_first != NULL) {
+ pbuf_free(netif_list->loop_first);
+ netif_list->loop_first = NULL;
+ }
+ netif_list->loop_last = NULL;
+ /* poll until all memory is released... */
+ tcpip_thread_poll_one();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+ test_netif_remove();
+ netif_set_up(netif_get_loopif());
+}
+
+/* Test functions */
+START_TEST(test_ip4_frag)
+{
+ struct pbuf *data = pbuf_alloc(PBUF_IP, 8000, PBUF_RAM);
+ ip_addr_t peer_ip = IPADDR4_INIT_BYTES(192,168,0,5);
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ linkoutput_ctr = 0;
+
+ /* Verify that 8000 byte payload is split into six packets */
+ fail_unless(data != NULL);
+ test_netif_add();
+ test_netif.output = arpless_output;
+ err = ip4_output_if_src(data, &test_ipaddr, ip_2_ip4(&peer_ip),
+ 16, 0, IP_PROTO_UDP, &test_netif);
+ fail_unless(err == ERR_OK);
+ fail_unless(linkoutput_ctr == 6);
+ fail_unless(linkoutput_byte_ctr == (8000 + (6 * IP_HLEN)));
+ pbuf_free(data);
+ test_netif_remove();
+}
+END_TEST
+
+START_TEST(test_ip4_reass)
+{
+ const u16_t ip_id = 128;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&lwip_stats.mib2, 0, sizeof(lwip_stats.mib2));
+
+ create_ip4_input_fragment(ip_id, 8*200, 200, 1);
+ fail_unless(lwip_stats.ip_frag.recv == 1);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 0);
+
+ create_ip4_input_fragment(ip_id, 0*200, 200, 0);
+ fail_unless(lwip_stats.ip_frag.recv == 2);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 0);
+
+ create_ip4_input_fragment(ip_id, 1*200, 200, 0);
+ fail_unless(lwip_stats.ip_frag.recv == 3);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 0);
+
+ create_ip4_input_fragment(ip_id, 2*200, 200, 0);
+ fail_unless(lwip_stats.ip_frag.recv == 4);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 0);
+
+ create_ip4_input_fragment(ip_id, 3*200, 200, 0);
+ fail_unless(lwip_stats.ip_frag.recv == 5);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 0);
+
+ create_ip4_input_fragment(ip_id, 4*200, 200, 0);
+ fail_unless(lwip_stats.ip_frag.recv == 6);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 0);
+
+ create_ip4_input_fragment(ip_id, 7*200, 200, 0);
+ fail_unless(lwip_stats.ip_frag.recv == 7);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 0);
+
+ create_ip4_input_fragment(ip_id, 6*200, 200, 0);
+ fail_unless(lwip_stats.ip_frag.recv == 8);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 0);
+
+ create_ip4_input_fragment(ip_id, 5*200, 200, 0);
+ fail_unless(lwip_stats.ip_frag.recv == 9);
+ fail_unless(lwip_stats.ip_frag.err == 0);
+ fail_unless(lwip_stats.ip_frag.memerr == 0);
+ fail_unless(lwip_stats.ip_frag.drop == 0);
+ fail_unless(lwip_stats.mib2.ipreasmoks == 1);
+}
+END_TEST
+
+/* packets to 127.0.0.1 shall not be sent out to netif_default */
+START_TEST(test_127_0_0_1)
+{
+ ip4_addr_t localhost;
+ struct pbuf* p;
+ LWIP_UNUSED_ARG(_i);
+
+ linkoutput_ctr = 0;
+
+ test_netif_add();
+ netif_set_down(netif_get_loopif());
+
+ IP4_ADDR(&localhost, 127, 0, 0, 1);
+ p = pbuf_alloc(PBUF_IP, 10, PBUF_POOL);
+
+ if(ip4_output(p, netif_ip4_addr(netif_default), &localhost, 0, 0, IP_PROTO_UDP) != ERR_OK) {
+ pbuf_free(p);
+ }
+ fail_unless(linkoutput_ctr == 0);
+}
+END_TEST
+
+START_TEST(test_ip4addr_aton)
+{
+ ip4_addr_t ip_addr;
+
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(ip4addr_aton("192.168.0.1", &ip_addr) == 1);
+ fail_unless(ip4addr_aton("192.168.0.0001", &ip_addr) == 1);
+ fail_unless(ip4addr_aton("192.168.0.zzz", &ip_addr) == 0);
+ fail_unless(ip4addr_aton("192.168.1", &ip_addr) == 1);
+ fail_unless(ip4addr_aton("192.168.0xd3", &ip_addr) == 1);
+ fail_unless(ip4addr_aton("192.168.0xz5", &ip_addr) == 0);
+ fail_unless(ip4addr_aton("192.168.095", &ip_addr) == 0);
+}
+END_TEST
+
+/* Test for bug #59364 */
+START_TEST(test_ip4_icmp_replylen_short)
+{
+ /* IP packet to 192.168.0.1 using proto 0x22 and 1 byte payload */
+ const u8_t unknown_proto[] = {
+ 0x45, 0x00, 0x00, 0x15, 0xd4, 0x31, 0x00, 0x00, 0xff, 0x22,
+ 0x66, 0x41, 0xc0, 0xa8, 0x00, 0x02, 0xc0, 0xa8, 0x00, 0x01,
+ 0xaa };
+ struct pbuf *p;
+ const int icmp_len = IP_HLEN + sizeof(struct icmp_hdr);
+ LWIP_UNUSED_ARG(_i);
+
+ linkoutput_ctr = 0;
+
+ test_netif_add();
+ test_netif.output = arpless_output;
+ p = pbuf_alloc(PBUF_IP, sizeof(unknown_proto), PBUF_RAM);
+ pbuf_take(p, unknown_proto, sizeof(unknown_proto));
+ fail_unless(ip4_input(p, &test_netif) == ERR_OK);
+
+ fail_unless(linkoutput_ctr == 1);
+ /* Verify outgoing ICMP packet has no extra data */
+ fail_unless(linkoutput_pkt_len == icmp_len + sizeof(unknown_proto));
+ fail_if(memcmp(&linkoutput_pkt[icmp_len], unknown_proto, sizeof(unknown_proto)));
+}
+END_TEST
+
+START_TEST(test_ip4_icmp_replylen_first_8)
+{
+ /* IP packet to 192.168.0.1 using proto 0x22 and 11 bytes payload */
+ const u8_t unknown_proto[] = {
+ 0x45, 0x00, 0x00, 0x1f, 0xd4, 0x31, 0x00, 0x00, 0xff, 0x22,
+ 0x66, 0x37, 0xc0, 0xa8, 0x00, 0x02, 0xc0, 0xa8, 0x00, 0x01,
+ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9,
+ 0xaa };
+ struct pbuf *p;
+ const int icmp_len = IP_HLEN + sizeof(struct icmp_hdr);
+ const int unreach_len = IP_HLEN + 8;
+ LWIP_UNUSED_ARG(_i);
+
+ linkoutput_ctr = 0;
+
+ test_netif_add();
+ test_netif.output = arpless_output;
+ p = pbuf_alloc(PBUF_IP, sizeof(unknown_proto), PBUF_RAM);
+ pbuf_take(p, unknown_proto, sizeof(unknown_proto));
+ fail_unless(ip4_input(p, &test_netif) == ERR_OK);
+
+ fail_unless(linkoutput_ctr == 1);
+ fail_unless(linkoutput_pkt_len == icmp_len + unreach_len);
+ fail_if(memcmp(&linkoutput_pkt[icmp_len], unknown_proto, unreach_len));
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+ip4_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_ip4_frag),
+ TESTFUNC(test_ip4_reass),
+ TESTFUNC(test_127_0_0_1),
+ TESTFUNC(test_ip4addr_aton),
+ TESTFUNC(test_ip4_icmp_replylen_short),
+ TESTFUNC(test_ip4_icmp_replylen_first_8),
+ };
+ return create_suite("IPv4", tests, sizeof(tests)/sizeof(testfunc), ip4_setup, ip4_teardown);
+}
diff --git a/test/unit/ip4/test_ip4.h b/test/unit/ip4/test_ip4.h
new file mode 100644
index 00000000000..df84a69f1a2
--- /dev/null
+++ b/test/unit/ip4/test_ip4.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_IP4_H
+#define LWIP_HDR_TEST_IP4_H
+
+#include "../lwip_check.h"
+
+Suite* ip4_suite(void);
+
+#endif
diff --git a/test/unit/ip6/test_ip6.c b/test/unit/ip6/test_ip6.c
new file mode 100644
index 00000000000..43ffcf78eb9
--- /dev/null
+++ b/test/unit/ip6/test_ip6.c
@@ -0,0 +1,469 @@
+#include "test_ip6.h"
+
+#include "lwip/ethip6.h"
+#include "lwip/ip6.h"
+#include "lwip/icmp6.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/nd6.h"
+#include "lwip/stats.h"
+#include "lwip/prot/ethernet.h"
+#include "lwip/prot/ip.h"
+#include "lwip/prot/ip6.h"
+
+#include "lwip/tcpip.h"
+
+#if LWIP_IPV6 /* allow to build the unit tests without IPv6 support */
+
+static struct netif test_netif6;
+static int linkoutput_ctr;
+static int linkoutput_byte_ctr;
+
+static err_t
+default_netif_linkoutput(struct netif *netif, struct pbuf *p)
+{
+ fail_unless(netif == &test_netif6);
+ fail_unless(p != NULL);
+ linkoutput_ctr++;
+ linkoutput_byte_ctr += p->tot_len;
+ return ERR_OK;
+}
+
+static err_t
+default_netif_init(struct netif *netif)
+{
+ fail_unless(netif != NULL);
+ netif->linkoutput = default_netif_linkoutput;
+ netif->output_ip6 = ethip6_output;
+ netif->mtu = 1500;
+ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHERNET | NETIF_FLAG_MLD6;
+ netif->hwaddr_len = ETH_HWADDR_LEN;
+ return ERR_OK;
+}
+
+static void
+default_netif_add(void)
+{
+ struct netif *n;
+ fail_unless(netif_default == NULL);
+ n = netif_add_noaddr(&test_netif6, NULL, default_netif_init, NULL);
+ fail_unless(n == &test_netif6);
+ netif_set_default(&test_netif6);
+}
+
+static void
+default_netif_remove(void)
+{
+ fail_unless(netif_default == &test_netif6);
+ netif_remove(&test_netif6);
+}
+
+static void
+ip6_test_handle_timers(int count)
+{
+ int i;
+ for (i = 0; i < count; i++) {
+ nd6_tmr();
+ }
+}
+
+/* Setups/teardown functions */
+
+static void
+ip6_setup(void)
+{
+ default_netif_add();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+ip6_teardown(void)
+{
+ if (netif_list->loop_first != NULL) {
+ pbuf_free(netif_list->loop_first);
+ netif_list->loop_first = NULL;
+ }
+ netif_list->loop_last = NULL;
+ /* poll until all memory is released... */
+ tcpip_thread_poll_one();
+ default_netif_remove();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+
+/* Test functions */
+
+static void
+test_ip6_ll_addr_iter(int expected_ctr1, int expected_ctr2)
+{
+ fail_unless(linkoutput_ctr == 0);
+
+ /* test that nothing is sent with link uo but netif down */
+ netif_set_link_up(&test_netif6);
+ ip6_test_handle_timers(500);
+ fail_unless(linkoutput_ctr == 0);
+ netif_set_link_down(&test_netif6);
+ fail_unless(linkoutput_ctr == 0);
+
+ /* test that nothing is sent with link down but netif up */
+ netif_set_up(&test_netif6);
+ ip6_test_handle_timers(500);
+ fail_unless(linkoutput_ctr == 0);
+ netif_set_down(&test_netif6);
+ fail_unless(linkoutput_ctr == 0);
+
+ /* test what is sent with link up + netif up */
+ netif_set_link_up(&test_netif6);
+ netif_set_up(&test_netif6);
+ ip6_test_handle_timers(500);
+ fail_unless(linkoutput_ctr == expected_ctr1);
+ netif_set_down(&test_netif6);
+ netif_set_link_down(&test_netif6);
+ fail_unless(linkoutput_ctr == expected_ctr1);
+ linkoutput_ctr = 0;
+
+ netif_set_up(&test_netif6);
+ netif_set_link_up(&test_netif6);
+ ip6_test_handle_timers(500);
+ fail_unless(linkoutput_ctr == expected_ctr2);
+ netif_set_link_down(&test_netif6);
+ netif_set_down(&test_netif6);
+ fail_unless(linkoutput_ctr == expected_ctr2);
+ linkoutput_ctr = 0;
+}
+
+START_TEST(test_ip6_ll_addr)
+{
+ LWIP_UNUSED_ARG(_i);
+
+ linkoutput_ctr = 0;
+
+ /* test without link-local address */
+ test_ip6_ll_addr_iter(0, 0);
+
+ /* test with link-local address */
+ netif_create_ip6_linklocal_address(&test_netif6, 1);
+ test_ip6_ll_addr_iter(3 + LWIP_IPV6_DUP_DETECT_ATTEMPTS + LWIP_IPV6_MLD, 3);
+}
+END_TEST
+
+START_TEST(test_ip6_aton_ipv4mapped)
+{
+ int ret;
+ ip_addr_t addr;
+ ip6_addr_t addr6;
+ const ip_addr_t addr_expected = IPADDR6_INIT_HOST(0, 0, 0xFFFF, 0xD4CC65D2);
+ const char *full_ipv6_addr = "0:0:0:0:0:FFFF:D4CC:65D2";
+ const char *shortened_ipv6_addr = "::FFFF:D4CC:65D2";
+ const char *full_ipv4_mapped_addr = "0:0:0:0:0:FFFF:212.204.101.210";
+ const char *shortened_ipv4_mapped_addr = "::FFFF:212.204.101.210";
+ const char *bogus_ipv4_mapped_addr = "::FFFF:212.204.101.2101";
+ LWIP_UNUSED_ARG(_i);
+
+ /* check IPv6 representation */
+ memset(&addr6, 0, sizeof(addr6));
+ ret = ip6addr_aton(full_ipv6_addr, &addr6);
+ fail_unless(ret == 1);
+ fail_unless(memcmp(&addr6, &addr_expected, 16) == 0);
+ memset(&addr, 0, sizeof(addr));
+ ret = ipaddr_aton(full_ipv6_addr, &addr);
+ fail_unless(ret == 1);
+ fail_unless(memcmp(&addr, &addr_expected, 16) == 0);
+
+ /* check shortened IPv6 representation */
+ memset(&addr6, 0, sizeof(addr6));
+ ret = ip6addr_aton(shortened_ipv6_addr, &addr6);
+ fail_unless(ret == 1);
+ fail_unless(memcmp(&addr6, &addr_expected, 16) == 0);
+ memset(&addr, 0, sizeof(addr));
+ ret = ipaddr_aton(shortened_ipv6_addr, &addr);
+ fail_unless(ret == 1);
+ fail_unless(memcmp(&addr, &addr_expected, 16) == 0);
+
+ /* checked shortened mixed representation */
+ memset(&addr6, 0, sizeof(addr6));
+ ret = ip6addr_aton(shortened_ipv4_mapped_addr, &addr6);
+ fail_unless(ret == 1);
+ fail_unless(memcmp(&addr6, &addr_expected, 16) == 0);
+ memset(&addr, 0, sizeof(addr));
+ ret = ipaddr_aton(shortened_ipv4_mapped_addr, &addr);
+ fail_unless(ret == 1);
+ fail_unless(memcmp(&addr, &addr_expected, 16) == 0);
+
+ /* checked mixed representation */
+ memset(&addr6, 0, sizeof(addr6));
+ ret = ip6addr_aton(full_ipv4_mapped_addr, &addr6);
+ fail_unless(ret == 1);
+ fail_unless(memcmp(&addr6, &addr_expected, 16) == 0);
+ memset(&addr, 0, sizeof(addr));
+ ret = ipaddr_aton(full_ipv4_mapped_addr, &addr);
+ fail_unless(ret == 1);
+ fail_unless(memcmp(&addr, &addr_expected, 16) == 0);
+
+ /* checked bogus mixed representation */
+ memset(&addr6, 0, sizeof(addr6));
+ ret = ip6addr_aton(bogus_ipv4_mapped_addr, &addr6);
+ fail_unless(ret == 0);
+ memset(&addr, 0, sizeof(addr));
+ ret = ipaddr_aton(bogus_ipv4_mapped_addr, &addr);
+ fail_unless(ret == 0);
+}
+END_TEST
+
+START_TEST(test_ip6_ntoa_ipv4mapped)
+{
+ const ip_addr_t addr = IPADDR6_INIT_HOST(0, 0, 0xFFFF, 0xD4CC65D2);
+ char buf[128];
+ char *str;
+ LWIP_UNUSED_ARG(_i);
+
+ str = ip6addr_ntoa_r(ip_2_ip6(&addr), buf, sizeof(buf));
+ fail_unless(str == buf);
+ fail_unless(!strcmp(str, "::FFFF:212.204.101.210"));
+}
+END_TEST
+
+struct test_addr_and_str {
+ ip_addr_t addr;
+ const char *str;
+};
+
+START_TEST(test_ip6_ntoa)
+{
+ struct test_addr_and_str tests[] = {
+ {IPADDR6_INIT_HOST(0xfe800000, 0x00000000, 0xb2a1a2ff, 0xfea3a4a5), "FE80::B2A1:A2FF:FEA3:A4A5"}, /* test shortened zeros */
+ {IPADDR6_INIT_HOST(0xfe800000, 0xff000000, 0xb2a1a2ff, 0xfea3a4a5), "FE80:0:FF00:0:B2A1:A2FF:FEA3:A4A5"}, /* don't omit single zero blocks */
+ {IPADDR6_INIT_HOST(0xfe800000, 0xff000000, 0xb2000000, 0x0000a4a5), "FE80:0:FF00:0:B200::A4A5"}, /* omit longest zero block */
+ };
+ char buf[128];
+ char *str;
+ size_t i;
+ LWIP_UNUSED_ARG(_i);
+
+ for (i = 0; i < LWIP_ARRAYSIZE(tests); i++) {
+ str = ip6addr_ntoa_r(ip_2_ip6(&tests[i].addr), buf, sizeof(buf));
+ fail_unless(str == buf);
+ fail_unless(!strcmp(str, tests[i].str));
+ }
+}
+END_TEST
+
+START_TEST(test_ip6_lladdr)
+{
+ u8_t zeros[128];
+ const u8_t test_mac_addr[6] = {0xb0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5};
+ const u32_t expected_ip6_addr_1[4] = {PP_HTONL(0xfe800000), 0, PP_HTONL(0xb2a1a2ff), PP_HTONL(0xfea3a4a5)};
+ const u32_t expected_ip6_addr_2[4] = {PP_HTONL(0xfe800000), 0, PP_HTONL(0x0000b0a1), PP_HTONL(0xa2a3a4a5)};
+ LWIP_UNUSED_ARG(_i);
+ memset(zeros, 0, sizeof(zeros));
+
+ fail_unless(test_netif6.hwaddr_len == 6);
+ fail_unless(!memcmp(test_netif6.hwaddr, zeros, 6));
+
+ fail_unless(test_netif6.ip6_addr_state[0] == 0);
+ fail_unless(!memcmp(netif_ip6_addr(&test_netif6, 0), zeros, sizeof(ip6_addr_t)));
+
+ /* set specific mac addr */
+ memcpy(test_netif6.hwaddr, test_mac_addr, 6);
+
+ /* create link-local addr based on mac (EUI-48) */
+ netif_create_ip6_linklocal_address(&test_netif6, 1);
+ fail_unless(IP_IS_V6(&test_netif6.ip6_addr[0]));
+ fail_unless(!memcmp(&netif_ip6_addr(&test_netif6, 0)->addr, expected_ip6_addr_1, 16));
+#if LWIP_IPV6_SCOPES
+ fail_unless(netif_ip6_addr(&test_netif6, 0)->zone == (test_netif6.num + 1));
+#endif
+ /* reset address */
+ memset(&test_netif6.ip6_addr[0], 0, sizeof(ip6_addr_t));
+ test_netif6.ip6_addr_state[0] = 0;
+
+ /* create link-local addr based interface ID */
+ netif_create_ip6_linklocal_address(&test_netif6, 0);
+ fail_unless(IP_IS_V6(&test_netif6.ip6_addr[0]));
+ fail_unless(!memcmp(&netif_ip6_addr(&test_netif6, 0)->addr, expected_ip6_addr_2, 16));
+#if LWIP_IPV6_SCOPES
+ fail_unless(netif_ip6_addr(&test_netif6, 0)->zone == (test_netif6.num + 1));
+#endif
+ /* reset address */
+ memset(&test_netif6.ip6_addr[0], 0, sizeof(ip6_addr_t));
+ test_netif6.ip6_addr_state[0] = 0;
+
+ /* reset mac address */
+ memset(&test_netif6.hwaddr, 0, sizeof(test_netif6.hwaddr));
+}
+END_TEST
+
+static struct pbuf *cloned_pbuf = NULL;
+static err_t clone_output(struct netif *netif, struct pbuf *p, const ip6_addr_t *addr) {
+ LWIP_UNUSED_ARG(netif);
+ LWIP_UNUSED_ARG(addr);
+ cloned_pbuf = pbuf_clone(PBUF_RAW, PBUF_RAM, p);
+ return ERR_OK;
+}
+
+/* Reproduces bug #58553 */
+START_TEST(test_ip6_dest_unreachable_chained_pbuf)
+{
+
+ ip_addr_t my_addr = IPADDR6_INIT_HOST(0x20010db8, 0x0, 0x0, 0x1);
+ ip_addr_t peer_addr = IPADDR6_INIT_HOST(0x20010db8, 0x0, 0x0, 0x4);
+ /* Create chained pbuf with UDP data that will get destination unreachable */
+ u8_t udp_hdr[] = {
+ 0x60, 0x00, 0x27, 0x03, 0x00, 0x2d, 0x11, 0x40,
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0xff, 0x03, 0xff, 0x00, 0x2d, 0x00, 0x00,
+ };
+ struct pbuf *header = pbuf_alloc(PBUF_RAW, sizeof(udp_hdr), PBUF_ROM);
+ u8_t udp_payload[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+ struct pbuf *data = pbuf_alloc(PBUF_RAW, sizeof(udp_payload), PBUF_ROM);
+ u8_t *icmpptr;
+ struct ip6_hdr *outhdr;
+ struct icmp6_hdr *icmp6hdr;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(header);
+ header->payload = udp_hdr;
+ fail_unless(data);
+ data->payload = udp_payload;
+ pbuf_cat(header, data);
+ data = NULL;
+
+ /* Configure and enable local address */
+ netif_set_up(&test_netif6);
+ netif_ip6_addr_set(&test_netif6, 0, ip_2_ip6(&my_addr));
+ netif_ip6_addr_set_state(&test_netif6, 0, IP6_ADDR_VALID);
+ test_netif6.output_ip6 = clone_output;
+
+ /* Process packet and send ICMPv6 reply for unreachable UDP port */
+ ip6_input(header, &test_netif6);
+ header = NULL;
+
+ /* Verify ICMP reply packet contents */
+ fail_unless(cloned_pbuf);
+ fail_unless(cloned_pbuf->len == IP6_HLEN + ICMP6_HLEN + sizeof(udp_hdr) + sizeof(udp_payload));
+ outhdr = (struct ip6_hdr*) cloned_pbuf->payload;
+ fail_unless(ip6_addr_packed_eq(ip_2_ip6(&my_addr), &outhdr->src, IP6_NO_ZONE));
+ fail_unless(ip6_addr_packed_eq(ip_2_ip6(&peer_addr), &outhdr->dest, IP6_NO_ZONE));
+ icmpptr = &((u8_t*)cloned_pbuf->payload)[IP6_HLEN];
+ icmp6hdr = (struct icmp6_hdr*) icmpptr;
+ fail_unless(icmp6hdr->type == ICMP6_TYPE_DUR);
+ fail_unless(icmp6hdr->code == ICMP6_DUR_PORT);
+ fail_unless(icmp6hdr->data == lwip_htonl(0));
+ icmpptr += ICMP6_HLEN;
+ fail_unless(memcmp(icmpptr, udp_hdr, sizeof(udp_hdr)) == 0, "mismatch in copied ip6/udp header");
+ icmpptr += sizeof(udp_hdr);
+ fail_unless(memcmp(icmpptr, udp_payload, sizeof(udp_payload)) == 0, "mismatch in copied udp payload");
+ pbuf_free(cloned_pbuf);
+}
+END_TEST
+
+/* Reproduces bug #57374 */
+START_TEST(test_ip6_frag_pbuf_len_assert)
+{
+ ip_addr_t my_addr = IPADDR6_INIT_HOST(0x20010db8, 0x0, 0x0, 0x1);
+ ip_addr_t peer_addr = IPADDR6_INIT_HOST(0x20010db8, 0x0, 0x0, 0x4);
+ struct pbuf *payload, *hdr;
+ err_t err;
+ int i;
+ LWIP_UNUSED_ARG(_i);
+
+ /* Configure and enable local address */
+ test_netif6.mtu = 1500;
+ netif_set_up(&test_netif6);
+ netif_ip6_addr_set(&test_netif6, 0, ip_2_ip6(&my_addr));
+ netif_ip6_addr_set_state(&test_netif6, 0, IP6_ADDR_VALID);
+
+ /* Create packet with lots of small pbufs around mtu limit */
+ payload = pbuf_alloc(PBUF_RAW, 1400, PBUF_POOL);
+ fail_unless(payload != NULL);
+ for (i = 0; i < 16; i++) {
+ struct pbuf *p = pbuf_alloc(PBUF_RAW, 32, PBUF_RAM);
+ fail_unless(p != NULL);
+ pbuf_cat(payload, p);
+ }
+ /* Prefix with header like UDP would */
+ hdr = pbuf_alloc(PBUF_IP, 8, PBUF_RAM);
+ fail_unless(hdr != NULL);
+ pbuf_chain(hdr, payload);
+
+ /* Send it and don't crash while fragmenting */
+ err = ip6_output_if_src(hdr, ip_2_ip6(&my_addr), ip_2_ip6(&peer_addr), 15, 0, IP_PROTO_UDP, &test_netif6);
+ fail_unless(err == ERR_OK);
+
+ pbuf_free(hdr);
+ pbuf_free(payload);
+}
+END_TEST
+
+static err_t direct_output(struct netif *netif, struct pbuf *p, const ip6_addr_t *addr) {
+ LWIP_UNUSED_ARG(addr);
+ return netif->linkoutput(netif, p);
+}
+
+START_TEST(test_ip6_frag)
+{
+ ip_addr_t my_addr = IPADDR6_INIT_HOST(0x20010db8, 0x0, 0x0, 0x1);
+ ip_addr_t peer_addr = IPADDR6_INIT_HOST(0x20010db8, 0x0, 0x0, 0x4);
+ struct pbuf *data;
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ /* Configure and enable local address */
+ test_netif6.mtu = 1500;
+ netif_set_up(&test_netif6);
+ netif_ip6_addr_set(&test_netif6, 0, ip_2_ip6(&my_addr));
+ netif_ip6_addr_set_state(&test_netif6, 0, IP6_ADDR_VALID);
+ test_netif6.output_ip6 = direct_output;
+ /* Reset counters after multicast traffic */
+ linkoutput_ctr = 0;
+ linkoutput_byte_ctr = 0;
+
+ /* Verify that 8000 byte payload is split into six packets */
+ data = pbuf_alloc(PBUF_IP, 8000, PBUF_RAM);
+ fail_unless(data != NULL);
+ err = ip6_output_if_src(data, ip_2_ip6(&my_addr), ip_2_ip6(&peer_addr),
+ 15, 0, IP_PROTO_UDP, &test_netif6);
+ fail_unless(err == ERR_OK);
+ fail_unless(linkoutput_ctr == 6);
+ fail_unless(linkoutput_byte_ctr == (8000 + (6 * (IP6_HLEN + IP6_FRAG_HLEN))));
+ pbuf_free(data);
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+ip6_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_ip6_ll_addr),
+ TESTFUNC(test_ip6_aton_ipv4mapped),
+ TESTFUNC(test_ip6_ntoa_ipv4mapped),
+ TESTFUNC(test_ip6_ntoa),
+ TESTFUNC(test_ip6_lladdr),
+ TESTFUNC(test_ip6_dest_unreachable_chained_pbuf),
+ TESTFUNC(test_ip6_frag_pbuf_len_assert),
+ TESTFUNC(test_ip6_frag)
+ };
+ return create_suite("IPv6", tests, sizeof(tests)/sizeof(testfunc), ip6_setup, ip6_teardown);
+}
+
+#else /* LWIP_IPV6 */
+
+/* allow to build the unit tests without IPv6 support */
+START_TEST(test_ip6_dummy)
+{
+ LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+Suite *
+ip6_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_ip6_dummy),
+ };
+ return create_suite("IPv6", tests, sizeof(tests)/sizeof(testfunc), NULL, NULL);
+}
+#endif /* LWIP_IPV6 */
diff --git a/test/unit/ip6/test_ip6.h b/test/unit/ip6/test_ip6.h
new file mode 100644
index 00000000000..a09345b380b
--- /dev/null
+++ b/test/unit/ip6/test_ip6.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_IP6_H
+#define LWIP_HDR_TEST_IP6_H
+
+#include "../lwip_check.h"
+
+Suite* ip6_suite(void);
+
+#endif
diff --git a/test/unit/lwip_check.h b/test/unit/lwip_check.h
new file mode 100644
index 00000000000..0246c59b622
--- /dev/null
+++ b/test/unit/lwip_check.h
@@ -0,0 +1,52 @@
+#ifndef LWIP_HDR_LWIP_CHECK_H
+#define LWIP_HDR_LWIP_CHECK_H
+
+/* Common header file for lwIP unit tests using the check framework */
+
+#include <config.h>
+#include <check.h>
+#include <stdlib.h>
+
+#define FAIL_RET() do { fail(); return; } while(0)
+#define EXPECT(x) fail_unless(x)
+#define EXPECT_RET(x) do { fail_unless(x); if(!(x)) { return; }} while(0)
+#define EXPECT_RETX(x, y) do { fail_unless(x); if(!(x)) { return y; }} while(0)
+#define EXPECT_RETNULL(x) EXPECT_RETX(x, NULL)
+
+#if (CHECK_MAJOR_VERSION == 0 && CHECK_MINOR_VERSION < 13)
+typedef struct {
+ TFun func;
+ const char *name;
+} testfunc;
+
+#define TESTFUNC(x) {(x), "" # x "" }
+
+/* Modified function from check.h, supplying function name */
+#define tcase_add_named_test(tc,tf) \
+ _tcase_add_test((tc),(tf).func,(tf).name,0, 0, 0, 1)
+
+#else
+/* From 0.13.0 check keeps track of the method name internally */
+typedef const TTest * testfunc;
+
+#define TESTFUNC(x) x
+
+#define tcase_add_named_test(tc,tf) tcase_add_test(tc,tf)
+#endif
+
+/** typedef for a function returning a test suite */
+typedef Suite* (suite_getter_fn)(void);
+
+/** Create a test suite */
+Suite* create_suite(const char* name, testfunc *tests, size_t num_tests, SFun setup, SFun teardown);
+
+#ifdef LWIP_UNITTESTS_LIB
+int lwip_unittests_run(void)
+#endif
+
+/* helper functions */
+#define SKIP_POOL(x) (1 << x)
+#define SKIP_HEAP (1 << MEMP_MAX)
+void lwip_check_ensure_no_alloc(unsigned int skip);
+
+#endif /* LWIP_HDR_LWIP_CHECK_H */
diff --git a/test/unit/lwip_unittests.c b/test/unit/lwip_unittests.c
new file mode 100644
index 00000000000..8d47e45ce4d
--- /dev/null
+++ b/test/unit/lwip_unittests.c
@@ -0,0 +1,125 @@
+#include "lwip_check.h"
+
+#include "ip4/test_ip4.h"
+#include "ip6/test_ip6.h"
+#include "udp/test_udp.h"
+#include "tcp/test_tcp.h"
+#include "tcp/test_tcp_oos.h"
+#include "tcp/test_tcp_state.h"
+#include "core/test_def.h"
+#include "core/test_dns.h"
+#include "core/test_mem.h"
+#include "core/test_netif.h"
+#include "core/test_pbuf.h"
+#include "core/test_timers.h"
+#include "etharp/test_etharp.h"
+#include "dhcp/test_dhcp.h"
+#include "mdns/test_mdns.h"
+#include "mqtt/test_mqtt.h"
+#include "api/test_sockets.h"
+#include "ppp/test_pppos.h"
+
+#include "lwip/init.h"
+#if !NO_SYS
+#include "lwip/tcpip.h"
+#endif
+
+/* This function is used for LWIP_RAND by some ports... */
+unsigned int
+lwip_port_rand(void)
+{
+ return (unsigned int)rand();
+}
+
+Suite* create_suite(const char* name, testfunc *tests, size_t num_tests, SFun setup, SFun teardown)
+{
+ size_t i;
+ Suite *s = suite_create(name);
+
+ for(i = 0; i < num_tests; i++) {
+ TCase *tc_core = tcase_create(name);
+ if ((setup != NULL) || (teardown != NULL)) {
+ tcase_add_checked_fixture(tc_core, setup, teardown);
+ }
+ tcase_add_named_test(tc_core, tests[i]);
+ suite_add_tcase(s, tc_core);
+ }
+ return s;
+}
+
+void lwip_check_ensure_no_alloc(unsigned int skip)
+{
+ int i;
+ unsigned int mask;
+
+ if (!(skip & SKIP_HEAP)) {
+ fail_unless(lwip_stats.mem.used == 0,
+ "mem heap still has %d bytes allocated", lwip_stats.mem.used);
+ }
+ for (i = 0, mask = 1; i < MEMP_MAX; i++, mask <<= 1) {
+ if (!(skip & mask)) {
+ fail_unless(lwip_stats.memp[i]->used == 0,
+ "memp pool '%s' still has %d entries allocated",
+ lwip_stats.memp[i]->name, lwip_stats.memp[i]->used);
+ }
+ }
+}
+
+#ifdef LWIP_UNITTESTS_LIB
+int lwip_unittests_run(void)
+#else
+int main(void)
+#endif
+{
+ int number_failed;
+ SRunner *sr;
+ size_t i;
+ suite_getter_fn* suites[] = {
+ ip4_suite,
+ ip6_suite,
+ udp_suite,
+ tcp_suite,
+ tcp_oos_suite,
+ tcp_state_suite,
+ def_suite,
+ dns_suite,
+ mem_suite,
+ netif_suite,
+ pbuf_suite,
+ timers_suite,
+ etharp_suite,
+ dhcp_suite,
+ mdns_suite,
+ mqtt_suite,
+ sockets_suite
+#if PPP_SUPPORT && PPPOS_SUPPORT
+ , pppos_suite
+#endif /* PPP_SUPPORT && PPPOS_SUPPORT */
+ };
+ size_t num = sizeof(suites)/sizeof(void*);
+ LWIP_ASSERT("No suites defined", num > 0);
+
+#if NO_SYS
+ lwip_init();
+#else
+ tcpip_init(NULL, NULL);
+#endif
+
+ sr = srunner_create((suites[0])());
+ srunner_set_xml(sr, "lwip_unittests.xml");
+ for(i = 1; i < num; i++) {
+ srunner_add_suite(sr, ((suite_getter_fn*)suites[i])());
+ }
+
+#ifdef LWIP_UNITTESTS_NOFORK
+ srunner_set_fork_status(sr, CK_NOFORK);
+#endif
+#ifdef LWIP_UNITTESTS_FORK
+ srunner_set_fork_status(sr, CK_FORK);
+#endif
+
+ srunner_run_all(sr, CK_NORMAL);
+ number_failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/test/unit/lwipopts.h b/test/unit/lwipopts.h
new file mode 100644
index 00000000000..0ee09dffd41
--- /dev/null
+++ b/test/unit/lwipopts.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Simon Goldschmidt
+ *
+ */
+#ifndef LWIP_HDR_LWIPOPTS_H
+#define LWIP_HDR_LWIPOPTS_H
+
+#define LWIP_TESTMODE 1
+
+#define LWIP_IPV6 1
+
+#define LWIP_CHECKSUM_ON_COPY 1
+#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK 1
+#define TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL(printfmsg) LWIP_ASSERT("TCP_CHECKSUM_ON_COPY_SANITY_CHECK_FAIL", 0)
+
+/* We link to special sys_arch.c (for basic non-waiting API layers unit tests) */
+#define NO_SYS 0
+#define SYS_LIGHTWEIGHT_PROT 0
+#define LWIP_NETCONN !NO_SYS
+#define LWIP_SOCKET !NO_SYS
+#define LWIP_NETCONN_FULLDUPLEX LWIP_SOCKET
+#define LWIP_NETCONN_SEM_PER_THREAD 1
+#define LWIP_NETBUF_RECVINFO 1
+#define LWIP_HAVE_LOOPIF 1
+#define TCPIP_THREAD_TEST
+
+/* Enable DHCP to test it */
+#define LWIP_DHCP 1
+
+/* Enable DNS, with random source port to avoid alloc in dns_init */
+#define LWIP_DNS 1
+#define LWIP_DNS_SECURE (LWIP_DNS_SECURE_RAND_XID | LWIP_DNS_SECURE_RAND_SRC_PORT)
+
+/* Minimal changes to opt.h required for tcp unit tests: */
+#define MEM_SIZE 16000
+#define TCP_SND_QUEUELEN 40
+#define MEMP_NUM_TCP_SEG TCP_SND_QUEUELEN
+#define TCP_SND_BUF (12 * TCP_MSS)
+#define TCP_WND (10 * TCP_MSS)
+#define LWIP_WND_SCALE 1
+#define TCP_RCV_SCALE 0
+#define PBUF_POOL_SIZE 400 /* pbuf tests need ~200KByte */
+
+/* Enable IGMP and MDNS for MDNS tests */
+#define LWIP_IGMP 1
+#define LWIP_MDNS_RESPONDER 1
+#define LWIP_NUM_NETIF_CLIENT_DATA (LWIP_MDNS_RESPONDER)
+
+/* Enable PPP and PPPOS support for PPPOS test suites */
+#define PPP_SUPPORT 1
+#define PPPOS_SUPPORT 1
+
+/* Minimal changes to opt.h required for etharp unit tests: */
+#define ETHARP_SUPPORT_STATIC_ENTRIES 1
+
+#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + 8)
+
+/* MIB2 stats are required to check IPv4 reassembly results */
+#define MIB2_STATS 1
+
+/* netif tests want to test this, so enable: */
+#define LWIP_NETIF_EXT_STATUS_CALLBACK 1
+
+/* Check lwip_stats.mem.illegal instead of asserting */
+#define LWIP_MEM_ILLEGAL_FREE(msg) /* to nothing */
+
+#endif /* LWIP_HDR_LWIPOPTS_H */
diff --git a/test/unit/mdns/test_mdns.c b/test/unit/mdns/test_mdns.c
new file mode 100644
index 00000000000..01434b5d73c
--- /dev/null
+++ b/test/unit/mdns/test_mdns.c
@@ -0,0 +1,916 @@
+/*
+ * Copyright (c) 2015 Verisure Innovation AB
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Erik Ekman <erik@kryo.se>
+ *
+ */
+
+#include "test_mdns.h"
+
+#include "lwip/pbuf.h"
+#include "lwip/apps/mdns.h"
+#include "lwip/apps/mdns_domain.h"
+#include "lwip/apps/mdns_priv.h"
+
+START_TEST(readname_basic)
+{
+ static const u8_t data[] = { 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0x00 };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0, &domain);
+ pbuf_free(p);
+ fail_unless(offset == sizeof(data));
+ fail_unless(domain.length == sizeof(data));
+ fail_if(memcmp(&domain.name, data, sizeof(data)));
+}
+END_TEST
+
+START_TEST(readname_anydata)
+{
+ static const u8_t data[] = { 0x05, 0x00, 0xFF, 0x08, 0xc0, 0x0f, 0x04, 0x7f, 0x80, 0x82, 0x88, 0x00 };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0, &domain);
+ pbuf_free(p);
+ fail_unless(offset == sizeof(data));
+ fail_unless(domain.length == sizeof(data));
+ fail_if(memcmp(&domain.name, data, sizeof(data)));
+}
+END_TEST
+
+START_TEST(readname_short_buf)
+{
+ static const u8_t data[] = { 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a' };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0, &domain);
+ pbuf_free(p);
+ fail_unless(offset == MDNS_READNAME_ERROR);
+}
+END_TEST
+
+START_TEST(readname_long_label)
+{
+ static const u8_t data[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i',
+ 0x52, 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
+ 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
+ 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
+ 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
+ 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a',
+ 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0, &domain);
+ pbuf_free(p);
+ fail_unless(offset == MDNS_READNAME_ERROR);
+}
+END_TEST
+
+START_TEST(readname_overflow)
+{
+ static const u8_t data[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0, &domain);
+ pbuf_free(p);
+ fail_unless(offset == MDNS_READNAME_ERROR);
+}
+END_TEST
+
+START_TEST(readname_jump_earlier)
+{
+ static const u8_t data[] = {
+ /* Some padding needed, not supported to jump to bytes containing dns header */
+ /* 0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 10 */ 0x0f, 0x0e, 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, 0xab,
+ /* 20 */ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0xc0, 0x0c
+ };
+ static const u8_t fullname[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 20, &domain);
+ pbuf_free(p);
+ fail_unless(offset == sizeof(data));
+ fail_unless(domain.length == sizeof(fullname));
+
+ fail_if(memcmp(&domain.name, fullname, sizeof(fullname)));
+}
+END_TEST
+
+START_TEST(readname_jump_earlier_jump)
+{
+ static const u8_t data[] = {
+ /* Some padding needed, not supported to jump to bytes containing dns header */
+ /* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x03, 0x0b, 0x0a, 0xf2,
+ /* 0x10 */ 0x04, 'c', 'a', 's', 't', 0x00, 0xc0, 0x10,
+ /* 0x18 */ 0x05, 'm', 'u', 'l', 't', 'i', 0xc0, 0x16
+ };
+ static const u8_t fullname[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0x18, &domain);
+ pbuf_free(p);
+ fail_unless(offset == sizeof(data));
+ fail_unless(domain.length == sizeof(fullname));
+
+ fail_if(memcmp(&domain.name, fullname, sizeof(fullname)));
+}
+END_TEST
+
+START_TEST(readname_jump_maxdepth)
+{
+ static const u8_t data[] = {
+ /* Some padding needed, not supported to jump to bytes containing dns header */
+ /* 0x00 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 0x08 */ 0x00, 0x00, 0x00, 0x00, 0x03, 0x0b, 0x0a, 0xf2,
+ /* 0x10 */ 0x04, 'n', 'a', 'm', 'e', 0xc0, 0x27, 0x03,
+ /* 0x18 */ 0x03, 'd', 'n', 's', 0xc0, 0x10, 0xc0, 0x10,
+ /* 0x20 */ 0x04, 'd', 'e', 'e', 'p', 0xc0, 0x18, 0x00,
+ /* 0x28 */ 0x04, 'c', 'a', 's', 't', 0xc0, 0x20, 0xb0,
+ /* 0x30 */ 0x05, 'm', 'u', 'l', 't', 'i', 0xc0, 0x28
+ };
+ static const u8_t fullname[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't',
+ 0x04, 'd', 'e', 'e', 'p', 0x03, 'd', 'n', 's',
+ 0x04, 'n', 'a', 'm', 'e', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0x30, &domain);
+ pbuf_free(p);
+ fail_unless(offset == sizeof(data));
+ fail_unless(domain.length == sizeof(fullname));
+
+ fail_if(memcmp(&domain.name, fullname, sizeof(fullname)));
+}
+END_TEST
+
+START_TEST(readname_jump_later)
+{
+ static const u8_t data[] = {
+ /* 0x00 */ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0xc0, 0x10, 0x00, 0x01, 0x40,
+ /* 0x10 */ 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, 0xab
+ };
+ static const u8_t fullname[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0, &domain);
+ pbuf_free(p);
+ fail_unless(offset == 13);
+ fail_unless(domain.length == sizeof(fullname));
+
+ fail_if(memcmp(&domain.name, fullname, sizeof(fullname)));
+}
+END_TEST
+
+START_TEST(readname_half_jump)
+{
+ static const u8_t data[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0xc0
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0, &domain);
+ pbuf_free(p);
+ fail_unless(offset == MDNS_READNAME_ERROR);
+}
+END_TEST
+
+START_TEST(readname_jump_toolong)
+{
+ static const u8_t data[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0xc2, 0x10, 0x00, 0x01, 0x40
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 0, &domain);
+ pbuf_free(p);
+ fail_unless(offset == MDNS_READNAME_ERROR);
+}
+END_TEST
+
+START_TEST(readname_jump_loop_label)
+{
+ static const u8_t data[] = {
+ /* 0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 10 */ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0xc0, 0x10
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 10, &domain);
+ pbuf_free(p);
+ fail_unless(offset == MDNS_READNAME_ERROR);
+}
+END_TEST
+
+START_TEST(readname_jump_loop_jump)
+{
+ static const u8_t data[] = {
+ /* 0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* 10 */ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0xc0, 0x15
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+ offset = mdns_readname(p, 10, &domain);
+ pbuf_free(p);
+ fail_unless(offset == MDNS_READNAME_ERROR);
+}
+END_TEST
+
+START_TEST(add_label_basic)
+{
+ static const u8_t data[] = { 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0x00 };
+ struct mdns_domain domain;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "cast", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == sizeof(data));
+ fail_if(memcmp(&domain.name, data, sizeof(data)));
+}
+END_TEST
+
+START_TEST(add_label_long_label)
+{
+ static const char *toolong = "abcdefghijklmnopqrstuvwxyz0123456789-abcdefghijklmnopqrstuvwxyz0123456789-abcdefghijklmnopqrstuvwxyz0123456789-";
+ struct mdns_domain domain;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, toolong, (u8_t)strlen(toolong));
+ fail_unless(res == ERR_VAL);
+}
+END_TEST
+
+START_TEST(add_label_full)
+{
+ static const char *label = "0123456789abcdef0123456789abcdef";
+ struct mdns_domain domain;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, label, (u8_t)strlen(label));
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 33);
+ res = mdns_domain_add_label(&domain, label, (u8_t)strlen(label));
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 66);
+ res = mdns_domain_add_label(&domain, label, (u8_t)strlen(label));
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 99);
+ res = mdns_domain_add_label(&domain, label, (u8_t)strlen(label));
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 132);
+ res = mdns_domain_add_label(&domain, label, (u8_t)strlen(label));
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 165);
+ res = mdns_domain_add_label(&domain, label, (u8_t)strlen(label));
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 198);
+ res = mdns_domain_add_label(&domain, label, (u8_t)strlen(label));
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 231);
+ res = mdns_domain_add_label(&domain, label, (u8_t)strlen(label));
+ fail_unless(res == ERR_VAL);
+ fail_unless(domain.length == 231);
+ res = mdns_domain_add_label(&domain, label, 25);
+ fail_unless(res == ERR_VAL);
+ fail_unless(domain.length == 231);
+ res = mdns_domain_add_label(&domain, label, 24);
+ fail_unless(res == ERR_VAL);
+ fail_unless(domain.length == 231);
+ res = mdns_domain_add_label(&domain, label, 23);
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 255);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+ fail_unless(domain.length == 256);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_VAL);
+ fail_unless(domain.length == 256);
+}
+END_TEST
+
+START_TEST(domain_eq_basic)
+{
+ static const u8_t data[] = {
+ 0x05, 'm', 'u', 'l', 't', 'i', 0x04, 'c', 'a', 's', 't', 0x00
+ };
+ struct mdns_domain domain1, domain2;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&domain1, 0, sizeof(domain1));
+ res = mdns_domain_add_label(&domain1, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, "cast", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, NULL, 0);
+ fail_unless(res == ERR_OK);
+ fail_unless(domain1.length == sizeof(data));
+
+ memset(&domain2, 0, sizeof(domain2));
+ res = mdns_domain_add_label(&domain2, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, "cast", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ fail_unless(mdns_domain_eq(&domain1, &domain2));
+}
+END_TEST
+
+START_TEST(domain_eq_diff)
+{
+ struct mdns_domain domain1, domain2;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&domain1, 0, sizeof(domain1));
+ res = mdns_domain_add_label(&domain1, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, "base", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ memset(&domain2, 0, sizeof(domain2));
+ res = mdns_domain_add_label(&domain2, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, "cast", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ fail_if(mdns_domain_eq(&domain1, &domain2));
+}
+END_TEST
+
+START_TEST(domain_eq_case)
+{
+ struct mdns_domain domain1, domain2;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&domain1, 0, sizeof(domain1));
+ res = mdns_domain_add_label(&domain1, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, "cast", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ memset(&domain2, 0, sizeof(domain2));
+ res = mdns_domain_add_label(&domain2, "MulTI", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, "casT", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ fail_unless(mdns_domain_eq(&domain1, &domain2));
+}
+END_TEST
+
+START_TEST(domain_eq_anydata)
+{
+ static const u8_t data1[] = { 0x05, 0xcc, 0xdc, 0x00, 0xa0 };
+ static const u8_t data2[] = { 0x7f, 0x8c, 0x01, 0xff, 0xcf };
+ struct mdns_domain domain1, domain2;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&domain1, 0, sizeof(domain1));
+ res = mdns_domain_add_label(&domain1, (const char*)data1, sizeof(data1));
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, "cast", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, (const char*)data2, sizeof(data2));
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ memset(&domain2, 0, sizeof(domain2));
+ res = mdns_domain_add_label(&domain2, (const char*)data1, sizeof(data1));
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, "casT", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, (const char*)data2, sizeof(data2));
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ fail_unless(mdns_domain_eq(&domain1, &domain2));
+}
+END_TEST
+
+START_TEST(domain_eq_length)
+{
+ struct mdns_domain domain1, domain2;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&domain1, 0, sizeof(domain1));
+ memset(domain1.name, 0xAA, sizeof(MDNS_DOMAIN_MAXLEN));
+ res = mdns_domain_add_label(&domain1, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain1, "cast", 4);
+ fail_unless(res == ERR_OK);
+
+ memset(&domain2, 0, sizeof(domain2));
+ memset(domain2.name, 0xBB, sizeof(MDNS_DOMAIN_MAXLEN));
+ res = mdns_domain_add_label(&domain2, "multi", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain2, "cast", 4);
+ fail_unless(res == ERR_OK);
+
+ fail_unless(mdns_domain_eq(&domain1, &domain2));
+}
+END_TEST
+
+START_TEST(compress_full_match)
+{
+ static const u8_t data[] = {
+ 0x00, 0x00,
+ 0x06, 'f', 'o', 'o', 'b', 'a', 'r', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ u16_t length;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "foobar", 6);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "local", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ offset = 2;
+ length = mdns_compress_domain(p, &offset, &domain);
+ /* Write 0 bytes, then a jump to addr 2 */
+ fail_unless(length == 0);
+ fail_unless(offset == 2);
+
+ pbuf_free(p);
+}
+END_TEST
+
+START_TEST(compress_full_match_subset)
+{
+ static const u8_t data[] = {
+ 0x00, 0x00,
+ 0x02, 'g', 'o', 0x06, 'f', 'o', 'o', 'b', 'a', 'r', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ u16_t length;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "foobar", 6);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "local", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ offset = 2;
+ length = mdns_compress_domain(p, &offset, &domain);
+ /* Write 0 bytes, then a jump to addr 5 */
+ fail_unless(length == 0);
+ fail_unless(offset == 5);
+
+ pbuf_free(p);
+}
+END_TEST
+
+START_TEST(compress_full_match_jump)
+{
+ static const u8_t data[] = {
+ /* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ /* 0x10 */ 0x04, 'l', 'w', 'i', 'p', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, 0xc0, 0x00, 0x02, 0x00,
+ /* 0x20 */ 0x06, 'f', 'o', 'o', 'b', 'a', 'r', 0xc0, 0x15
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ u16_t length;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "foobar", 6);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "local", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ offset = 0x20;
+ length = mdns_compress_domain(p, &offset, &domain);
+ /* Write 0 bytes, then a jump to addr 0x20 */
+ fail_unless(length == 0);
+ fail_unless(offset == 0x20);
+
+ pbuf_free(p);
+}
+END_TEST
+
+START_TEST(compress_no_match)
+{
+ static const u8_t data[] = {
+ 0x00, 0x00,
+ 0x04, 'l', 'w', 'i', 'p', 0x05, 'w', 'i', 'k', 'i', 'a', 0x03, 'c', 'o', 'm', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ u16_t length;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "foobar", 6);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "local", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ offset = 2;
+ length = mdns_compress_domain(p, &offset, &domain);
+ /* Write all bytes, no jump */
+ fail_unless(length == domain.length);
+
+ pbuf_free(p);
+}
+END_TEST
+
+START_TEST(compress_2nd_label)
+{
+ static const u8_t data[] = {
+ 0x00, 0x00,
+ 0x06, 'f', 'o', 'o', 'b', 'a', 'r', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ u16_t length;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "lwip", 4);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "local", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ offset = 2;
+ length = mdns_compress_domain(p, &offset, &domain);
+ /* Write 5 bytes, then a jump to addr 9 */
+ fail_unless(length == 5);
+ fail_unless(offset == 9);
+
+ pbuf_free(p);
+}
+END_TEST
+
+START_TEST(compress_2nd_label_short)
+{
+ static const u8_t data[] = {
+ 0x00, 0x00,
+ 0x04, 'l', 'w', 'i', 'p', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ u16_t length;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "foobar", 6);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "local", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ offset = 2;
+ length = mdns_compress_domain(p, &offset, &domain);
+ /* Write 5 bytes, then a jump to addr 7 */
+ fail_unless(length == 7);
+ fail_unless(offset == 7);
+
+ pbuf_free(p);
+}
+END_TEST
+
+START_TEST(compress_jump_to_jump)
+{
+ static const u8_t data[] = {
+ /* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ /* 0x10 */ 0x04, 'l', 'w', 'i', 'p', 0x05, 'l', 'o', 'c', 'a', 'l', 0x00, 0xc0, 0x00, 0x02, 0x00,
+ /* 0x20 */ 0x07, 'b', 'a', 'n', 'a', 'n', 'a', 's', 0xc0, 0x15
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ u16_t length;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "foobar", 6);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "local", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ offset = 0x20;
+ length = mdns_compress_domain(p, &offset, &domain);
+ /* Don't compress if jump would be to a jump */
+ fail_unless(length == domain.length);
+
+ offset = 0x10;
+ length = mdns_compress_domain(p, &offset, &domain);
+ /* Write 7 bytes, then a jump to addr 0x15 */
+ fail_unless(length == 7);
+ fail_unless(offset == 0x15);
+
+ pbuf_free(p);
+}
+END_TEST
+
+START_TEST(compress_long_match)
+{
+ static const u8_t data[] = {
+ 0x00, 0x00,
+ 0x06, 'f', 'o', 'o', 'b', 'a', 'r', 0x05, 'l', 'o', 'c', 'a', 'l', 0x03, 'c', 'o', 'm', 0x00
+ };
+ struct pbuf *p;
+ struct mdns_domain domain;
+ u16_t offset;
+ u16_t length;
+ err_t res;
+ LWIP_UNUSED_ARG(_i);
+
+ p = pbuf_alloc(PBUF_RAW, sizeof(data), PBUF_ROM);
+ fail_if(p == NULL);
+ p->payload = (void *)(size_t)data;
+
+ memset(&domain, 0, sizeof(domain));
+ res = mdns_domain_add_label(&domain, "foobar", 6);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, "local", 5);
+ fail_unless(res == ERR_OK);
+ res = mdns_domain_add_label(&domain, NULL, 0);
+ fail_unless(res == ERR_OK);
+
+ offset = 2;
+ length = mdns_compress_domain(p, &offset, &domain);
+ fail_unless(length == domain.length);
+
+ pbuf_free(p);
+}
+END_TEST
+
+Suite* mdns_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(readname_basic),
+ TESTFUNC(readname_anydata),
+ TESTFUNC(readname_short_buf),
+ TESTFUNC(readname_long_label),
+ TESTFUNC(readname_overflow),
+ TESTFUNC(readname_jump_earlier),
+ TESTFUNC(readname_jump_earlier_jump),
+ TESTFUNC(readname_jump_maxdepth),
+ TESTFUNC(readname_jump_later),
+ TESTFUNC(readname_half_jump),
+ TESTFUNC(readname_jump_toolong),
+ TESTFUNC(readname_jump_loop_label),
+ TESTFUNC(readname_jump_loop_jump),
+
+ TESTFUNC(add_label_basic),
+ TESTFUNC(add_label_long_label),
+ TESTFUNC(add_label_full),
+
+ TESTFUNC(domain_eq_basic),
+ TESTFUNC(domain_eq_diff),
+ TESTFUNC(domain_eq_case),
+ TESTFUNC(domain_eq_anydata),
+ TESTFUNC(domain_eq_length),
+
+ TESTFUNC(compress_full_match),
+ TESTFUNC(compress_full_match_subset),
+ TESTFUNC(compress_full_match_jump),
+ TESTFUNC(compress_no_match),
+ TESTFUNC(compress_2nd_label),
+ TESTFUNC(compress_2nd_label_short),
+ TESTFUNC(compress_jump_to_jump),
+ TESTFUNC(compress_long_match),
+ };
+ return create_suite("MDNS", tests, sizeof(tests)/sizeof(testfunc), NULL, NULL);
+}
diff --git a/test/unit/mdns/test_mdns.h b/test/unit/mdns/test_mdns.h
new file mode 100644
index 00000000000..c3df339140b
--- /dev/null
+++ b/test/unit/mdns/test_mdns.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_MDNS_H__
+#define LWIP_HDR_TEST_MDNS_H__
+
+#include "../lwip_check.h"
+
+Suite* mdns_suite(void);
+
+#endif
diff --git a/test/unit/mqtt/test_mqtt.c b/test/unit/mqtt/test_mqtt.c
new file mode 100644
index 00000000000..7cff13ea238
--- /dev/null
+++ b/test/unit/mqtt/test_mqtt.c
@@ -0,0 +1,115 @@
+#include "test_mqtt.h"
+
+#include "lwip/pbuf.h"
+#include "lwip/apps/mqtt.h"
+#include "lwip/apps/mqtt_priv.h"
+#include "lwip/netif.h"
+
+const ip_addr_t test_mqtt_local_ip = IPADDR4_INIT_BYTES(192, 168, 1, 1);
+const ip_addr_t test_mqtt_remote_ip = IPADDR4_INIT_BYTES(192, 168, 1, 2);
+const ip_addr_t test_mqtt_netmask = IPADDR4_INIT_BYTES(255, 255, 255, 0);
+
+static err_t test_mqtt_netif_output(struct netif *netif, struct pbuf *p,
+ const ip4_addr_t *ipaddr)
+{
+ LWIP_UNUSED_ARG(netif);
+ LWIP_UNUSED_ARG(ipaddr);
+ LWIP_UNUSED_ARG(p);
+ return ERR_OK;
+}
+
+static void
+test_mqtt_init_netif(struct netif *netif, const ip_addr_t *ip_addr, const ip_addr_t *netmask)
+{
+ struct netif *n;
+ memset(netif, 0, sizeof(struct netif));
+ netif->output = test_mqtt_netif_output;
+ netif->flags |= NETIF_FLAG_UP | NETIF_FLAG_LINK_UP;
+ ip_addr_copy_from_ip4(netif->netmask, *ip_2_ip4(netmask));
+ ip_addr_copy_from_ip4(netif->ip_addr, *ip_2_ip4(ip_addr));
+ for (n = netif_list; n != NULL; n = n->next) {
+ if (n == netif) {
+ return;
+ }
+ }
+ netif->next = NULL;
+ netif_list = netif;
+}
+
+/* Setups/teardown functions */
+static struct netif *old_netif_list;
+static struct netif *old_netif_default;
+
+static void
+mqtt_setup(void)
+{
+ old_netif_list = netif_list;
+ old_netif_default = netif_default;
+ netif_list = NULL;
+ netif_default = NULL;
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+mqtt_teardown(void)
+{
+ netif_list = NULL;
+ netif_default = NULL;
+ /* restore netif_list for next tests (e.g. loopif) */
+ netif_list = old_netif_list;
+ netif_default = old_netif_default;
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void test_mqtt_connection_cb(mqtt_client_t *client, void *arg, mqtt_connection_status_t status)
+{
+ LWIP_UNUSED_ARG(client);
+ LWIP_UNUSED_ARG(arg);
+ LWIP_UNUSED_ARG(status);
+}
+
+START_TEST(basic_connect)
+{
+ mqtt_client_t* client;
+ struct netif netif;
+ err_t err;
+ struct mqtt_connect_client_info_t client_info = {
+ "dumm",
+ NULL, NULL,
+ 10,
+ NULL, NULL, 0, 0
+ };
+ struct pbuf *p;
+ unsigned char rxbuf[] = {0x20, 0x02, 0x00, 0x00};
+ LWIP_UNUSED_ARG(_i);
+
+ test_mqtt_init_netif(&netif, &test_mqtt_local_ip, &test_mqtt_netmask);
+
+ client = mqtt_client_new();
+ fail_unless(client != NULL);
+ err = mqtt_client_connect(client, &test_mqtt_remote_ip, 1234, test_mqtt_connection_cb, NULL, &client_info);
+ fail_unless(err == ERR_OK);
+
+ client->conn->connected(client->conn->callback_arg, client->conn, ERR_OK);
+ p = pbuf_alloc(PBUF_RAW, sizeof(rxbuf), PBUF_REF);
+ fail_unless(p != NULL);
+ p->payload = rxbuf;
+ /* since we hack the rx path, we have to hack the rx window, too: */
+ client->conn->rcv_wnd -= p->tot_len;
+ if (client->conn->recv(client->conn->callback_arg, client->conn, p, ERR_OK) != ERR_OK) {
+ pbuf_free(p);
+ }
+
+ mqtt_disconnect(client);
+ /* fixme: mqtt_client_fre() is missing... */
+ mem_free(client);
+}
+END_TEST
+
+Suite* mqtt_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(basic_connect),
+ };
+ return create_suite("MQTT", tests, sizeof(tests)/sizeof(testfunc), mqtt_setup, mqtt_teardown);
+}
diff --git a/test/unit/mqtt/test_mqtt.h b/test/unit/mqtt/test_mqtt.h
new file mode 100644
index 00000000000..181758c4b3b
--- /dev/null
+++ b/test/unit/mqtt/test_mqtt.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_MQTT_H__
+#define LWIP_HDR_TEST_MQTT_H__
+
+#include "../lwip_check.h"
+
+Suite* mqtt_suite(void);
+
+#endif
diff --git a/test/unit/ppp/test_pppos.c b/test/unit/ppp/test_pppos.c
new file mode 100644
index 00000000000..2b9aee810f8
--- /dev/null
+++ b/test/unit/ppp/test_pppos.c
@@ -0,0 +1,67 @@
+#include "test_pppos.h"
+
+#include "lwip/netif.h"
+#include "netif/ppp/pppos.h"
+#include "netif/ppp/ppp.h"
+
+#if PPP_SUPPORT && PPPOS_SUPPORT
+static struct netif pppos_netif;
+static ppp_pcb *ppp;
+
+static u32_t ppp_output_cb(ppp_pcb *pcb, const void *data, u32_t len, void *ctx)
+{
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(data);
+ LWIP_UNUSED_ARG(len);
+ LWIP_UNUSED_ARG(ctx);
+
+ return 0;
+}
+
+static void ppp_link_status_cb(ppp_pcb *pcb, int err_code, void *ctx)
+{
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(err_code);
+ LWIP_UNUSED_ARG(ctx);
+}
+
+static void pppos_setup(void)
+{
+ ppp = pppos_create(&pppos_netif, ppp_output_cb, ppp_link_status_cb, NULL);
+ fail_if(ppp == NULL);
+ ppp_connect(ppp, 0);
+}
+
+static void pppos_teardown(void)
+{
+}
+
+START_TEST(test_pppos_empty_packet_with_valid_fcs)
+{
+ u8_t two_breaks[] = { 0x7e, 0, 0, 0x7e };
+ u8_t other_packet[] = { 0x7e, 0x7d, 0x20, 0x00, 0x7e };
+ /* Set internal states of the underlying pcb */
+ pppos_pcb *pppos = (pppos_pcb *)ppp->link_ctx_cb;
+
+ LWIP_UNUSED_ARG(_i);
+
+ pppos->open = 1; /* Pretend the connection is open already */
+ pppos->in_accm[0] = 0xf0; /* Make sure 0x0's are not escaped chars */
+
+ pppos_input(ppp, two_breaks, sizeof(two_breaks));
+ pppos_input(ppp, other_packet, sizeof(other_packet));
+
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+pppos_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_pppos_empty_packet_with_valid_fcs)
+ };
+ return create_suite("PPPOS", tests, sizeof(tests)/sizeof(testfunc), pppos_setup, pppos_teardown);
+}
+
+#endif /* PPP_SUPPORT && PPPOS_SUPPORT */
diff --git a/test/unit/ppp/test_pppos.h b/test/unit/ppp/test_pppos.h
new file mode 100644
index 00000000000..56b3b0cbb42
--- /dev/null
+++ b/test/unit/ppp/test_pppos.h
@@ -0,0 +1,13 @@
+#ifndef LWIP_HDR_TEST_PPPOS_H
+#define LWIP_HDR_TEST_PPPOS_H
+
+#include "../lwip_check.h"
+#include "netif/ppp/ppp.h"
+
+#if PPP_SUPPORT && PPPOS_SUPPORT
+
+Suite* pppos_suite(void);
+
+#endif /* PPP_SUPPORT && PPPOS_SUPPORT */
+
+#endif /* LWIP_HDR_TEST_PPPOS_H */
diff --git a/test/unit/tcp/tcp_helper.c b/test/unit/tcp/tcp_helper.c
new file mode 100644
index 00000000000..61d6e5625e1
--- /dev/null
+++ b/test/unit/tcp/tcp_helper.c
@@ -0,0 +1,325 @@
+#include "tcp_helper.h"
+
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/stats.h"
+#include "lwip/pbuf.h"
+#include "lwip/inet.h"
+#include "lwip/inet_chksum.h"
+#include "lwip/ip_addr.h"
+
+#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS
+#error "This tests needs TCP- and MEMP-statistics enabled"
+#endif
+
+const ip_addr_t test_local_ip = IPADDR4_INIT_BYTES(192, 168, 1, 1);
+const ip_addr_t test_remote_ip = IPADDR4_INIT_BYTES(192, 168, 1, 2);
+const ip_addr_t test_netmask = IPADDR4_INIT_BYTES(255, 255, 255, 0);
+
+/** Remove all pcbs on the given list. */
+static void
+tcp_remove(struct tcp_pcb* pcb_list)
+{
+ struct tcp_pcb *pcb = pcb_list;
+ struct tcp_pcb *pcb2;
+
+ while(pcb != NULL) {
+ pcb2 = pcb;
+ pcb = pcb->next;
+ if (pcb2->state == LISTEN) {
+ tcp_close(pcb2);
+ } else {
+ tcp_abort(pcb2);
+ }
+ }
+}
+
+/** Remove all pcbs on listen-, active- and time-wait-list (bound- isn't exported). */
+void
+tcp_remove_all(void)
+{
+ tcp_remove(tcp_listen_pcbs.pcbs);
+ tcp_remove(tcp_bound_pcbs);
+ tcp_remove(tcp_active_pcbs);
+ tcp_remove(tcp_tw_pcbs);
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 0);
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_SEG) == 0);
+ fail_unless(MEMP_STATS_GET(used, MEMP_PBUF_POOL) == 0);
+}
+
+/** Create a TCP segment usable for passing to tcp_input */
+static struct pbuf*
+tcp_create_segment_wnd(ip_addr_t* src_ip, ip_addr_t* dst_ip,
+ u16_t src_port, u16_t dst_port, void* data, size_t data_len,
+ u32_t seqno, u32_t ackno, u8_t headerflags, u16_t wnd)
+{
+ struct pbuf *p, *q;
+ struct ip_hdr* iphdr;
+ struct tcp_hdr* tcphdr;
+ u16_t pbuf_len = (u16_t)(sizeof(struct ip_hdr) + sizeof(struct tcp_hdr) + data_len);
+ LWIP_ASSERT("data_len too big", data_len <= 0xFFFF);
+
+ p = pbuf_alloc(PBUF_RAW, pbuf_len, PBUF_POOL);
+ EXPECT_RETNULL(p != NULL);
+ /* first pbuf must be big enough to hold the headers */
+ EXPECT_RETNULL(p->len >= (sizeof(struct ip_hdr) + sizeof(struct tcp_hdr)));
+ if (data_len > 0) {
+ /* first pbuf must be big enough to hold at least 1 data byte, too */
+ EXPECT_RETNULL(p->len > (sizeof(struct ip_hdr) + sizeof(struct tcp_hdr)));
+ }
+
+ for(q = p; q != NULL; q = q->next) {
+ memset(q->payload, 0, q->len);
+ }
+
+ iphdr = (struct ip_hdr*)p->payload;
+ /* fill IP header */
+ iphdr->dest.addr = ip_2_ip4(dst_ip)->addr;
+ iphdr->src.addr = ip_2_ip4(src_ip)->addr;
+ IPH_VHL_SET(iphdr, 4, IP_HLEN / 4);
+ IPH_TOS_SET(iphdr, 0);
+ IPH_LEN_SET(iphdr, htons(p->tot_len));
+ IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));
+
+ /* let p point to TCP header */
+ pbuf_header(p, -(s16_t)sizeof(struct ip_hdr));
+
+ tcphdr = (struct tcp_hdr*)p->payload;
+ tcphdr->src = htons(src_port);
+ tcphdr->dest = htons(dst_port);
+ tcphdr->seqno = htonl(seqno);
+ tcphdr->ackno = htonl(ackno);
+ TCPH_HDRLEN_SET(tcphdr, sizeof(struct tcp_hdr)/4);
+ TCPH_FLAGS_SET(tcphdr, headerflags);
+ tcphdr->wnd = htons(wnd);
+
+ if (data_len > 0) {
+ /* let p point to TCP data */
+ pbuf_header(p, -(s16_t)sizeof(struct tcp_hdr));
+ /* copy data */
+ pbuf_take(p, data, (u16_t)data_len);
+ /* let p point to TCP header again */
+ pbuf_header(p, sizeof(struct tcp_hdr));
+ }
+
+ /* calculate checksum */
+
+ tcphdr->chksum = ip_chksum_pseudo(p,
+ IP_PROTO_TCP, p->tot_len, src_ip, dst_ip);
+
+ pbuf_header(p, sizeof(struct ip_hdr));
+
+ return p;
+}
+
+/** Create a TCP segment usable for passing to tcp_input */
+struct pbuf*
+tcp_create_segment(ip_addr_t* src_ip, ip_addr_t* dst_ip,
+ u16_t src_port, u16_t dst_port, void* data, size_t data_len,
+ u32_t seqno, u32_t ackno, u8_t headerflags)
+{
+ return tcp_create_segment_wnd(src_ip, dst_ip, src_port, dst_port, data,
+ data_len, seqno, ackno, headerflags, TCP_WND);
+}
+
+/** Create a TCP segment usable for passing to tcp_input
+ * - IP-addresses, ports, seqno and ackno are taken from pcb
+ * - seqno and ackno can be altered with an offset
+ */
+struct pbuf*
+tcp_create_rx_segment(struct tcp_pcb* pcb, void* data, size_t data_len, u32_t seqno_offset,
+ u32_t ackno_offset, u8_t headerflags)
+{
+ return tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, pcb->local_port,
+ data, data_len, pcb->rcv_nxt + seqno_offset, pcb->lastack + ackno_offset, headerflags);
+}
+
+/** Create a TCP segment usable for passing to tcp_input
+ * - IP-addresses, ports, seqno and ackno are taken from pcb
+ * - seqno and ackno can be altered with an offset
+ * - TCP window can be adjusted
+ */
+struct pbuf* tcp_create_rx_segment_wnd(struct tcp_pcb* pcb, void* data, size_t data_len,
+ u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags, u16_t wnd)
+{
+ return tcp_create_segment_wnd(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, pcb->local_port,
+ data, data_len, pcb->rcv_nxt + seqno_offset, pcb->lastack + ackno_offset, headerflags, wnd);
+}
+
+/** Safely bring a tcp_pcb into the requested state */
+void
+tcp_set_state(struct tcp_pcb* pcb, enum tcp_state state, const ip_addr_t* local_ip,
+ const ip_addr_t* remote_ip, u16_t local_port, u16_t remote_port)
+{
+ u32_t iss;
+
+ /* @todo: are these all states? */
+ /* @todo: remove from previous list */
+ pcb->state = state;
+
+ iss = tcp_next_iss(pcb);
+ pcb->snd_wl2 = iss;
+ pcb->snd_nxt = iss;
+ pcb->lastack = iss;
+ pcb->snd_lbb = iss;
+
+ if (state == ESTABLISHED) {
+ TCP_REG(&tcp_active_pcbs, pcb);
+ ip_addr_copy(pcb->local_ip, *local_ip);
+ pcb->local_port = local_port;
+ ip_addr_copy(pcb->remote_ip, *remote_ip);
+ pcb->remote_port = remote_port;
+ } else if(state == LISTEN) {
+ TCP_REG(&tcp_listen_pcbs.pcbs, pcb);
+ ip_addr_copy(pcb->local_ip, *local_ip);
+ pcb->local_port = local_port;
+ } else if(state == TIME_WAIT) {
+ TCP_REG(&tcp_tw_pcbs, pcb);
+ ip_addr_copy(pcb->local_ip, *local_ip);
+ pcb->local_port = local_port;
+ ip_addr_copy(pcb->remote_ip, *remote_ip);
+ pcb->remote_port = remote_port;
+ } else {
+ fail();
+ }
+}
+
+void
+test_tcp_counters_err(void* arg, err_t err)
+{
+ struct test_tcp_counters* counters = (struct test_tcp_counters*)arg;
+ EXPECT_RET(arg != NULL);
+ counters->err_calls++;
+ counters->last_err = err;
+}
+
+static void
+test_tcp_counters_check_rxdata(struct test_tcp_counters* counters, struct pbuf* p)
+{
+ struct pbuf* q;
+ u32_t i, received;
+ if(counters->expected_data == NULL) {
+ /* no data to compare */
+ return;
+ }
+ EXPECT_RET(counters->recved_bytes + p->tot_len <= counters->expected_data_len);
+ received = counters->recved_bytes;
+ for(q = p; q != NULL; q = q->next) {
+ char *data = (char*)q->payload;
+ for(i = 0; i < q->len; i++) {
+ EXPECT_RET(data[i] == counters->expected_data[received]);
+ received++;
+ }
+ }
+ EXPECT(received == counters->recved_bytes + p->tot_len);
+}
+
+err_t
+test_tcp_counters_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err)
+{
+ struct test_tcp_counters* counters = (struct test_tcp_counters*)arg;
+ EXPECT_RETX(arg != NULL, ERR_OK);
+ EXPECT_RETX(pcb != NULL, ERR_OK);
+ EXPECT_RETX(err == ERR_OK, ERR_OK);
+
+ if (p != NULL) {
+ if (counters->close_calls == 0) {
+ counters->recv_calls++;
+ test_tcp_counters_check_rxdata(counters, p);
+ counters->recved_bytes += p->tot_len;
+ } else {
+ counters->recv_calls_after_close++;
+ counters->recved_bytes_after_close += p->tot_len;
+ }
+ pbuf_free(p);
+ } else {
+ counters->close_calls++;
+ }
+ EXPECT(counters->recv_calls_after_close == 0 && counters->recved_bytes_after_close == 0);
+ return ERR_OK;
+}
+
+/** Allocate a pcb and set up the test_tcp_counters_* callbacks */
+struct tcp_pcb*
+test_tcp_new_counters_pcb(struct test_tcp_counters* counters)
+{
+ struct tcp_pcb* pcb = tcp_new();
+ if (pcb != NULL) {
+ /* set up args and callbacks */
+ tcp_arg(pcb, counters);
+ tcp_recv(pcb, test_tcp_counters_recv);
+ tcp_err(pcb, test_tcp_counters_err);
+ pcb->snd_wnd = TCP_WND;
+ pcb->snd_wnd_max = TCP_WND;
+ }
+ return pcb;
+}
+
+/** Calls tcp_input() after adjusting current_iphdr_dest */
+void test_tcp_input(struct pbuf *p, struct netif *inp)
+{
+ struct ip_hdr *iphdr = (struct ip_hdr*)p->payload;
+ /* these lines are a hack, don't use them as an example :-) */
+ ip_addr_copy_from_ip4(*ip_current_dest_addr(), iphdr->dest);
+ ip_addr_copy_from_ip4(*ip_current_src_addr(), iphdr->src);
+ ip_current_netif() = inp;
+ ip_data.current_ip4_header = iphdr;
+ ip_data.current_input_netif = inp;
+
+ /* since adding IPv6, p->payload must point to tcp header, not ip header */
+ pbuf_header(p, -(s16_t)sizeof(struct ip_hdr));
+
+ tcp_input(p, inp);
+
+ ip_addr_set_zero(ip_current_dest_addr());
+ ip_addr_set_zero(ip_current_src_addr());
+ ip_current_netif() = NULL;
+ ip_data.current_ip4_header = NULL;
+}
+
+static err_t test_tcp_netif_output(struct netif *netif, struct pbuf *p,
+ const ip4_addr_t *ipaddr)
+{
+ struct test_tcp_txcounters *txcounters = (struct test_tcp_txcounters*)netif->state;
+ LWIP_UNUSED_ARG(ipaddr);
+ if (txcounters != NULL)
+ {
+ txcounters->num_tx_calls++;
+ txcounters->num_tx_bytes += p->tot_len;
+ if (txcounters->copy_tx_packets) {
+ struct pbuf *p_copy = pbuf_alloc(PBUF_LINK, p->tot_len, PBUF_RAM);
+ err_t err;
+ EXPECT(p_copy != NULL);
+ err = pbuf_copy(p_copy, p);
+ EXPECT(err == ERR_OK);
+ if (txcounters->tx_packets == NULL) {
+ txcounters->tx_packets = p_copy;
+ } else {
+ pbuf_cat(txcounters->tx_packets, p_copy);
+ }
+ }
+ }
+ return ERR_OK;
+}
+
+void test_tcp_init_netif(struct netif *netif, struct test_tcp_txcounters *txcounters,
+ const ip_addr_t *ip_addr, const ip_addr_t *netmask)
+{
+ struct netif *n;
+ memset(netif, 0, sizeof(struct netif));
+ if (txcounters != NULL) {
+ memset(txcounters, 0, sizeof(struct test_tcp_txcounters));
+ netif->state = txcounters;
+ }
+ netif->output = test_tcp_netif_output;
+ netif->flags |= NETIF_FLAG_UP | NETIF_FLAG_LINK_UP;
+ ip_addr_copy_from_ip4(netif->netmask, *ip_2_ip4(netmask));
+ ip_addr_copy_from_ip4(netif->ip_addr, *ip_2_ip4(ip_addr));
+ for (n = netif_list; n != NULL; n = n->next) {
+ if (n == netif) {
+ return;
+ }
+ }
+ netif->next = NULL;
+ netif_list = netif;
+}
diff --git a/test/unit/tcp/tcp_helper.h b/test/unit/tcp/tcp_helper.h
new file mode 100644
index 00000000000..cc72e2aba90
--- /dev/null
+++ b/test/unit/tcp/tcp_helper.h
@@ -0,0 +1,58 @@
+#ifndef LWIP_HDR_TCP_HELPER_H
+#define LWIP_HDR_TCP_HELPER_H
+
+#include "../lwip_check.h"
+#include "lwip/arch.h"
+#include "lwip/tcp.h"
+#include "lwip/netif.h"
+
+/* counters used for test_tcp_counters_* callback functions */
+struct test_tcp_counters {
+ u32_t recv_calls;
+ u32_t recved_bytes;
+ u32_t recv_calls_after_close;
+ u32_t recved_bytes_after_close;
+ u32_t close_calls;
+ u32_t err_calls;
+ err_t last_err;
+ char* expected_data;
+ u32_t expected_data_len;
+};
+
+struct test_tcp_txcounters {
+ u32_t num_tx_calls;
+ u32_t num_tx_bytes;
+ u8_t copy_tx_packets;
+ struct pbuf *tx_packets;
+};
+
+extern const ip_addr_t test_local_ip;
+extern const ip_addr_t test_remote_ip;
+extern const ip_addr_t test_netmask;
+#define TEST_REMOTE_PORT 0x100
+#define TEST_LOCAL_PORT 0x101
+
+/* Helper functions */
+void tcp_remove_all(void);
+
+struct pbuf* tcp_create_segment(ip_addr_t* src_ip, ip_addr_t* dst_ip,
+ u16_t src_port, u16_t dst_port, void* data, size_t data_len,
+ u32_t seqno, u32_t ackno, u8_t headerflags);
+struct pbuf* tcp_create_rx_segment(struct tcp_pcb* pcb, void* data, size_t data_len,
+ u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags);
+struct pbuf* tcp_create_rx_segment_wnd(struct tcp_pcb* pcb, void* data, size_t data_len,
+ u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags, u16_t wnd);
+void tcp_set_state(struct tcp_pcb* pcb, enum tcp_state state, const ip_addr_t* local_ip,
+ const ip_addr_t* remote_ip, u16_t local_port, u16_t remote_port);
+void test_tcp_counters_err(void* arg, err_t err);
+err_t test_tcp_counters_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err);
+
+struct tcp_pcb* test_tcp_new_counters_pcb(struct test_tcp_counters* counters);
+
+void test_tcp_input(struct pbuf *p, struct netif *inp);
+
+void test_tcp_init_netif(struct netif *netif, struct test_tcp_txcounters *txcounters,
+ const ip_addr_t *ip_addr, const ip_addr_t *netmask);
+
+
+#endif
diff --git a/test/unit/tcp/test_tcp.c b/test/unit/tcp/test_tcp.c
new file mode 100644
index 00000000000..42324d47d6c
--- /dev/null
+++ b/test/unit/tcp/test_tcp.c
@@ -0,0 +1,1700 @@
+#include "test_tcp.h"
+
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/stats.h"
+#include "lwip/inet.h"
+#include "tcp_helper.h"
+#include "lwip/inet_chksum.h"
+
+#ifdef _MSC_VER
+#pragma warning(disable: 4307) /* we explicitly wrap around TCP seqnos */
+#endif
+
+#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS
+#error "This tests needs TCP- and MEMP-statistics enabled"
+#endif
+#if TCP_SND_BUF <= TCP_WND
+#error "This tests needs TCP_SND_BUF to be > TCP_WND"
+#endif
+
+/* used with check_seqnos() */
+#define SEQNO1 (0xFFFFFF00 - TCP_MSS)
+#define ISS 6510
+static u32_t seqnos[] = {
+ SEQNO1,
+ SEQNO1 + (1 * TCP_MSS),
+ SEQNO1 + (2 * TCP_MSS),
+ SEQNO1 + (3 * TCP_MSS),
+ SEQNO1 + (4 * TCP_MSS),
+ SEQNO1 + (5 * TCP_MSS) };
+
+static u8_t test_tcp_timer;
+
+/* our own version of tcp_tmr so we can reset fast/slow timer state */
+static void
+test_tcp_tmr(void)
+{
+ tcp_fasttmr();
+ if (++test_tcp_timer & 1) {
+ tcp_slowtmr();
+ }
+}
+
+/* Setups/teardown functions */
+static struct netif *old_netif_list;
+static struct netif *old_netif_default;
+
+static void
+tcp_setup(void)
+{
+ struct tcp_pcb dummy_pcb; /* we need this for tcp_next_iss() only */
+
+ old_netif_list = netif_list;
+ old_netif_default = netif_default;
+ netif_list = NULL;
+ netif_default = NULL;
+ /* reset iss to default (6510) */
+ tcp_ticks = 0;
+ tcp_ticks = 0 - (tcp_next_iss(&dummy_pcb) - 6510);
+ tcp_next_iss(&dummy_pcb);
+ tcp_ticks = 0;
+
+ test_tcp_timer = 0;
+ tcp_remove_all();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+tcp_teardown(void)
+{
+ netif_list = NULL;
+ netif_default = NULL;
+ tcp_remove_all();
+ /* restore netif_list for next tests (e.g. loopif) */
+ netif_list = old_netif_list;
+ netif_default = old_netif_default;
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+
+/* Test functions */
+
+/** Call tcp_new() and tcp_abort() and test memp stats */
+START_TEST(test_tcp_new_abort)
+{
+ struct tcp_pcb* pcb;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+
+ pcb = tcp_new();
+ fail_unless(pcb != NULL);
+ if (pcb != NULL) {
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+ }
+}
+END_TEST
+
+/** Call tcp_new() and tcp_abort() and test memp stats */
+START_TEST(test_tcp_listen_passive_open)
+{
+ struct tcp_pcb *pcb, *pcbl;
+ struct tcp_pcb_listen *lpcb;
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct pbuf *p;
+ ip_addr_t src_addr;
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+
+ pcb = tcp_new();
+ EXPECT_RET(pcb != NULL);
+ err = tcp_bind(pcb, &netif.ip_addr, 1234);
+ EXPECT(err == ERR_OK);
+ pcbl = tcp_listen(pcb);
+ EXPECT_RET(pcbl != NULL);
+ EXPECT_RET(pcbl != pcb);
+ lpcb = (struct tcp_pcb_listen *)pcbl;
+
+ ip_addr_set_ip4_u32_val(src_addr, lwip_htonl(lwip_ntohl(ip_addr_get_ip4_u32(&lpcb->local_ip)) + 1));
+
+ /* check correct syn packet */
+ p = tcp_create_segment(&src_addr, &lpcb->local_ip, 12345,
+ lpcb->local_port, NULL, 0, 12345, 54321, TCP_SYN);
+ EXPECT(p != NULL);
+ if (p != NULL) {
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(txcounters.num_tx_calls == 1);
+ }
+
+ /* check syn packet with short length */
+ p = tcp_create_segment(&src_addr, &lpcb->local_ip, 12345,
+ lpcb->local_port, NULL, 0, 12345, 54321, TCP_SYN);
+ EXPECT(p != NULL);
+ EXPECT(p->next == NULL);
+ if ((p != NULL) && (p->next == NULL)) {
+ p->len -= 2;
+ p->tot_len -= 2;
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(txcounters.num_tx_calls == 1);
+ }
+
+ tcp_close(pcbl);
+}
+END_TEST
+
+/** Create an ESTABLISHED pcb and check if receive callback is called */
+START_TEST(test_tcp_recv_inseq)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ char data[] = {1, 2, 3, 4};
+ u16_t data_len;
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ data_len = sizeof(data);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = data_len;
+ counters.expected_data = data;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* create a segment */
+ p = tcp_create_rx_segment(pcb, counters.expected_data, data_len, 0, 0, 0);
+ EXPECT(p != NULL);
+ if (p != NULL) {
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 1);
+ EXPECT(counters.recved_bytes == data_len);
+ EXPECT(counters.err_calls == 0);
+ }
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+/** Create an ESTABLISHED pcb and check if receive callback is called if a segment
+ * overlapping rcv_nxt is received */
+START_TEST(test_tcp_recv_inseq_trim)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ char data[PBUF_POOL_BUFSIZE*2];
+ u16_t data_len;
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ const u32_t new_data_len = 40;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ data_len = sizeof(data);
+ memset(data, 0, sizeof(data));
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = data_len;
+ counters.expected_data = data;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* create a segment (with an overlapping/old seqno so that the new data begins in the 2nd pbuf) */
+ p = tcp_create_rx_segment(pcb, counters.expected_data, data_len, (u32_t)(0-(data_len-new_data_len)), 0, 0);
+ EXPECT(p != NULL);
+ if (p != NULL) {
+ EXPECT(p->next != NULL);
+ if (p->next != NULL) {
+ EXPECT(p->next->next != NULL);
+ }
+ }
+ if ((p != NULL) && (p->next != NULL) && (p->next->next != NULL)) {
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 1);
+ EXPECT(counters.recved_bytes == new_data_len);
+ EXPECT(counters.err_calls == 0);
+ }
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+static err_t test_tcp_recv_expect1byte(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err);
+
+static err_t
+test_tcp_recv_expectclose(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err)
+{
+ EXPECT_RETX(pcb != NULL, ERR_OK);
+ EXPECT_RETX(err == ERR_OK, ERR_OK);
+ LWIP_UNUSED_ARG(arg);
+
+ if (p != NULL) {
+ fail();
+ } else {
+ /* correct: FIN received; close our end, too */
+ err_t err2 = tcp_close(pcb);
+ fail_unless(err2 == ERR_OK);
+ /* set back to some other rx function, just to not get here again */
+ tcp_recv(pcb, test_tcp_recv_expect1byte);
+ }
+ return ERR_OK;
+}
+
+static err_t
+test_tcp_recv_expect1byte(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err)
+{
+ EXPECT_RETX(pcb != NULL, ERR_OK);
+ EXPECT_RETX(err == ERR_OK, ERR_OK);
+ LWIP_UNUSED_ARG(arg);
+
+ if (p != NULL) {
+ if ((p->len == 1) && (p->tot_len == 1)) {
+ tcp_recv(pcb, test_tcp_recv_expectclose);
+ } else {
+ fail();
+ }
+ pbuf_free(p);
+ } else {
+ fail();
+ }
+ return ERR_OK;
+}
+
+START_TEST(test_tcp_passive_close)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ char data = 0x0f;
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = 1;
+ counters.expected_data = &data;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* create a segment without data */
+ p = tcp_create_rx_segment(pcb, &data, 1, 0, 0, TCP_FIN);
+ EXPECT(p != NULL);
+ if (p != NULL) {
+ tcp_recv(pcb, test_tcp_recv_expect1byte);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ }
+ /* don't free the pcb here (part of the test!) */
+}
+END_TEST
+
+START_TEST(test_tcp_active_abort)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ char data = 0x0f;
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ LWIP_UNUSED_ARG(_i);
+
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = 1;
+ counters.expected_data = &data;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* abort the pcb */
+ EXPECT_RET(txcounters.num_tx_calls == 0);
+ txcounters.copy_tx_packets = 1;
+ tcp_abort(pcb);
+ txcounters.copy_tx_packets = 0;
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 40U);
+ EXPECT(txcounters.tx_packets != NULL);
+ if (txcounters.tx_packets != NULL) {
+ u16_t ret;
+ struct tcp_hdr tcphdr;
+ ret = pbuf_copy_partial(txcounters.tx_packets, &tcphdr, 20, 20);
+ EXPECT(ret == 20);
+ EXPECT(tcphdr.dest == PP_HTONS(TEST_REMOTE_PORT));
+ EXPECT(tcphdr.src == PP_HTONS(TEST_LOCAL_PORT));
+ pbuf_free(txcounters.tx_packets);
+ txcounters.tx_packets = NULL;
+ }
+
+ /* don't free the pcb here (part of the test!) */
+}
+END_TEST
+
+/** Check that we handle malformed tcp headers, and discard the pbuf(s) */
+START_TEST(test_tcp_malformed_header)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ char data[] = {1, 2, 3, 4};
+ u16_t data_len, chksum;
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct tcp_hdr *hdr;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ data_len = sizeof(data);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = data_len;
+ counters.expected_data = data;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* create a segment */
+ p = tcp_create_rx_segment(pcb, counters.expected_data, data_len, 0, 0, 0);
+
+ pbuf_header(p, -(s16_t)sizeof(struct ip_hdr));
+
+ hdr = (struct tcp_hdr *)p->payload;
+ TCPH_HDRLEN_FLAGS_SET(hdr, 15, 0x3d1);
+
+ hdr->chksum = 0;
+
+ chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len,
+ &test_remote_ip, &test_local_ip);
+
+ hdr->chksum = chksum;
+
+ pbuf_header(p, sizeof(struct ip_hdr));
+
+ EXPECT(p != NULL);
+ EXPECT(p->next == NULL);
+ if (p != NULL) {
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ }
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+
+/** Provoke fast retransmission by duplicate ACKs and then recover by ACKing all sent data.
+ * At the end, send more data. */
+START_TEST(test_tcp_fast_retx_recover)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ char data1[] = { 1, 2, 3, 4};
+ char data2[] = { 5, 6, 7, 8};
+ char data3[] = { 9, 10, 11, 12};
+ char data4[] = {13, 14, 15, 16};
+ char data5[] = {17, 18, 19, 20};
+ char data6[TCP_MSS] = {21, 22, 23, 24};
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ /* disable initial congestion window (we don't send a SYN here...) */
+ pcb->cwnd = pcb->snd_wnd;
+
+ /* send data1 */
+ err = tcp_write(pcb, data1, sizeof(data1), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(txcounters.num_tx_calls == 1);
+ EXPECT_RET(txcounters.num_tx_bytes == sizeof(data1) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* "recv" ACK for data1 */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 4, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &netif);
+ EXPECT_RET(txcounters.num_tx_calls == 0);
+ EXPECT_RET(pcb->unacked == NULL);
+ /* send data2 */
+ err = tcp_write(pcb, data2, sizeof(data2), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(txcounters.num_tx_calls == 1);
+ EXPECT_RET(txcounters.num_tx_bytes == sizeof(data2) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* duplicate ACK for data1 (data2 is lost) */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &netif);
+ EXPECT_RET(txcounters.num_tx_calls == 0);
+ EXPECT_RET(pcb->dupacks == 1);
+ /* send data3 */
+ err = tcp_write(pcb, data3, sizeof(data3), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ /* nagle enabled, no tx calls */
+ EXPECT_RET(txcounters.num_tx_calls == 0);
+ EXPECT_RET(txcounters.num_tx_bytes == 0);
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* 2nd duplicate ACK for data1 (data2 and data3 are lost) */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &netif);
+ EXPECT_RET(txcounters.num_tx_calls == 0);
+ EXPECT_RET(pcb->dupacks == 2);
+ /* queue data4, don't send it (unsent-oversize is != 0) */
+ err = tcp_write(pcb, data4, sizeof(data4), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ /* 3nd duplicate ACK for data1 (data2 and data3 are lost) -> fast retransmission */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &netif);
+ /*EXPECT_RET(txcounters.num_tx_calls == 1);*/
+ EXPECT_RET(pcb->dupacks == 3);
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* @todo: check expected data?*/
+
+ /* send data5, not output yet */
+ err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ /*err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);*/
+ EXPECT_RET(txcounters.num_tx_calls == 0);
+ EXPECT_RET(txcounters.num_tx_bytes == 0);
+ memset(&txcounters, 0, sizeof(txcounters));
+ {
+ int i = 0;
+ do
+ {
+ err = tcp_write(pcb, data6, TCP_MSS, TCP_WRITE_FLAG_COPY);
+ i++;
+ }while(err == ERR_OK);
+ EXPECT_RET(err != ERR_OK);
+ }
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ /*EXPECT_RET(txcounters.num_tx_calls == 0);
+ EXPECT_RET(txcounters.num_tx_bytes == 0);*/
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* send even more data */
+ err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ /* ...and even more data */
+ err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ /* ...and even more data */
+ err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ /* ...and even more data */
+ err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+
+ /* send ACKs for data2 and data3 */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 12, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &netif);
+ /*EXPECT_RET(txcounters.num_tx_calls == 0);*/
+
+ /* ...and even more data */
+ err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ /* ...and even more data */
+ err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+
+#if 0
+ /* create expected segment */
+ p1 = tcp_create_rx_segment(pcb, counters.expected_data, data_len, 0, 0, 0);
+ EXPECT_RET(p != NULL);
+ if (p != NULL) {
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT_RET(counters.close_calls == 0);
+ EXPECT_RET(counters.recv_calls == 1);
+ EXPECT_RET(counters.recved_bytes == data_len);
+ EXPECT_RET(counters.err_calls == 0);
+ }
+#endif
+ /* make sure the pcb is freed */
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+static u8_t tx_data[TCP_WND*2];
+
+static void
+check_seqnos(struct tcp_seg *segs, int num_expected, u32_t *seqnos_expected)
+{
+ struct tcp_seg *s = segs;
+ int i;
+ for (i = 0; i < num_expected; i++, s = s->next) {
+ EXPECT_RET(s != NULL);
+ EXPECT(s->tcphdr->seqno == htonl(seqnos_expected[i]));
+ }
+ EXPECT(s == NULL);
+}
+
+/** Send data with sequence numbers that wrap around the u32_t range.
+ * Then, provoke fast retransmission by duplicate ACKs and check that all
+ * segment lists are still properly sorted. */
+START_TEST(test_tcp_fast_rexmit_wraparound)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ err_t err;
+ size_t i;
+ u16_t sent_total = 0;
+ LWIP_UNUSED_ARG(_i);
+
+ for (i = 0; i < sizeof(tx_data); i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ tcp_ticks = SEQNO1 - ISS;
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ /* disable initial congestion window (we don't send a SYN here...) */
+ pcb->cwnd = 2*TCP_MSS;
+ /* start in congestion advoidance */
+ pcb->ssthresh = pcb->cwnd;
+
+ /* send 6 mss-sized segments */
+ for (i = 0; i < 6; i++) {
+ err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ sent_total += TCP_MSS;
+ }
+ check_seqnos(pcb->unsent, 6, seqnos);
+ EXPECT(pcb->unacked == NULL);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT(txcounters.num_tx_calls == 2);
+ EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ check_seqnos(pcb->unacked, 2, seqnos);
+ check_seqnos(pcb->unsent, 4, &seqnos[2]);
+
+ /* ACK the first segment */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK);
+ test_tcp_input(p, &netif);
+ /* ensure this didn't trigger a retransmission. Only one
+ segment should be transmitted because cwnd opened up by
+ TCP_MSS and a fraction since we are in congestion avoidance */
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U);
+ memset(&txcounters, 0, sizeof(txcounters));
+ check_seqnos(pcb->unacked, 2, &seqnos[1]);
+ check_seqnos(pcb->unsent, 3, &seqnos[3]);
+
+ /* 3 dupacks */
+ EXPECT(pcb->dupacks == 0);
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+ test_tcp_input(p, &netif);
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(pcb->dupacks == 1);
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+ test_tcp_input(p, &netif);
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(pcb->dupacks == 2);
+ /* 3rd dupack -> fast rexmit */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+ test_tcp_input(p, &netif);
+ EXPECT(pcb->dupacks == 3);
+ EXPECT(txcounters.num_tx_calls == 4);
+ memset(&txcounters, 0, sizeof(txcounters));
+ EXPECT(pcb->unsent == NULL);
+ check_seqnos(pcb->unacked, 5, &seqnos[1]);
+
+ /* make sure the pcb is freed */
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+/** Send data with sequence numbers that wrap around the u32_t range.
+ * Then, provoke RTO retransmission and check that all
+ * segment lists are still properly sorted. */
+START_TEST(test_tcp_rto_rexmit_wraparound)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct tcp_pcb dummy_pcb_for_iss; /* we need this for tcp_next_iss() only */
+ err_t err;
+ size_t i;
+ u16_t sent_total = 0;
+ LWIP_UNUSED_ARG(_i);
+
+ for (i = 0; i < sizeof(tx_data); i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ tcp_ticks = 0;
+ tcp_ticks = 0 - tcp_next_iss(&dummy_pcb_for_iss);
+ tcp_ticks = SEQNO1 - tcp_next_iss(&dummy_pcb_for_iss);
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ /* disable initial congestion window (we don't send a SYN here...) */
+ pcb->cwnd = 2*TCP_MSS;
+
+ /* send 6 mss-sized segments */
+ for (i = 0; i < 6; i++) {
+ err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ sent_total += TCP_MSS;
+ }
+ check_seqnos(pcb->unsent, 6, seqnos);
+ EXPECT(pcb->unacked == NULL);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT(txcounters.num_tx_calls == 2);
+ EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ check_seqnos(pcb->unacked, 2, seqnos);
+ check_seqnos(pcb->unsent, 4, &seqnos[2]);
+
+ /* call the tcp timer some times */
+ for (i = 0; i < 10; i++) {
+ test_tcp_tmr();
+ EXPECT(txcounters.num_tx_calls == 0);
+ }
+ /* 11th call to tcp_tmr: RTO rexmit fires */
+ test_tcp_tmr();
+ EXPECT(txcounters.num_tx_calls == 1);
+ check_seqnos(pcb->unacked, 1, seqnos);
+ check_seqnos(pcb->unsent, 5, &seqnos[1]);
+
+ /* fake greater cwnd */
+ pcb->cwnd = pcb->snd_wnd;
+ /* send more data */
+ err = tcp_output(pcb);
+ EXPECT(err == ERR_OK);
+ /* check queues are sorted */
+ EXPECT(pcb->unsent == NULL);
+ check_seqnos(pcb->unacked, 6, seqnos);
+
+ /* make sure the pcb is freed */
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+/** Provoke fast retransmission by duplicate ACKs and then recover by ACKing all sent data.
+ * At the end, send more data. */
+static void test_tcp_tx_full_window_lost(u8_t zero_window_probe_from_unsent)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *p;
+ err_t err;
+ size_t i;
+ u16_t sent_total;
+ u8_t expected = 0xFE;
+
+ for (i = 0; i < sizeof(tx_data); i++) {
+ u8_t d = (u8_t)i;
+ if (d == 0xFE) {
+ d = 0xF0;
+ }
+ tx_data[i] = d;
+ }
+ if (zero_window_probe_from_unsent) {
+ tx_data[TCP_WND] = expected;
+ } else {
+ tx_data[0] = expected;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ /* disable initial congestion window (we don't send a SYN here...) */
+ pcb->cwnd = pcb->snd_wnd;
+
+ /* send a full window (minus 1 packets) of TCP data in MSS-sized chunks */
+ sent_total = 0;
+ if ((TCP_WND - TCP_MSS) % TCP_MSS != 0) {
+ u16_t initial_data_len = (TCP_WND - TCP_MSS) % TCP_MSS;
+ err = tcp_write(pcb, &tx_data[sent_total], initial_data_len, TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == initial_data_len + 40U);
+ memset(&txcounters, 0, sizeof(txcounters));
+ sent_total += initial_data_len;
+ }
+ for (; sent_total < (TCP_WND - TCP_MSS); sent_total += TCP_MSS) {
+ err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U);
+ memset(&txcounters, 0, sizeof(txcounters));
+ }
+ EXPECT(sent_total == (TCP_WND - TCP_MSS));
+
+ /* now ACK the packet before the first */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK);
+ test_tcp_input(p, &netif);
+ /* ensure this didn't trigger a retransmission */
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+
+ EXPECT(pcb->persist_backoff == 0);
+ /* send the last packet, now a complete window has been sent */
+ err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ sent_total += TCP_MSS;
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U);
+ memset(&txcounters, 0, sizeof(txcounters));
+ EXPECT(pcb->persist_backoff == 0);
+
+ if (zero_window_probe_from_unsent) {
+ /* ACK all data but close the TX window */
+ p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, TCP_WND, TCP_ACK, 0);
+ test_tcp_input(p, &netif);
+ /* ensure this didn't trigger any transmission */
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+ /* window is completely full, but persist timer is off since send buffer is empty */
+ EXPECT(pcb->snd_wnd == 0);
+ EXPECT(pcb->persist_backoff == 0);
+ }
+
+ /* send one byte more (out of window) -> persist timer starts */
+ err = tcp_write(pcb, &tx_data[sent_total], 1, TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+ memset(&txcounters, 0, sizeof(txcounters));
+ if (!zero_window_probe_from_unsent) {
+ /* no persist timer unless a zero window announcement has been received */
+ EXPECT(pcb->persist_backoff == 0);
+ } else {
+ EXPECT(pcb->persist_backoff == 1);
+
+ /* call tcp_timer some more times to let persist timer count up */
+ for (i = 0; i < 4; i++) {
+ test_tcp_tmr();
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+ }
+
+ /* this should trigger the zero-window-probe */
+ txcounters.copy_tx_packets = 1;
+ test_tcp_tmr();
+ txcounters.copy_tx_packets = 0;
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 1 + 40U);
+ EXPECT(txcounters.tx_packets != NULL);
+ if (txcounters.tx_packets != NULL) {
+ u8_t sent;
+ u16_t ret;
+ ret = pbuf_copy_partial(txcounters.tx_packets, &sent, 1, 40U);
+ EXPECT(ret == 1);
+ EXPECT(sent == expected);
+ }
+ if (txcounters.tx_packets != NULL) {
+ pbuf_free(txcounters.tx_packets);
+ txcounters.tx_packets = NULL;
+ }
+ }
+
+ /* make sure the pcb is freed */
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+
+START_TEST(test_tcp_tx_full_window_lost_from_unsent)
+{
+ LWIP_UNUSED_ARG(_i);
+ test_tcp_tx_full_window_lost(1);
+}
+END_TEST
+
+START_TEST(test_tcp_tx_full_window_lost_from_unacked)
+{
+ LWIP_UNUSED_ARG(_i);
+ test_tcp_tx_full_window_lost(0);
+}
+END_TEST
+
+/** Send data, provoke retransmission and then add data to a segment
+ * that already has been sent before. */
+START_TEST(test_tcp_retx_add_to_sent)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ char data1a[] = { 1, 2, 3};
+ char data1b[] = { 4};
+ char data2a[] = { 5, 6, 7, 8};
+ char data2b[] = { 5, 6, 7};
+ char data3[] = { 9, 10, 11, 12, 12};
+ char data4[] = { 13, 14, 15, 16,17};
+ err_t err;
+ int i;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ /* disable initial congestion window (we don't send a SYN here...) */
+ pcb->cwnd = pcb->snd_wnd;
+
+ /* send data1 */
+ err = tcp_write(pcb, data1a, sizeof(data1a), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_write(pcb, data1b, sizeof(data1b), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(txcounters.num_tx_calls == 1);
+ EXPECT_RET(txcounters.num_tx_bytes == sizeof(data1a) + sizeof(data1b) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* "recv" ACK for data1 */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 4, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &netif);
+ EXPECT_RET(txcounters.num_tx_calls == 0);
+ EXPECT_RET(pcb->unacked == NULL);
+ /* send data2 */
+ err = tcp_write(pcb, data2a, sizeof(data2a), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_write(pcb, data2b, sizeof(data2b), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(txcounters.num_tx_calls == 1);
+ EXPECT_RET(txcounters.num_tx_bytes == sizeof(data2a) + sizeof(data2b) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* send data3 */
+ err = tcp_write(pcb, data3, sizeof(data3), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(txcounters.num_tx_calls == 0);
+ EXPECT_RET(txcounters.num_tx_bytes == 0);
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* data3 not sent yet (nagle) */
+ EXPECT_RET(pcb->unacked != NULL);
+ EXPECT_RET(pcb->unsent != NULL);
+
+ /* disable nagle for this test so data to sent segment can be added below... */
+ tcp_nagle_disable(pcb);
+
+ /* call the tcp timer some times */
+ for (i = 0; i < 20; i++) {
+ test_tcp_tmr();
+ if (txcounters.num_tx_calls != 0) {
+ break;
+ }
+ }
+ /* data3 sent */
+ EXPECT_RET(txcounters.num_tx_calls == 1);
+ EXPECT_RET(txcounters.num_tx_bytes == sizeof(data3) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+ EXPECT_RET(pcb->unacked != NULL);
+ EXPECT_RET(pcb->unsent == NULL);
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ tcp_nagle_enable(pcb);
+
+ /* call the tcp timer some times */
+ for (i = 0; i < 20; i++) {
+ test_tcp_tmr();
+ if (txcounters.num_tx_calls != 0) {
+ break;
+ }
+ }
+ /* RTO: rexmit of data2 */
+ EXPECT_RET(txcounters.num_tx_calls == 1);
+ EXPECT_RET(txcounters.num_tx_bytes == sizeof(data2a) + sizeof(data2b) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+ EXPECT_RET(pcb->unacked != NULL);
+ EXPECT_RET(pcb->unsent != NULL);
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* send data4 */
+ err = tcp_write(pcb, data4, sizeof(data4), TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ /* disable nagle for this test so data to transmit without further ACKs... */
+ tcp_nagle_disable(pcb);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ /* nagle enabled, no tx calls */
+ EXPECT_RET(txcounters.num_tx_calls == 1);
+ EXPECT_RET(txcounters.num_tx_bytes == sizeof(data3) + sizeof(data4) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr));
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* make sure the pcb is freed */
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+START_TEST(test_tcp_rto_tracking)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ err_t err;
+ size_t i;
+ u16_t sent_total = 0;
+ LWIP_UNUSED_ARG(_i);
+
+ for (i = 0; i < sizeof(tx_data); i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ tcp_ticks = SEQNO1 - ISS;
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ /* Set congestion window large enough to send all our segments */
+ pcb->cwnd = 5*TCP_MSS;
+
+ /* send 5 mss-sized segments */
+ for (i = 0; i < 5; i++) {
+ err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ sent_total += TCP_MSS;
+ }
+ check_seqnos(pcb->unsent, 5, seqnos);
+ EXPECT(pcb->unacked == NULL);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT(txcounters.num_tx_calls == 5);
+ EXPECT(txcounters.num_tx_bytes == 5 * (TCP_MSS + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* Check all 5 are in-flight */
+ EXPECT(pcb->unsent == NULL);
+ check_seqnos(pcb->unacked, 5, seqnos);
+
+ /* Force us into retransmisson timeout */
+ while (!(pcb->flags & TF_RTO)) {
+ test_tcp_tmr();
+ }
+ /* Ensure 4 remaining segments are back on unsent, ready for retransmission */
+ check_seqnos(pcb->unsent, 4, &seqnos[1]);
+ /* Ensure 1st segment is on unacked (already retransmitted) */
+ check_seqnos(pcb->unacked, 1, seqnos);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U);
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* Ensure rto_end points to next byte */
+ EXPECT(pcb->rto_end == seqnos[5]);
+ EXPECT(pcb->rto_end == pcb->snd_nxt);
+ /* Check cwnd was reset */
+ EXPECT(pcb->cwnd == pcb->mss);
+
+ /* Add another segment to send buffer which is outside of RTO */
+ err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ sent_total += TCP_MSS;
+ check_seqnos(pcb->unsent, 5, &seqnos[1]);
+ /* Ensure no new data was sent */
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+ EXPECT(pcb->rto_end == pcb->snd_nxt);
+
+ /* ACK first segment */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK);
+ test_tcp_input(p, &netif);
+ /* Next two retranmissions should go out, due to cwnd in slow start */
+ EXPECT(txcounters.num_tx_calls == 2);
+ EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+ check_seqnos(pcb->unacked, 2, &seqnos[1]);
+ check_seqnos(pcb->unsent, 3, &seqnos[3]);
+ /* RTO should still be marked */
+ EXPECT(pcb->flags & TF_RTO);
+ /* cwnd should have only grown by 1 MSS */
+ EXPECT(pcb->cwnd == (tcpwnd_size_t)(2 * pcb->mss));
+ /* Ensure no new data was sent */
+ EXPECT(pcb->rto_end == pcb->snd_nxt);
+
+ /* ACK the next two segments */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 2*TCP_MSS, TCP_ACK);
+ test_tcp_input(p, &netif);
+ /* Final 2 retransmissions and 1 new data should go out */
+ EXPECT(txcounters.num_tx_calls == 3);
+ EXPECT(txcounters.num_tx_bytes == 3 * (TCP_MSS + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+ check_seqnos(pcb->unacked, 3, &seqnos[3]);
+ EXPECT(pcb->unsent == NULL);
+ /* RTO should still be marked */
+ EXPECT(pcb->flags & TF_RTO);
+ /* cwnd should have only grown by 1 MSS */
+ EXPECT(pcb->cwnd == (tcpwnd_size_t)(3 * pcb->mss));
+ /* snd_nxt should have been advanced past rto_end */
+ EXPECT(TCP_SEQ_GT(pcb->snd_nxt, pcb->rto_end));
+
+ /* ACK the next two segments, finishing our RTO, leaving new segment unacked */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 2*TCP_MSS, TCP_ACK);
+ test_tcp_input(p, &netif);
+ EXPECT(!(pcb->flags & TF_RTO));
+ check_seqnos(pcb->unacked, 1, &seqnos[5]);
+ /* We should be in ABC congestion avoidance, so no change in cwnd */
+ EXPECT(pcb->cwnd == (tcpwnd_size_t)(3 * pcb->mss));
+ EXPECT(pcb->cwnd >= pcb->ssthresh);
+ /* Ensure ABC congestion avoidance is tracking bytes acked */
+ EXPECT(pcb->bytes_acked == (tcpwnd_size_t)(2 * pcb->mss));
+
+ /* make sure the pcb is freed */
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+static void test_tcp_rto_timeout_impl(int link_down)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb *pcb, *cur;
+ err_t err;
+ size_t i;
+ const size_t max_wait_ctr = 1024 * 1024;
+
+ /* Setup data for a single segment */
+ for (i = 0; i < TCP_MSS; i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ tcp_ticks = SEQNO1 - ISS;
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ pcb->cwnd = TCP_MSS;
+
+ /* send our segment */
+ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ EXPECT_RET(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* ensure no errors have been recorded */
+ EXPECT(counters.err_calls == 0);
+ EXPECT(counters.last_err == ERR_OK);
+
+ /* Force us into retransmisson timeout */
+ for (i = 0; !(pcb->flags & TF_RTO) && i < max_wait_ctr; i++) {
+ test_tcp_tmr();
+ }
+ EXPECT(i < max_wait_ctr);
+
+ /* check first rexmit */
+ EXPECT(pcb->nrtx == 1);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U));
+
+ /* still no error expected */
+ EXPECT(counters.err_calls == 0);
+ EXPECT(counters.last_err == ERR_OK);
+
+ if (link_down) {
+ netif_set_link_down(&netif);
+ }
+
+ /* keep running the timer till we hit our maximum RTO */
+ for (i = 0; counters.last_err == ERR_OK && i < max_wait_ctr; i++) {
+ test_tcp_tmr();
+ }
+ EXPECT(i < max_wait_ctr);
+
+ /* check number of retransmissions */
+ if (link_down) {
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U));
+ } else {
+ EXPECT(txcounters.num_tx_calls == TCP_MAXRTX);
+ EXPECT(txcounters.num_tx_bytes == TCP_MAXRTX * (TCP_MSS + 40U));
+ }
+
+ /* check the connection (pcb) has been aborted */
+ EXPECT(counters.err_calls == 1);
+ EXPECT(counters.last_err == ERR_ABRT);
+ /* check our pcb is no longer active */
+ for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) {
+ EXPECT(cur != pcb);
+ }
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+
+START_TEST(test_tcp_rto_timeout)
+{
+ LWIP_UNUSED_ARG(_i);
+ test_tcp_rto_timeout_impl(0);
+}
+END_TEST
+
+START_TEST(test_tcp_rto_timeout_link_down)
+{
+ LWIP_UNUSED_ARG(_i);
+ test_tcp_rto_timeout_impl(1);
+}
+END_TEST
+
+static void test_tcp_rto_timeout_syn_sent_impl(int link_down)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb *pcb, *cur;
+ err_t err;
+ size_t i;
+ const size_t max_wait_ctr = 1024 * 1024;
+ const u16_t tcp_syn_opts_len = LWIP_TCP_OPT_LENGTH(TF_SEG_OPTS_MSS|TF_SEG_OPTS_WND_SCALE|TF_SEG_OPTS_SACK_PERM|TF_SEG_OPTS_TS);
+
+ /* Setup data for a single segment */
+ for (i = 0; i < TCP_MSS; i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ tcp_ticks = SEQNO1 - ISS;
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ err = tcp_connect(pcb, &netif.gw, 123, NULL);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(pcb->state == SYN_SENT);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 40U + tcp_syn_opts_len);
+
+ /* ensure no errors have been recorded */
+ EXPECT(counters.err_calls == 0);
+ EXPECT(counters.last_err == ERR_OK);
+
+ txcounters.num_tx_calls = 0;
+ txcounters.num_tx_bytes = 0;
+
+ /* Force us into retransmisson timeout */
+ for (i = 0; !(pcb->flags & TF_RTO) && i < max_wait_ctr; i++) {
+ test_tcp_tmr();
+ }
+ EXPECT(i < max_wait_ctr);
+
+ /* check first rexmit */
+ EXPECT(pcb->nrtx == 1);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 40U + tcp_syn_opts_len); /* 40: headers; >=: options */
+
+ /* still no error expected */
+ EXPECT(counters.err_calls == 0);
+ EXPECT(counters.last_err == ERR_OK);
+
+ if (link_down) {
+ /* set link down and check what happens to the RTO counter */
+ netif_set_link_down(&netif);
+ }
+
+ /* keep running the timer till we hit our maximum RTO */
+ for (i = 0; counters.last_err == ERR_OK && i < max_wait_ctr; i++) {
+ test_tcp_tmr();
+ }
+ EXPECT(i < max_wait_ctr);
+
+ /* check number of retransmissions */
+ if (link_down) {
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 40U + tcp_syn_opts_len);
+ } else {
+ EXPECT(txcounters.num_tx_calls == TCP_SYNMAXRTX);
+ EXPECT(txcounters.num_tx_bytes == TCP_SYNMAXRTX * (tcp_syn_opts_len + 40U));
+ }
+
+ /* check the connection (pcb) has been aborted */
+ EXPECT(counters.err_calls == 1);
+ EXPECT(counters.last_err == ERR_ABRT);
+ /* check our pcb is no longer active */
+ for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) {
+ EXPECT(cur != pcb);
+ }
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+
+START_TEST(test_tcp_rto_timeout_syn_sent)
+{
+ LWIP_UNUSED_ARG(_i);
+ test_tcp_rto_timeout_syn_sent_impl(0);
+}
+END_TEST
+
+START_TEST(test_tcp_rto_timeout_syn_sent_link_down)
+{
+ LWIP_UNUSED_ARG(_i);
+ test_tcp_rto_timeout_syn_sent_impl(1);
+}
+END_TEST
+
+static void test_tcp_zwp_timeout_impl(int link_down)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb *pcb, *cur;
+ struct pbuf* p;
+ err_t err;
+ size_t i;
+
+ /* Setup data for two segments */
+ for (i = 0; i < 2*TCP_MSS; i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ tcp_ticks = SEQNO1 - ISS;
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ pcb->cwnd = TCP_MSS;
+
+ /* send first segment */
+ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ EXPECT(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT(err == ERR_OK);
+
+ /* verify segment is in-flight */
+ EXPECT(pcb->unsent == NULL);
+ check_seqnos(pcb->unacked, 1, seqnos);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* ACK the segment and close the TX window */
+ p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK, 0);
+ test_tcp_input(p, &netif);
+ EXPECT(pcb->unacked == NULL);
+ EXPECT(pcb->unsent == NULL);
+ /* send buffer empty, persist should be off */
+ EXPECT(pcb->persist_backoff == 0);
+ EXPECT(pcb->snd_wnd == 0);
+
+ /* send second segment, should be buffered */
+ err = tcp_write(pcb, &tx_data[TCP_MSS], TCP_MSS, TCP_WRITE_FLAG_COPY);
+ EXPECT(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT(err == ERR_OK);
+
+ /* ensure it is buffered and persist timer started */
+ EXPECT(pcb->unacked == NULL);
+ check_seqnos(pcb->unsent, 1, &seqnos[1]);
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+ EXPECT(pcb->persist_backoff == 1);
+
+ /* ensure no errors have been recorded */
+ EXPECT(counters.err_calls == 0);
+ EXPECT(counters.last_err == ERR_OK);
+
+ /* run timer till first probe */
+ EXPECT(pcb->persist_probe == 0);
+ while (pcb->persist_probe == 0) {
+ test_tcp_tmr();
+ }
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == (1 + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* respond to probe with remote's current SEQ, ACK, and zero-window */
+ p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, 0, TCP_ACK, 0);
+ test_tcp_input(p, &netif);
+ /* ensure zero-window is still active, but probe count reset */
+ EXPECT(pcb->persist_backoff > 1);
+ EXPECT(pcb->persist_probe == 0);
+ EXPECT(pcb->snd_wnd == 0);
+
+ /* ensure no errors have been recorded */
+ EXPECT(counters.err_calls == 0);
+ EXPECT(counters.last_err == ERR_OK);
+
+ if (link_down) {
+ netif_set_link_down(&netif);
+ }
+
+ /* now run the timer till we hit our maximum probe count */
+ while (counters.last_err == ERR_OK) {
+ test_tcp_tmr();
+ }
+
+ if (link_down) {
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+ } else {
+ /* check maximum number of 1 byte probes were sent */
+ EXPECT(txcounters.num_tx_calls == TCP_MAXRTX);
+ EXPECT(txcounters.num_tx_bytes == TCP_MAXRTX * (1 + 40U));
+ }
+
+ /* check the connection (pcb) has been aborted */
+ EXPECT(counters.err_calls == 1);
+ EXPECT(counters.last_err == ERR_ABRT);
+ /* check our pcb is no longer active */
+ for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) {
+ EXPECT(cur != pcb);
+ }
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+
+START_TEST(test_tcp_zwp_timeout)
+{
+ LWIP_UNUSED_ARG(_i);
+ test_tcp_zwp_timeout_impl(0);
+}
+END_TEST
+
+START_TEST(test_tcp_zwp_timeout_link_down)
+{
+ LWIP_UNUSED_ARG(_i);
+ test_tcp_zwp_timeout_impl(1);
+}
+END_TEST
+
+START_TEST(test_tcp_persist_split)
+{
+ struct netif netif;
+ struct test_tcp_txcounters txcounters;
+ struct test_tcp_counters counters;
+ struct tcp_pcb *pcb;
+ struct pbuf* p;
+ err_t err;
+ size_t i;
+ LWIP_UNUSED_ARG(_i);
+
+ /* Setup data for four segments */
+ for (i = 0; i < 4 * TCP_MSS; i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ tcp_ticks = SEQNO1 - ISS;
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->mss = TCP_MSS;
+ /* set window to three segments */
+ pcb->cwnd = 3 * TCP_MSS;
+ pcb->snd_wnd = 3 * TCP_MSS;
+ pcb->snd_wnd_max = 3 * TCP_MSS;
+
+ /* send four segments. Fourth should stay buffered and is a 3/4 MSS segment to
+ get coverage on the oversized segment case */
+ err = tcp_write(pcb, &tx_data[0], (3 * TCP_MSS) + (TCP_MSS - (TCP_MSS / 4)), TCP_WRITE_FLAG_COPY);
+ EXPECT(err == ERR_OK);
+ err = tcp_output(pcb);
+ EXPECT(err == ERR_OK);
+
+ /* verify 3 segments are in-flight */
+ EXPECT(pcb->unacked != NULL);
+ check_seqnos(pcb->unacked, 3, seqnos);
+ EXPECT(txcounters.num_tx_calls == 3);
+ EXPECT(txcounters.num_tx_bytes == 3 * (TCP_MSS + 40U));
+ memset(&txcounters, 0, sizeof(txcounters));
+ /* verify 4th segment is on unsent */
+ EXPECT(pcb->unsent != NULL);
+ EXPECT(pcb->unsent->len == TCP_MSS - (TCP_MSS / 4));
+ check_seqnos(pcb->unsent, 1, &seqnos[3]);
+#if TCP_OVERSIZE
+ EXPECT(pcb->unsent_oversize == TCP_MSS / 4);
+#if TCP_OVERSIZE_DBGCHECK
+ EXPECT(pcb->unsent->oversize_left == pcb->unsent_oversize);
+#endif /* TCP_OVERSIZE_DBGCHECK */
+#endif /* TCP_OVERSIZE */
+
+ /* ACK the 3 segments and update the window to only 1/2 TCP_MSS.
+ 4th segment should stay on unsent because it's bigger than 1/2 MSS */
+ p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, 3 * TCP_MSS, TCP_ACK, TCP_MSS / 2);
+ test_tcp_input(p, &netif);
+ EXPECT(pcb->unacked == NULL);
+ EXPECT(pcb->snd_wnd == TCP_MSS / 2);
+ EXPECT(pcb->unsent != NULL);
+ check_seqnos(pcb->unsent, 1, &seqnos[3]);
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+ /* persist timer should be started since 4th segment is stuck waiting on snd_wnd */
+ EXPECT(pcb->persist_backoff == 1);
+
+ /* ensure no errors have been recorded */
+ EXPECT(counters.err_calls == 0);
+ EXPECT(counters.last_err == ERR_OK);
+
+ /* call tcp_timer some more times to let persist timer count up */
+ for (i = 0; i < 4; i++) {
+ test_tcp_tmr();
+ EXPECT(txcounters.num_tx_calls == 0);
+ EXPECT(txcounters.num_tx_bytes == 0);
+ }
+
+ /* this should be the first timer shot, which should split the
+ * segment and send a runt (of the remaining window size) */
+ txcounters.copy_tx_packets = 1;
+ test_tcp_tmr();
+ txcounters.copy_tx_packets = 0;
+ /* persist will be disabled as RTO timer takes over */
+ EXPECT(pcb->persist_backoff == 0);
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == ((TCP_MSS /2) + 40U));
+ /* verify 1/2 MSS segment sent, 1/4 MSS still buffered */
+ EXPECT(pcb->unsent != NULL);
+ EXPECT(pcb->unsent->len == TCP_MSS / 4);
+ EXPECT(pcb->unacked != NULL);
+ EXPECT(pcb->unacked->len == TCP_MSS / 2);
+#if TCP_OVERSIZE
+ /* verify there is no oversized remaining since during the
+ segment split, the remainder pbuf is always the exact length */
+ EXPECT(pcb->unsent_oversize == 0);
+#if TCP_OVERSIZE_DBGCHECK
+ /* Split segment already transmitted, should be at 0 */
+ EXPECT(pcb->unacked->oversize_left == 0);
+ /* Remainder segment should match pcb value (which is 0) */
+ EXPECT(pcb->unsent->oversize_left == pcb->unsent_oversize);
+#endif /* TCP_OVERSIZE_DBGCHECK */
+#endif /* TCP_OVERSIZE */
+
+ /* verify first half segment */
+ EXPECT(txcounters.tx_packets != NULL);
+ if (txcounters.tx_packets != NULL) {
+ u8_t sent[TCP_MSS / 2];
+ u16_t ret;
+ ret = pbuf_copy_partial(txcounters.tx_packets, &sent, TCP_MSS / 2, 40U);
+ EXPECT(ret == TCP_MSS / 2);
+ EXPECT(memcmp(sent, &tx_data[3 * TCP_MSS], TCP_MSS / 2) == 0);
+ }
+ if (txcounters.tx_packets != NULL) {
+ pbuf_free(txcounters.tx_packets);
+ txcounters.tx_packets = NULL;
+ }
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* ACK the half segment, leave window at half segment */
+ p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, TCP_MSS / 2, TCP_ACK, TCP_MSS / 2);
+ txcounters.copy_tx_packets = 1;
+ test_tcp_input(p, &netif);
+ txcounters.copy_tx_packets = 0;
+ /* ensure remaining segment was sent */
+ EXPECT(txcounters.num_tx_calls == 1);
+ EXPECT(txcounters.num_tx_bytes == ((TCP_MSS / 4) + 40U));
+ EXPECT(pcb->unsent == NULL);
+ EXPECT(pcb->unacked != NULL);
+ EXPECT(pcb->unacked->len == TCP_MSS / 4);
+ EXPECT(pcb->snd_wnd == TCP_MSS / 2);
+
+ /* verify remainder segment */
+ EXPECT(txcounters.tx_packets != NULL);
+ if (txcounters.tx_packets != NULL) {
+ u8_t sent[TCP_MSS / 4];
+ u16_t ret;
+ ret = pbuf_copy_partial(txcounters.tx_packets, &sent, TCP_MSS / 4, 40U);
+ EXPECT(ret == TCP_MSS / 4);
+ EXPECT(memcmp(sent, &tx_data[(3 * TCP_MSS) + TCP_MSS / 2], TCP_MSS / 4) == 0);
+ }
+ if (txcounters.tx_packets != NULL) {
+ pbuf_free(txcounters.tx_packets);
+ txcounters.tx_packets = NULL;
+ }
+
+ /* ensure no errors have been recorded */
+ EXPECT(counters.err_calls == 0);
+ EXPECT(counters.last_err == ERR_OK);
+
+ /* make sure the pcb is freed */
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+tcp_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_tcp_new_abort),
+ TESTFUNC(test_tcp_listen_passive_open),
+ TESTFUNC(test_tcp_recv_inseq),
+ TESTFUNC(test_tcp_recv_inseq_trim),
+ TESTFUNC(test_tcp_passive_close),
+ TESTFUNC(test_tcp_active_abort),
+ TESTFUNC(test_tcp_malformed_header),
+ TESTFUNC(test_tcp_fast_retx_recover),
+ TESTFUNC(test_tcp_fast_rexmit_wraparound),
+ TESTFUNC(test_tcp_rto_rexmit_wraparound),
+ TESTFUNC(test_tcp_tx_full_window_lost_from_unacked),
+ TESTFUNC(test_tcp_tx_full_window_lost_from_unsent),
+ TESTFUNC(test_tcp_retx_add_to_sent),
+ TESTFUNC(test_tcp_rto_tracking),
+ TESTFUNC(test_tcp_rto_timeout),
+ TESTFUNC(test_tcp_rto_timeout_link_down),
+ TESTFUNC(test_tcp_rto_timeout_syn_sent),
+ TESTFUNC(test_tcp_rto_timeout_syn_sent_link_down),
+ TESTFUNC(test_tcp_zwp_timeout),
+ TESTFUNC(test_tcp_zwp_timeout_link_down),
+ TESTFUNC(test_tcp_persist_split)
+ };
+ return create_suite("TCP", tests, sizeof(tests)/sizeof(testfunc), tcp_setup, tcp_teardown);
+}
diff --git a/test/unit/tcp/test_tcp.h b/test/unit/tcp/test_tcp.h
new file mode 100644
index 00000000000..f28ee56530e
--- /dev/null
+++ b/test/unit/tcp/test_tcp.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_TCP_H
+#define LWIP_HDR_TEST_TCP_H
+
+#include "../lwip_check.h"
+
+Suite *tcp_suite(void);
+
+#endif
diff --git a/test/unit/tcp/test_tcp_oos.c b/test/unit/tcp/test_tcp_oos.c
new file mode 100644
index 00000000000..a190e7dca3e
--- /dev/null
+++ b/test/unit/tcp/test_tcp_oos.c
@@ -0,0 +1,1018 @@
+#include "test_tcp_oos.h"
+
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/stats.h"
+#include "tcp_helper.h"
+
+#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS
+#error "This tests needs TCP- and MEMP-statistics enabled"
+#endif
+#if !TCP_QUEUE_OOSEQ
+#error "This tests needs TCP_QUEUE_OOSEQ enabled"
+#endif
+
+/** CHECK_SEGMENTS_ON_OOSEQ:
+ * 1: check count, seqno and len of segments on pcb->ooseq (strict)
+ * 0: only check that bytes are received in correct order (less strict) */
+#define CHECK_SEGMENTS_ON_OOSEQ 1
+
+#if CHECK_SEGMENTS_ON_OOSEQ
+#define EXPECT_OOSEQ(x) EXPECT(x)
+#else
+#define EXPECT_OOSEQ(x)
+#endif
+
+/* helper functions */
+
+/** Get the numbers of segments on the ooseq list */
+static int tcp_oos_count(struct tcp_pcb* pcb)
+{
+ int num = 0;
+ struct tcp_seg* seg = pcb->ooseq;
+ while(seg != NULL) {
+ num++;
+ seg = seg->next;
+ }
+ return num;
+}
+
+#if TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_PBUFS < ((TCP_WND / TCP_MSS) + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))
+/** Get the numbers of pbufs on the ooseq list */
+static int tcp_oos_pbuf_count(struct tcp_pcb* pcb)
+{
+ int num = 0;
+ struct tcp_seg* seg = pcb->ooseq;
+ while(seg != NULL) {
+ num += pbuf_clen(seg->p);
+ seg = seg->next;
+ }
+ return num;
+}
+#endif
+
+/** Get the seqno of a segment (by index) on the ooseq list
+ *
+ * @param pcb the pcb to check for ooseq segments
+ * @param seg_index index of the segment on the ooseq list
+ * @return seqno of the segment
+ */
+static u32_t
+tcp_oos_seg_seqno(struct tcp_pcb* pcb, int seg_index)
+{
+ int num = 0;
+ struct tcp_seg* seg = pcb->ooseq;
+
+ /* then check the actual segment */
+ while(seg != NULL) {
+ if(num == seg_index) {
+ return seg->tcphdr->seqno;
+ }
+ num++;
+ seg = seg->next;
+ }
+ fail();
+ return 0;
+}
+
+/** Get the tcplen (datalen + SYN/FIN) of a segment (by index) on the ooseq list
+ *
+ * @param pcb the pcb to check for ooseq segments
+ * @param seg_index index of the segment on the ooseq list
+ * @return tcplen of the segment
+ */
+static int
+tcp_oos_seg_tcplen(struct tcp_pcb* pcb, int seg_index)
+{
+ int num = 0;
+ struct tcp_seg* seg = pcb->ooseq;
+
+ /* then check the actual segment */
+ while(seg != NULL) {
+ if(num == seg_index) {
+ return TCP_TCPLEN(seg);
+ }
+ num++;
+ seg = seg->next;
+ }
+ fail();
+ return -1;
+}
+
+/** Get the tcplen (datalen + SYN/FIN) of all segments on the ooseq list
+ *
+ * @param pcb the pcb to check for ooseq segments
+ * @return tcplen of all segment
+ */
+static int
+tcp_oos_tcplen(struct tcp_pcb* pcb)
+{
+ int len = 0;
+ struct tcp_seg* seg = pcb->ooseq;
+
+ /* then check the actual segment */
+ while(seg != NULL) {
+ len += TCP_TCPLEN(seg);
+ seg = seg->next;
+ }
+ return len;
+}
+
+/* Setup/teardown functions */
+static struct netif *old_netif_list;
+static struct netif *old_netif_default;
+
+static void
+tcp_oos_setup(void)
+{
+ old_netif_list = netif_list;
+ old_netif_default = netif_default;
+ netif_list = NULL;
+ netif_default = NULL;
+ tcp_remove_all();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+tcp_oos_teardown(void)
+{
+ netif_list = NULL;
+ netif_default = NULL;
+ tcp_remove_all();
+ /* restore netif_list for next tests (e.g. loopif) */
+ netif_list = old_netif_list;
+ netif_default = old_netif_default;
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+
+
+/* Test functions */
+
+/** create multiple segments and pass them to tcp_input in a wrong
+ * order to see if ooseq-caching works correctly
+ * FIN is received in out-of-sequence segments only */
+START_TEST(test_tcp_recv_ooseq_FIN_OOSEQ)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *p_8_9, *p_4_8, *p_4_10, *p_2_14, *p_fin, *pinseq;
+ char data[] = {
+ 1, 2, 3, 4,
+ 5, 6, 7, 8,
+ 9, 10, 11, 12,
+ 13, 14, 15, 16};
+ u16_t data_len;
+ struct netif netif;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask);
+ data_len = sizeof(data);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = data_len;
+ counters.expected_data = data;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* create segments */
+ /* pinseq is sent as last segment! */
+ pinseq = tcp_create_rx_segment(pcb, &data[0], 4, 0, 0, TCP_ACK);
+ /* p1: 8 bytes before FIN */
+ /* seqno: 8..16 */
+ p_8_9 = tcp_create_rx_segment(pcb, &data[8], 8, 8, 0, TCP_ACK|TCP_FIN);
+ /* p2: 4 bytes before p1, including the first 4 bytes of p1 (partly duplicate) */
+ /* seqno: 4..11 */
+ p_4_8 = tcp_create_rx_segment(pcb, &data[4], 8, 4, 0, TCP_ACK);
+ /* p3: same as p2 but 2 bytes longer */
+ /* seqno: 4..13 */
+ p_4_10 = tcp_create_rx_segment(pcb, &data[4], 10, 4, 0, TCP_ACK);
+ /* p4: 14 bytes before FIN, includes data from p1 and p2, plus partly from pinseq */
+ /* seqno: 2..15 */
+ p_2_14 = tcp_create_rx_segment(pcb, &data[2], 14, 2, 0, TCP_ACK);
+ /* FIN, seqno 16 */
+ p_fin = tcp_create_rx_segment(pcb, NULL, 0,16, 0, TCP_ACK|TCP_FIN);
+ EXPECT(pinseq != NULL);
+ EXPECT(p_8_9 != NULL);
+ EXPECT(p_4_8 != NULL);
+ EXPECT(p_4_10 != NULL);
+ EXPECT(p_2_14 != NULL);
+ EXPECT(p_fin != NULL);
+ if ((pinseq != NULL) && (p_8_9 != NULL) && (p_4_8 != NULL) && (p_4_10 != NULL) && (p_2_14 != NULL) && (p_fin != NULL)) {
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_8_9, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 8);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 9); /* includes FIN */
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_4_8, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 4);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 4);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 8);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 9); /* includes FIN */
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_4_10, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* ooseq queue: unchanged */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 4);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 4);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 8);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 9); /* includes FIN */
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_2_14, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 15); /* includes FIN */
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_fin, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* ooseq queue: unchanged */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 15); /* includes FIN */
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(pinseq, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 1);
+ EXPECT(counters.recv_calls == 1);
+ EXPECT(counters.recved_bytes == data_len);
+ EXPECT(counters.err_calls == 0);
+ EXPECT(pcb->ooseq == NULL);
+ }
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+
+/** create multiple segments and pass them to tcp_input in a wrong
+ * order to see if ooseq-caching works correctly
+ * FIN is received IN-SEQUENCE at the end */
+START_TEST(test_tcp_recv_ooseq_FIN_INSEQ)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *p_1_2, *p_4_8, *p_3_11, *p_2_12, *p_15_1, *p_15_1a, *pinseq, *pinseqFIN;
+ char data[] = {
+ 1, 2, 3, 4,
+ 5, 6, 7, 8,
+ 9, 10, 11, 12,
+ 13, 14, 15, 16};
+ u16_t data_len;
+ struct netif netif;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask);
+ data_len = sizeof(data);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = data_len;
+ counters.expected_data = data;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* create segments */
+ /* p1: 7 bytes - 2 before FIN */
+ /* seqno: 1..2 */
+ p_1_2 = tcp_create_rx_segment(pcb, &data[1], 2, 1, 0, TCP_ACK);
+ /* p2: 4 bytes before p1, including the first 4 bytes of p1 (partly duplicate) */
+ /* seqno: 4..11 */
+ p_4_8 = tcp_create_rx_segment(pcb, &data[4], 8, 4, 0, TCP_ACK);
+ /* p3: same as p2 but 2 bytes longer and one byte more at the front */
+ /* seqno: 3..13 */
+ p_3_11 = tcp_create_rx_segment(pcb, &data[3], 11, 3, 0, TCP_ACK);
+ /* p4: 13 bytes - 2 before FIN - should be ignored as contained in p1 and p3 */
+ /* seqno: 2..13 */
+ p_2_12 = tcp_create_rx_segment(pcb, &data[2], 12, 2, 0, TCP_ACK);
+ /* pinseq is the first segment that is held back to create ooseq! */
+ /* seqno: 0..3 */
+ pinseq = tcp_create_rx_segment(pcb, &data[0], 4, 0, 0, TCP_ACK);
+ /* p5: last byte before FIN */
+ /* seqno: 15 */
+ p_15_1 = tcp_create_rx_segment(pcb, &data[15], 1, 15, 0, TCP_ACK);
+ /* p6: same as p5, should be ignored */
+ p_15_1a= tcp_create_rx_segment(pcb, &data[15], 1, 15, 0, TCP_ACK);
+ /* pinseqFIN: last 2 bytes plus FIN */
+ /* only segment containing seqno 14 and FIN */
+ pinseqFIN = tcp_create_rx_segment(pcb, &data[14], 2, 14, 0, TCP_ACK|TCP_FIN);
+ EXPECT(pinseq != NULL);
+ EXPECT(p_1_2 != NULL);
+ EXPECT(p_4_8 != NULL);
+ EXPECT(p_3_11 != NULL);
+ EXPECT(p_2_12 != NULL);
+ EXPECT(p_15_1 != NULL);
+ EXPECT(p_15_1a != NULL);
+ EXPECT(pinseqFIN != NULL);
+ if ((pinseq != NULL) && (p_1_2 != NULL) && (p_4_8 != NULL) && (p_3_11 != NULL) && (p_2_12 != NULL)
+ && (p_15_1 != NULL) && (p_15_1a != NULL) && (pinseqFIN != NULL)) {
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_1_2, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2);
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_4_8, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 4);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 8);
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_3_11, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2);
+ /* p_3_11 has removed p_4_8 from ooseq */
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 3);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 11);
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_2_12, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 2);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 12);
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(pinseq, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 1);
+ EXPECT(counters.recved_bytes == 14);
+ EXPECT(counters.err_calls == 0);
+ EXPECT(pcb->ooseq == NULL);
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_15_1, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 1);
+ EXPECT(counters.recved_bytes == 14);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 15);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1);
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_15_1a, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 1);
+ EXPECT(counters.recved_bytes == 14);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue: unchanged */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == 1);
+ EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 15);
+ EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1);
+
+ /* pass the segment to tcp_input */
+ test_tcp_input(pinseqFIN, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 1);
+ EXPECT(counters.recv_calls == 2);
+ EXPECT(counters.recved_bytes == data_len);
+ EXPECT(counters.err_calls == 0);
+ EXPECT(pcb->ooseq == NULL);
+ }
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+static char data_full_wnd[TCP_WND + TCP_MSS];
+
+/** create multiple segments and pass them to tcp_input with the first segment missing
+ * to simulate overruning the rxwin with ooseq queueing enabled */
+START_TEST(test_tcp_recv_ooseq_overrun_rxwin)
+{
+#if !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS
+ int i, k;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *pinseq, *p_ovr;
+ struct netif netif;
+ int datalen = 0;
+ int datalen2;
+
+ for(i = 0; i < (int)sizeof(data_full_wnd); i++) {
+ data_full_wnd[i] = (char)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = TCP_WND;
+ counters.expected_data = data_full_wnd;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->rcv_nxt = 0x8000;
+
+ /* create segments */
+ /* pinseq is sent as last segment! */
+ pinseq = tcp_create_rx_segment(pcb, &data_full_wnd[0], TCP_MSS, 0, 0, TCP_ACK);
+
+ for(i = TCP_MSS, k = 0; i < TCP_WND; i += TCP_MSS, k++) {
+ int count, expected_datalen;
+ struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)],
+ TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ count = tcp_oos_count(pcb);
+ EXPECT_OOSEQ(count == k+1);
+ datalen = tcp_oos_tcplen(pcb);
+ if (i + TCP_MSS < TCP_WND) {
+ expected_datalen = (k+1)*TCP_MSS;
+ } else {
+ expected_datalen = TCP_WND - TCP_MSS;
+ }
+ if (datalen != expected_datalen) {
+ EXPECT_OOSEQ(datalen == expected_datalen);
+ }
+ }
+
+ /* pass in one more segment, cleary overrunning the rxwin */
+ p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)], TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK);
+ EXPECT_RET(p_ovr != NULL);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_ovr, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == k);
+ datalen2 = tcp_oos_tcplen(pcb);
+ EXPECT_OOSEQ(datalen == datalen2);
+
+ /* now pass inseq */
+ test_tcp_input(pinseq, &netif);
+ EXPECT(pcb->ooseq == NULL);
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+#endif /* !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS */
+ LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+/** similar to above test, except seqno starts near the max rxwin */
+START_TEST(test_tcp_recv_ooseq_overrun_rxwin_edge)
+{
+#if !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS
+ int i, k;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *pinseq, *p_ovr;
+ struct netif netif;
+ int datalen = 0;
+ int datalen2;
+
+ for(i = 0; i < (int)sizeof(data_full_wnd); i++) {
+ data_full_wnd[i] = (char)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = TCP_WND;
+ counters.expected_data = data_full_wnd;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->rcv_nxt = 0xffffffff - (TCP_WND / 2);
+
+ /* create segments */
+ /* pinseq is sent as last segment! */
+ pinseq = tcp_create_rx_segment(pcb, &data_full_wnd[0], TCP_MSS, 0, 0, TCP_ACK);
+
+ for(i = TCP_MSS, k = 0; i < TCP_WND; i += TCP_MSS, k++) {
+ int count, expected_datalen;
+ struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)],
+ TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ count = tcp_oos_count(pcb);
+ EXPECT_OOSEQ(count == k+1);
+ datalen = tcp_oos_tcplen(pcb);
+ if (i + TCP_MSS < TCP_WND) {
+ expected_datalen = (k+1)*TCP_MSS;
+ } else {
+ expected_datalen = TCP_WND - TCP_MSS;
+ }
+ if (datalen != expected_datalen) {
+ EXPECT_OOSEQ(datalen == expected_datalen);
+ }
+ }
+
+ /* pass in one more segment, cleary overrunning the rxwin */
+ p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)], TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK);
+ EXPECT_RET(p_ovr != NULL);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_ovr, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == k);
+ datalen2 = tcp_oos_tcplen(pcb);
+ EXPECT_OOSEQ(datalen == datalen2);
+
+ /* now pass inseq */
+ test_tcp_input(pinseq, &netif);
+ EXPECT(pcb->ooseq == NULL);
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+#endif /* !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS */
+ LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+START_TEST(test_tcp_recv_ooseq_max_bytes)
+{
+#if TCP_OOSEQ_MAX_BYTES && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))
+ int i, k;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *p_ovr;
+ struct netif netif;
+ int datalen = 0;
+ int datalen2;
+
+ for(i = 0; i < sizeof(data_full_wnd); i++) {
+ data_full_wnd[i] = (char)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = TCP_WND;
+ counters.expected_data = data_full_wnd;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->rcv_nxt = 0x8000;
+
+ /* don't 'recv' the first segment (1 byte) so that all other segments will be ooseq */
+
+ /* create segments and 'recv' them */
+ for(k = 1, i = 1; k < TCP_OOSEQ_MAX_BYTES; k += TCP_MSS, i++) {
+ int count;
+ struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[k],
+ TCP_MSS, k, 0, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ EXPECT_RET(p->next == NULL);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ count = tcp_oos_pbuf_count(pcb);
+ EXPECT_OOSEQ(count == i);
+ datalen = tcp_oos_tcplen(pcb);
+ EXPECT_OOSEQ(datalen == (i * TCP_MSS));
+ }
+
+ /* pass in one more segment, overrunning the limit */
+ p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[k+1], 1, k+1, 0, TCP_ACK);
+ EXPECT_RET(p_ovr != NULL);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_ovr, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue (ensure the new segment was not accepted) */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == (i-1));
+ datalen2 = tcp_oos_tcplen(pcb);
+ EXPECT_OOSEQ(datalen2 == ((i-1) * TCP_MSS));
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+#endif /* TCP_OOSEQ_MAX_BYTES && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) */
+ LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+START_TEST(test_tcp_recv_ooseq_max_pbufs)
+{
+#if TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_PBUFS < ((TCP_WND / TCP_MSS) + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))
+ int i;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *p_ovr;
+ struct netif netif;
+ int datalen = 0;
+ int datalen2;
+
+ for(i = 0; i < sizeof(data_full_wnd); i++) {
+ data_full_wnd[i] = (char)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = TCP_WND;
+ counters.expected_data = data_full_wnd;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->rcv_nxt = 0x8000;
+
+ /* don't 'recv' the first segment (1 byte) so that all other segments will be ooseq */
+
+ /* create segments and 'recv' them */
+ for(i = 1; i <= TCP_OOSEQ_MAX_PBUFS; i++) {
+ int count;
+ struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[i],
+ 1, i, 0, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ EXPECT_RET(p->next == NULL);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue */
+ count = tcp_oos_pbuf_count(pcb);
+ EXPECT_OOSEQ(count == i);
+ datalen = tcp_oos_tcplen(pcb);
+ EXPECT_OOSEQ(datalen == i);
+ }
+
+ /* pass in one more segment, overrunning the limit */
+ p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[i+1], 1, i+1, 0, TCP_ACK);
+ EXPECT_RET(p_ovr != NULL);
+ /* pass the segment to tcp_input */
+ test_tcp_input(p_ovr, &netif);
+ /* check if counters are as expected */
+ EXPECT(counters.close_calls == 0);
+ EXPECT(counters.recv_calls == 0);
+ EXPECT(counters.recved_bytes == 0);
+ EXPECT(counters.err_calls == 0);
+ /* check ooseq queue (ensure the new segment was not accepted) */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == (i-1));
+ datalen2 = tcp_oos_tcplen(pcb);
+ EXPECT_OOSEQ(datalen2 == (i-1));
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+#endif /* TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) */
+ LWIP_UNUSED_ARG(_i);
+}
+END_TEST
+
+static void
+check_rx_counters(struct tcp_pcb *pcb, struct test_tcp_counters *counters, u32_t exp_close_calls, u32_t exp_rx_calls,
+ u32_t exp_rx_bytes, u32_t exp_err_calls, int exp_oos_count, int exp_oos_len)
+{
+ int oos_len;
+ EXPECT(counters->close_calls == exp_close_calls);
+ EXPECT(counters->recv_calls == exp_rx_calls);
+ EXPECT(counters->recved_bytes == exp_rx_bytes);
+ EXPECT(counters->err_calls == exp_err_calls);
+ /* check that pbuf is queued in ooseq */
+ EXPECT_OOSEQ(tcp_oos_count(pcb) == exp_oos_count);
+ oos_len = tcp_oos_tcplen(pcb);
+ EXPECT_OOSEQ(exp_oos_len == oos_len);
+}
+
+/* this test uses 4 packets:
+ * - data (len=TCP_MSS)
+ * - FIN
+ * - data after FIN (len=1) (invalid)
+ * - 2nd FIN (invalid)
+ *
+ * the parameter 'delay_packet' is a bitmask that choses which on these packets is ooseq
+ */
+static void test_tcp_recv_ooseq_double_FINs(int delay_packet)
+{
+ int i, k;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *p_normal_fin, *p_data_after_fin, *p, *p_2nd_fin_ooseq;
+ struct netif netif;
+ u32_t exp_rx_calls = 0, exp_rx_bytes = 0, exp_close_calls = 0, exp_oos_pbufs = 0, exp_oos_tcplen = 0;
+ int first_dropped = 0xff;
+
+ for(i = 0; i < (int)sizeof(data_full_wnd); i++) {
+ data_full_wnd[i] = (char)i;
+ }
+
+ /* initialize local vars */
+ test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask);
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = TCP_WND;
+ counters.expected_data = data_full_wnd;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb->rcv_nxt = 0x8000;
+
+ /* create segments */
+ p = tcp_create_rx_segment(pcb, &data_full_wnd[0], TCP_MSS, 0, 0, TCP_ACK);
+ p_normal_fin = tcp_create_rx_segment(pcb, NULL, 0, TCP_MSS, 0, TCP_ACK|TCP_FIN);
+ k = 1;
+ p_data_after_fin = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS+1], k, TCP_MSS+1, 0, TCP_ACK);
+ p_2nd_fin_ooseq = tcp_create_rx_segment(pcb, NULL, 0, TCP_MSS+1+k, 0, TCP_ACK|TCP_FIN);
+
+ if(delay_packet & 1) {
+ /* drop normal data */
+ first_dropped = 1;
+ } else {
+ /* send normal data */
+ test_tcp_input(p, &netif);
+ exp_rx_calls++;
+ exp_rx_bytes += TCP_MSS;
+ }
+ /* check if counters are as expected */
+ check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+ if(delay_packet & 2) {
+ /* drop FIN */
+ if(first_dropped > 2) {
+ first_dropped = 2;
+ }
+ } else {
+ /* send FIN */
+ test_tcp_input(p_normal_fin, &netif);
+ if (first_dropped < 2) {
+ /* already dropped packets, this one is ooseq */
+ exp_oos_pbufs++;
+ exp_oos_tcplen++;
+ } else {
+ /* inseq */
+ exp_close_calls++;
+ }
+ }
+ /* check if counters are as expected */
+ check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+ if(delay_packet & 4) {
+ /* drop data-after-FIN */
+ if(first_dropped > 3) {
+ first_dropped = 3;
+ }
+ } else {
+ /* send data-after-FIN */
+ test_tcp_input(p_data_after_fin, &netif);
+ if (first_dropped < 3) {
+ /* already dropped packets, this one is ooseq */
+ if (delay_packet & 2) {
+ /* correct FIN was ooseq */
+ exp_oos_pbufs++;
+ exp_oos_tcplen += k;
+ }
+ } else {
+ /* inseq: no change */
+ }
+ }
+ /* check if counters are as expected */
+ check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+ if(delay_packet & 8) {
+ /* drop 2nd-FIN */
+ if(first_dropped > 4) {
+ first_dropped = 4;
+ }
+ } else {
+ /* send 2nd-FIN */
+ test_tcp_input(p_2nd_fin_ooseq, &netif);
+ if (first_dropped < 3) {
+ /* already dropped packets, this one is ooseq */
+ if (delay_packet & 2) {
+ /* correct FIN was ooseq */
+ exp_oos_pbufs++;
+ exp_oos_tcplen++;
+ }
+ } else {
+ /* inseq: no change */
+ }
+ }
+ /* check if counters are as expected */
+ check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+ if(delay_packet & 1) {
+ /* dropped normal data before */
+ test_tcp_input(p, &netif);
+ exp_rx_calls++;
+ exp_rx_bytes += TCP_MSS;
+ if((delay_packet & 2) == 0) {
+ /* normal FIN was NOT delayed */
+ exp_close_calls++;
+ exp_oos_pbufs = exp_oos_tcplen = 0;
+ }
+ }
+ /* check if counters are as expected */
+ check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+ if(delay_packet & 2) {
+ /* dropped normal FIN before */
+ test_tcp_input(p_normal_fin, &netif);
+ exp_close_calls++;
+ exp_oos_pbufs = exp_oos_tcplen = 0;
+ }
+ /* check if counters are as expected */
+ check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+ if(delay_packet & 4) {
+ /* dropped data-after-FIN before */
+ test_tcp_input(p_data_after_fin, &netif);
+ }
+ /* check if counters are as expected */
+ check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+ if(delay_packet & 8) {
+ /* dropped 2nd-FIN before */
+ test_tcp_input(p_2nd_fin_ooseq, &netif);
+ }
+ /* check if counters are as expected */
+ check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen);
+
+ /* check that ooseq data has been dumped */
+ EXPECT(pcb->ooseq == NULL);
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+
+/** create multiple segments and pass them to tcp_input with the first segment missing
+ * to simulate overruning the rxwin with ooseq queueing enabled */
+#define FIN_TEST(name, num) \
+ START_TEST(name) \
+ { \
+ LWIP_UNUSED_ARG(_i); \
+ test_tcp_recv_ooseq_double_FINs(num); \
+ } \
+ END_TEST
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_0, 0)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_1, 1)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_2, 2)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_3, 3)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_4, 4)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_5, 5)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_6, 6)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_7, 7)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_8, 8)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_9, 9)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_10, 10)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_11, 11)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_12, 12)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_13, 13)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_14, 14)
+FIN_TEST(test_tcp_recv_ooseq_double_FIN_15, 15)
+
+
+/** Create the suite including all tests for this module */
+Suite *
+tcp_oos_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_tcp_recv_ooseq_FIN_OOSEQ),
+ TESTFUNC(test_tcp_recv_ooseq_FIN_INSEQ),
+ TESTFUNC(test_tcp_recv_ooseq_overrun_rxwin),
+ TESTFUNC(test_tcp_recv_ooseq_overrun_rxwin_edge),
+ TESTFUNC(test_tcp_recv_ooseq_max_bytes),
+ TESTFUNC(test_tcp_recv_ooseq_max_pbufs),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_0),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_1),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_2),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_3),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_4),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_5),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_6),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_7),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_8),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_9),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_10),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_11),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_12),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_13),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_14),
+ TESTFUNC(test_tcp_recv_ooseq_double_FIN_15)
+ };
+ return create_suite("TCP_OOS", tests, sizeof(tests)/sizeof(testfunc), tcp_oos_setup, tcp_oos_teardown);
+}
diff --git a/test/unit/tcp/test_tcp_oos.h b/test/unit/tcp/test_tcp_oos.h
new file mode 100644
index 00000000000..5b82013b73b
--- /dev/null
+++ b/test/unit/tcp/test_tcp_oos.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_TCP_OOS_H
+#define LWIP_HDR_TEST_TCP_OOS_H
+
+#include "../lwip_check.h"
+
+Suite *tcp_oos_suite(void);
+
+#endif
diff --git a/test/unit/tcp/test_tcp_state.c b/test/unit/tcp/test_tcp_state.c
new file mode 100644
index 00000000000..afd21fce626
--- /dev/null
+++ b/test/unit/tcp/test_tcp_state.c
@@ -0,0 +1,665 @@
+#include "test_tcp_state.h"
+
+#include "lwip/priv/tcp_priv.h"
+#include "lwip/stats.h"
+#include "tcp_helper.h"
+#include "lwip/inet_chksum.h"
+
+#ifdef _MSC_VER
+#pragma warning(disable: 4307) /* we explicitly wrap around TCP seqnos */
+#endif
+
+#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS
+#error "This tests needs TCP- and MEMP-statistics enabled"
+#endif
+
+static struct netif test_netif = {0};
+static struct test_tcp_txcounters test_txcounters = {0};
+
+#define SEQNO1 (0xFFFFFF00 - TCP_MSS)
+#define ISS 6510
+static u8_t test_tcp_timer;
+
+/* our own version of tcp_tmr so we can reset fast/slow timer state */
+static void
+test_tcp_tmr(void)
+{
+ tcp_fasttmr();
+ if (++test_tcp_timer & 1) {
+ tcp_slowtmr();
+ }
+}
+
+/* Get TCP flags from packets */
+static u8_t
+get_tcp_flags_from_packet(struct pbuf *p, u16_t tcp_hdr_offset)
+{
+ struct tcp_hdr tcphdr;
+ u16_t ret;
+ EXPECT_RETX(p != NULL, 0);
+ EXPECT_RETX(p->len >= tcp_hdr_offset + sizeof(struct tcp_hdr), 0);
+ ret = pbuf_copy_partial(p, &tcphdr, sizeof(struct tcp_hdr), tcp_hdr_offset);
+ EXPECT(ret == sizeof(struct tcp_hdr));
+ return TCPH_FLAGS(&tcphdr);
+}
+
+/* Create listening tcp_pcb */
+static struct tcp_pcb_listen *
+create_listening_pcb(u16_t local_port, struct test_tcp_counters *counters)
+{
+ struct tcp_pcb *pcb;
+ struct tcp_pcb_listen *lpcb=NULL;
+ err_t err;
+ u16_t port = local_port?local_port:1234;
+
+ if (counters) {
+ pcb = test_tcp_new_counters_pcb(counters);
+ } else {
+ pcb = tcp_new();
+ }
+ EXPECT(pcb != NULL);
+
+ if (pcb) {
+ err = tcp_bind(pcb, &test_netif.ip_addr, port);
+ EXPECT(err == ERR_OK);
+ lpcb = (struct tcp_pcb_listen *)tcp_listen(pcb);
+ }
+
+ return lpcb;
+}
+
+/* Setup/teardown functions */
+static struct netif* old_netif_list;
+static struct netif* old_netif_default;
+
+static void
+tcp_state_setup(void)
+{
+ struct tcp_pcb dummy_pcb; /* we need this for tcp_next_iss() only */
+
+ /* reset iss to default (6510) */
+ tcp_ticks = 0;
+ tcp_ticks = 0 - (tcp_next_iss(&dummy_pcb) - 6510);
+ tcp_next_iss(&dummy_pcb);
+ tcp_ticks = 0;
+
+ test_tcp_timer = 0;
+
+ old_netif_list = netif_list;
+ old_netif_default = netif_default;
+ netif_list = NULL;
+ netif_default = NULL;
+ tcp_remove_all();
+ test_tcp_init_netif(&test_netif, &test_txcounters, &test_local_ip, &test_netmask);
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+tcp_state_teardown(void)
+{
+ netif_list = NULL;
+ netif_default = NULL;
+ tcp_remove_all();
+ /* restore netif_list for next tests (e.g. loopif) */
+ netif_list = old_netif_list;
+ netif_default = old_netif_default;
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+/* helper functions */
+
+static void
+test_rst_generation_with_incoming_packet(struct pbuf *p,
+ struct netif *netif, struct test_tcp_txcounters *tx_counters)
+{
+ u16_t tcp_flags;
+ EXPECT_RET(p != NULL);
+ memset(tx_counters, 0, sizeof(struct test_tcp_txcounters));
+ /* pass the segment to tcp_input */
+ tx_counters->copy_tx_packets = 1;
+ test_tcp_input(p, netif);
+ tx_counters->copy_tx_packets = 0;
+ /* check if packets are as expected */
+ EXPECT(tx_counters->tx_packets != NULL);
+ if (tx_counters->tx_packets) {
+ tcp_flags = get_tcp_flags_from_packet(tx_counters->tx_packets, 20);
+ EXPECT(tcp_flags & TCP_RST);
+ pbuf_free(tx_counters->tx_packets);
+ tx_counters->tx_packets = NULL;
+ }
+}
+
+/* Test functions */
+
+/* Call tcp_new() and test memp stats (max number) */
+START_TEST(test_tcp_new_max_num)
+{
+ struct tcp_pcb* pcb[MEMP_NUM_TCP_PCB + 1];
+ int i;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+
+ for(i = 0;i < MEMP_NUM_TCP_PCB; i++) {
+ pcb[i] = tcp_new();
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == (i + 1));
+ }
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == MEMP_NUM_TCP_PCB);
+ /* Trying to remove the oldest pcb in TIME_WAIT,LAST_ACK,CLOSING state when pcb full */
+ pcb[MEMP_NUM_TCP_PCB] = tcp_new();
+ fail_unless(pcb[MEMP_NUM_TCP_PCB] == NULL);
+ tcp_set_state(pcb[0], TIME_WAIT, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ pcb[MEMP_NUM_TCP_PCB] = tcp_new();
+ fail_unless(pcb[MEMP_NUM_TCP_PCB] != NULL);
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == MEMP_NUM_TCP_PCB);
+
+ for (i = 1; i <= MEMP_NUM_TCP_PCB; i++)
+ {
+ tcp_abort(pcb[i]);
+ }
+ fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+
+/* pcbs in TIME_WAIT state will be deleted when creating new pcb reach the max number */
+START_TEST(test_tcp_new_max_num_remove_TIME_WAIT)
+{
+ struct tcp_pcb* pcb;
+ struct tcp_pcb* pcb_list[MEMP_NUM_TCP_PCB + 1];
+ int i;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create a pcb in TIME_WAIT state */
+ pcb = tcp_new();
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, TIME_WAIT, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ EXPECT_RET(pcb->state == TIME_WAIT);
+
+ /* Create max number pcbs */
+ for(i = 0;i < MEMP_NUM_TCP_PCB-1; i++) {
+ pcb_list[i] = tcp_new();
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == (i + 2));
+ }
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == MEMP_NUM_TCP_PCB);
+
+ /* Create one more pcb, and expect that the pcb in the TIME_WAIT state is deleted */
+ pcb_list[MEMP_NUM_TCP_PCB-1] = tcp_new();
+ EXPECT_RET(pcb_list[MEMP_NUM_TCP_PCB-1] != NULL);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == MEMP_NUM_TCP_PCB);
+
+ for (i = 0; i <= MEMP_NUM_TCP_PCB-1; i++)
+ {
+ tcp_abort(pcb_list[i]);
+ }
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+
+}
+END_TEST
+
+
+/* Call tcp_connect to check active open */
+START_TEST(test_tcp_connect_active_open)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb *pcb;
+ struct pbuf *p;
+ err_t err;
+ u16_t test_port = 1234;
+ u32_t seqno = 0;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create and initialize the pcb */
+ tcp_ticks = SEQNO1 - ISS;
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+
+ /* Get seqno from SYN packet */
+ test_txcounters.copy_tx_packets = 1;
+ err = tcp_connect(pcb, &test_remote_ip, test_port, NULL);
+ test_txcounters.copy_tx_packets = 0;
+ EXPECT(err == ERR_OK);
+ EXPECT(pcb->state == SYN_SENT);
+ EXPECT(test_txcounters.num_tx_calls == 1);
+ EXPECT_RET(test_txcounters.tx_packets != NULL);
+ if (test_txcounters.tx_packets != NULL) {
+ struct tcp_hdr tcphdr;
+ u16_t ret;
+ ret = pbuf_copy_partial(test_txcounters.tx_packets, &tcphdr, 20, 20);
+ EXPECT(ret == 20);
+ EXPECT(TCPH_FLAGS(&tcphdr) & TCP_SYN);
+ pbuf_free(test_txcounters.tx_packets);
+ test_txcounters.tx_packets = NULL;
+ seqno = lwip_htonl(tcphdr.seqno);
+ EXPECT(seqno == pcb->lastack);
+ }
+
+ /* check correct syn packet */
+ p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, test_port,
+ pcb->local_port, NULL, 0, 12345, seqno + 1, TCP_SYN|TCP_ACK);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &test_netif);
+ EXPECT_RET(pcb->state == ESTABLISHED);
+ EXPECT_RET(test_txcounters.num_tx_calls == 2);
+
+ /* make sure the pcb is freed */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ tcp_abort(pcb);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+START_TEST(test_tcp_active_close)
+{
+ struct tcp_pcb *pcb, *pcbl;
+ struct test_tcp_counters counters;
+ struct pbuf *p;
+ err_t err;
+ u32_t i;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create TCP in LISTEN state */
+ memset(&counters, 0, sizeof(counters));
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ err = tcp_bind(pcb, &test_netif.ip_addr, 1234);
+ EXPECT_RET(err == ERR_OK);
+ pcbl = tcp_listen(pcb);
+ EXPECT_RET(pcbl != NULL);
+ EXPECT_RET(pcbl->state == LISTEN);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 1);
+
+ memset(&test_txcounters, 0, sizeof(test_txcounters));
+ err = tcp_close(pcbl);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 0);
+ EXPECT(test_txcounters.num_tx_calls == 0);
+
+ /* close TCP in SYN_SENT state */
+ memset(&counters, 0, sizeof(counters));
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ err = tcp_connect(pcb, &test_netif.gw, 1234, NULL);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(pcb->state == SYN_SENT);
+ EXPECT(test_txcounters.num_tx_calls == 1);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+
+ memset(&test_txcounters, 0, sizeof(test_txcounters));
+ err = tcp_close(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+ EXPECT(test_txcounters.num_tx_calls == 0);
+
+ /* close TCP in ESTABLISHED state */
+ memset(&counters, 0, sizeof(counters));
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+
+ memset(&test_txcounters, 0, sizeof(test_txcounters));
+ err = tcp_close(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(pcb->state == FIN_WAIT_1);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ /* test_tcp_tmr(); */
+ EXPECT(test_txcounters.num_tx_calls == 1);
+ /* create a segment ACK and pass it to tcp_input */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 1, TCP_ACK);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &test_netif);
+ EXPECT_RET(pcb->state == FIN_WAIT_2);
+ /* create a segment FIN and pass it to tcp_input */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_FIN);
+ EXPECT_RET(p != NULL);
+ test_tcp_input(p, &test_netif);
+ EXPECT_RET(pcb->state == TIME_WAIT);
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ for (i = 0; i < 2 * TCP_MSL / TCP_TMR_INTERVAL + 1; i++) {
+ test_tcp_tmr();
+ }
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+START_TEST(test_tcp_imultaneous_close)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf* p;
+ char data = 0x0f;
+ err_t err;
+ u32_t i;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize counter struct */
+ memset(&counters, 0, sizeof(counters));
+ counters.expected_data_len = 1;
+ counters.expected_data = &data;
+
+ /* create and initialize the pcb */
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+ err = tcp_close(pcb);
+ EXPECT_RET(err == ERR_OK);
+ EXPECT_RET(pcb->state == FIN_WAIT_1);
+ /* create a FIN segment */
+ p = tcp_create_rx_segment(pcb, &data, 0, 0, 0, TCP_FIN);
+ EXPECT(p != NULL);
+ if (p != NULL) {
+ test_tcp_input(p, &test_netif);
+ }
+ EXPECT_RET(pcb->state == CLOSING);
+ /* create an ACK segment */
+ p = tcp_create_rx_segment(pcb, &data, 0, 0, 1, TCP_ACK);
+ EXPECT(p != NULL);
+ if (p != NULL) {
+ test_tcp_input(p, &test_netif);
+ }
+ EXPECT_RET(pcb->state == TIME_WAIT);
+ for (i = 0; i < 2 * TCP_MSL / TCP_TMR_INTERVAL + 1; i++) {
+ test_tcp_tmr();
+ }
+ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+}
+END_TEST
+
+
+/* RST was generated when receive any incoming segment in CLOSED state */
+START_TEST(test_tcp_gen_rst_in_CLOSED)
+{
+ struct pbuf *p;
+ ip_addr_t src_addr = test_remote_ip;
+ ip_addr_t dst_addr = test_local_ip;
+ LWIP_UNUSED_ARG(_i);
+
+ /* Do not create any pcb */
+
+ /* create a segment */
+ p = tcp_create_segment(&src_addr, &dst_addr, TEST_REMOTE_PORT,
+ TEST_LOCAL_PORT, NULL, 0, 12345, 54321, TCP_ACK);
+ EXPECT(p != NULL);
+ test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters);
+ EXPECT(test_txcounters.num_tx_calls == 1);
+
+}
+END_TEST
+
+/* RST was generated when receive ACK in LISTEN state */
+START_TEST(test_tcp_gen_rst_in_LISTEN)
+{
+ struct tcp_pcb_listen *lpcb;
+ struct pbuf *p;
+ ip_addr_t src_addr = test_remote_ip;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create a pcb in LISTEN state */
+ lpcb = create_listening_pcb(TEST_LOCAL_PORT, NULL);
+ EXPECT_RET(lpcb != NULL);
+
+ /* create a segment */
+ p = tcp_create_segment(&src_addr,&lpcb->local_ip, TEST_REMOTE_PORT,
+ lpcb->local_port, NULL, 0, 12345, 54321, TCP_ACK);
+ EXPECT(p != NULL);
+ test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters);
+ EXPECT(test_txcounters.num_tx_calls == 1);
+
+ /* the PCB still in LISTEN state */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 1);
+ if (MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) != 0) {
+ /* can not use tcp_abort() */
+ tcp_close((struct tcp_pcb *)lpcb);
+ }
+
+}
+END_TEST
+
+
+/* RST was generated when receive an SYN in TIME_WAIT state */
+START_TEST(test_tcp_gen_rst_in_TIME_WAIT)
+{
+ struct tcp_pcb *pcb;
+ struct pbuf *p;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create a pcb in LISTEN state */
+ pcb = tcp_new();
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, TIME_WAIT, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* create a segment */
+ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_SYN);
+ EXPECT(p != NULL);
+ test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters);
+ EXPECT(test_txcounters.num_tx_calls == 1);
+
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ EXPECT(pcb->state == TIME_WAIT);
+}
+END_TEST
+
+/* receive TCP_RST with different seqno */
+START_TEST(test_tcp_process_rst_seqno)
+{
+ struct test_tcp_counters counters;
+ struct tcp_pcb *pcb;
+ struct pbuf *p;
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create and initialize a pcb in SYN_SENT state */
+ memset(&counters, 0, sizeof(counters));
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ err = tcp_connect(pcb, &test_remote_ip, TEST_REMOTE_PORT, NULL);
+ EXPECT_RET(err == ERR_OK);
+
+ /* a RST segment with incorrect seqno will not be accepted */
+ p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, TEST_REMOTE_PORT,
+ pcb->local_port, NULL, 0, 12345, pcb->snd_nxt-10, TCP_RST);
+ EXPECT(p != NULL);
+ test_tcp_input(p, &test_netif);
+ EXPECT(counters.err_calls == 0);
+
+ /* a RST segment with correct seqno will be accepted */
+ p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, TEST_REMOTE_PORT,
+ pcb->local_port, NULL, 0, 12345, pcb->snd_nxt, TCP_RST);
+ EXPECT(p != NULL);
+ test_tcp_input(p, &test_netif);
+ EXPECT(counters.err_calls == 1);
+ counters.err_calls = 0;
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+
+ /* create another pcb in ESTABLISHED state */
+ memset(&counters, 0, sizeof(counters));
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT);
+
+ /* a RST segment with incorrect seqno will not be accepted */
+ p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port,
+ pcb->local_port, NULL, 0, pcb->rcv_nxt-10, 54321, TCP_RST);
+ EXPECT(p != NULL);
+ test_tcp_input(p, &test_netif);
+ EXPECT(counters.err_calls == 0);
+
+ /* a RST segment with correct seqno will be accepted */
+ p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, TEST_REMOTE_PORT,
+ pcb->local_port, NULL, 0, pcb->rcv_nxt, 54321, TCP_RST);
+ EXPECT(p != NULL);
+ test_tcp_input(p, &test_netif);
+ EXPECT(counters.err_calls == 1);
+ counters.err_calls = 0;
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+
+}
+END_TEST
+
+/* RST was generated when receive an SYN+ACK with incorrect ACK number in SYN_SENT state */
+START_TEST(test_tcp_gen_rst_in_SYN_SENT_ackseq)
+{
+ struct tcp_pcb *pcb;
+ struct pbuf *p;
+ u16_t test_port = 1234;
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create and initialize a pcb in listen state */
+ pcb = tcp_new();
+ EXPECT_RET(pcb != NULL);
+ err = tcp_connect(pcb, &test_remote_ip, test_port, NULL);
+ EXPECT_RET(err == ERR_OK);
+
+ /* create a SYN+ACK segment with incorrect seqno */
+ p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port,
+ pcb->local_port, NULL, 0, 12345, pcb->lastack-10, TCP_SYN|TCP_ACK);
+ EXPECT(p != NULL);
+ test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters);
+
+ /* LWIP: send RST then re-send SYN immediately */
+ EXPECT(test_txcounters.num_tx_calls == 2);
+
+}
+END_TEST
+
+/* RST was generated when receive an ACK without SYN in SYN_SENT state */
+START_TEST(test_tcp_gen_rst_in_SYN_SENT_non_syn_ack)
+{
+ struct tcp_pcb *pcb;
+ struct pbuf *p;
+ u16_t test_port = 1234;
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create and initialize a pcb in listen state */
+ pcb = tcp_new();
+ EXPECT_RET(pcb != NULL);
+ err = tcp_connect(pcb, &test_remote_ip, test_port, NULL);
+ EXPECT_RET(err == ERR_OK);
+
+ /* create a SYN+ACK segment with incorrect seqno */
+ p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port,
+ pcb->local_port, NULL, 0, 12345, pcb->lastack, TCP_ACK);
+ EXPECT(p != NULL);
+ test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters);
+
+ /* LWIP: send RST then re-send SYN immediately */
+ EXPECT(test_txcounters.num_tx_calls == 2);
+
+}
+END_TEST
+
+/* RST was generated when receive an ACK with incorrect seqno in SYN_RCVD state */
+START_TEST(test_tcp_gen_rst_in_SYN_RCVD)
+{
+ struct tcp_pcb_listen *lpcb;
+ struct pbuf *p;
+ u32_t ack_seqno = 0;
+ ip_addr_t src_addr = test_remote_ip;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create and initialize a pcb in listen state */
+ lpcb = create_listening_pcb(TEST_LOCAL_PORT, NULL);
+ EXPECT_RET(lpcb != NULL);
+
+ /* LISTEN -> SYN_RCVD */
+ p = tcp_create_segment(&src_addr, &lpcb->local_ip, TEST_REMOTE_PORT,
+ lpcb->local_port, NULL, 0, 1000, 54321, TCP_SYN);
+ EXPECT(p != NULL);
+ memset(&test_txcounters, 0, sizeof(struct test_tcp_txcounters));
+ test_tcp_input(p, &test_netif);
+ EXPECT(test_txcounters.num_tx_calls == 1);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ if (MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1) {
+ ack_seqno = tcp_active_pcbs[0].lastack;
+ }
+
+ /* create a ACK segment with incorrect seqno */
+ p = tcp_create_segment(&src_addr, &lpcb->local_ip, TEST_REMOTE_PORT,
+ lpcb->local_port, NULL, 0, 1001, ack_seqno+1111, TCP_ACK);
+ EXPECT(p != NULL);
+ test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters);
+ EXPECT(test_txcounters.num_tx_calls == 1);
+
+ /* the active pcb still exists */
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 1);
+ if (MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) != 0) {
+ /* can not use tcp_abort() */
+ tcp_close((struct tcp_pcb *)lpcb);
+ }
+}
+END_TEST
+
+/* a listen pcb returns to LISTEN from SYN_RCVD when RST received */
+START_TEST(test_tcp_receive_rst_SYN_RCVD_to_LISTEN)
+{
+ struct tcp_pcb_listen *lpcb;
+ struct pbuf *p;
+ u16_t tcp_flags;
+ ip_addr_t src_addr = test_remote_ip;
+ LWIP_UNUSED_ARG(_i);
+
+ /* create and initialize a pcb in listen state */
+ lpcb = create_listening_pcb(TEST_LOCAL_PORT, NULL);
+ EXPECT_RET(lpcb != NULL);
+
+ /* create a SYN segment */
+ p = tcp_create_segment(&src_addr, &lpcb->local_ip, TEST_REMOTE_PORT,
+ lpcb->local_port, NULL, 0, 1000, 54321, TCP_SYN);
+ EXPECT(p != NULL);
+ /* pass the segment to tcp_input */
+ memset(&test_txcounters, 0, sizeof(struct test_tcp_txcounters));
+ test_txcounters.copy_tx_packets = 1;
+ test_tcp_input(p, &test_netif);
+ test_txcounters.copy_tx_packets = 0;
+ /* check if packets are as expected */
+ EXPECT(test_txcounters.num_tx_calls == 1);
+ tcp_flags = get_tcp_flags_from_packet(test_txcounters.tx_packets, 20);
+ pbuf_free(test_txcounters.tx_packets);
+ test_txcounters.tx_packets = NULL;
+ EXPECT((tcp_flags & TCP_SYN) && (tcp_flags & TCP_ACK));
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1);
+
+ /* create a RST segment */
+ p = tcp_create_segment(&src_addr, &lpcb->local_ip, TEST_REMOTE_PORT,
+ lpcb->local_port, NULL, 0, 1001, 54321, TCP_RST);
+ EXPECT(p != NULL);
+ test_tcp_input(p, &test_netif);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0);
+ EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 1);
+
+ if (MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) != 0) {
+ /* can not use tcp_abort() */
+ tcp_close((struct tcp_pcb *)lpcb);
+ }
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+tcp_state_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_tcp_new_max_num),
+ TESTFUNC(test_tcp_new_max_num_remove_TIME_WAIT),
+ TESTFUNC(test_tcp_connect_active_open),
+ TESTFUNC(test_tcp_active_close),
+ TESTFUNC(test_tcp_imultaneous_close),
+ TESTFUNC(test_tcp_gen_rst_in_CLOSED),
+ TESTFUNC(test_tcp_gen_rst_in_LISTEN),
+ TESTFUNC(test_tcp_gen_rst_in_TIME_WAIT),
+ TESTFUNC(test_tcp_process_rst_seqno),
+ TESTFUNC(test_tcp_gen_rst_in_SYN_SENT_ackseq),
+ TESTFUNC(test_tcp_gen_rst_in_SYN_SENT_non_syn_ack),
+ TESTFUNC(test_tcp_gen_rst_in_SYN_RCVD),
+ TESTFUNC(test_tcp_receive_rst_SYN_RCVD_to_LISTEN),
+ };
+ return create_suite("TCP_STATE", tests, sizeof(tests) / sizeof(testfunc), tcp_state_setup, tcp_state_teardown);
+}
diff --git a/test/unit/tcp/test_tcp_state.h b/test/unit/tcp/test_tcp_state.h
new file mode 100644
index 00000000000..00d5c8a4f7c
--- /dev/null
+++ b/test/unit/tcp/test_tcp_state.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_TCP_STATE_H
+#define LWIP_HDR_TEST_TCP_STATE_H
+
+#include "../lwip_check.h"
+
+Suite *tcp_state_suite(void);
+
+#endif
diff --git a/test/unit/udp/test_udp.c b/test/unit/udp/test_udp.c
new file mode 100644
index 00000000000..a9a6b25b58e
--- /dev/null
+++ b/test/unit/udp/test_udp.c
@@ -0,0 +1,472 @@
+#include "test_udp.h"
+
+#include "lwip/udp.h"
+#include "lwip/stats.h"
+#include "lwip/inet_chksum.h"
+
+#if !LWIP_STATS || !UDP_STATS || !MEMP_STATS
+#error "This tests needs UDP- and MEMP-statistics enabled"
+#endif
+
+struct test_udp_rxdata {
+ u32_t rx_cnt;
+ u32_t rx_bytes;
+ struct udp_pcb *pcb;
+};
+
+static struct netif test_netif1, test_netif2;
+static ip4_addr_t test_gw1, test_ipaddr1, test_netmask1;
+static ip4_addr_t test_gw2, test_ipaddr2, test_netmask2;
+static int output_ctr, linkoutput_ctr;
+
+/* Helper functions */
+static void
+udp_remove_all(void)
+{
+ struct udp_pcb *pcb = udp_pcbs;
+ struct udp_pcb *pcb2;
+
+ while(pcb != NULL) {
+ pcb2 = pcb;
+ pcb = pcb->next;
+ udp_remove(pcb2);
+ }
+ fail_unless(MEMP_STATS_GET(used, MEMP_UDP_PCB) == 0);
+}
+
+static err_t
+default_netif_output(struct netif *netif, struct pbuf *p, const ip4_addr_t *ipaddr)
+{
+ fail_unless((netif == &test_netif1) || (netif == &test_netif2));
+ fail_unless(p != NULL);
+ fail_unless(ipaddr != NULL);
+ output_ctr++;
+ return ERR_OK;
+}
+
+static err_t
+default_netif_linkoutput(struct netif *netif, struct pbuf *p)
+{
+ fail_unless((netif == &test_netif1) || (netif == &test_netif2));
+ fail_unless(p != NULL);
+ linkoutput_ctr++;
+ return ERR_OK;
+}
+
+static err_t
+default_netif_init(struct netif *netif)
+{
+ fail_unless(netif != NULL);
+ netif->output = default_netif_output;
+ netif->linkoutput = default_netif_linkoutput;
+ netif->mtu = 1500;
+ netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
+ netif->hwaddr_len = 6;
+ return ERR_OK;
+}
+
+static void
+default_netif_add(void)
+{
+ struct netif *n;
+
+#if LWIP_HAVE_LOOPIF
+ fail_unless(netif_list != NULL); /* the loopif */
+ fail_unless(netif_list->next == NULL);
+#else
+ fail_unless(netif_list == NULL);
+#endif
+ fail_unless(netif_default == NULL);
+
+ IP4_ADDR(&test_ipaddr1, 192,168,0,1);
+ IP4_ADDR(&test_netmask1, 255,255,255,0);
+ IP4_ADDR(&test_gw1, 192,168,0,254);
+ n = netif_add(&test_netif1, &test_ipaddr1, &test_netmask1,
+ &test_gw1, NULL, default_netif_init, NULL);
+ fail_unless(n == &test_netif1);
+
+ IP4_ADDR(&test_ipaddr2, 192,168,1,1);
+ IP4_ADDR(&test_netmask2, 255,255,255,0);
+ IP4_ADDR(&test_gw2, 192,168,1,254);
+ n = netif_add(&test_netif2, &test_ipaddr2, &test_netmask2,
+ &test_gw2, NULL, default_netif_init, NULL);
+ fail_unless(n == &test_netif2);
+
+ netif_set_default(&test_netif1);
+ netif_set_up(&test_netif1);
+ netif_set_up(&test_netif2);
+}
+
+static void
+default_netif_remove(void)
+{
+ fail_unless(netif_default == &test_netif1);
+ netif_remove(&test_netif1);
+ netif_remove(&test_netif2);
+ fail_unless(netif_default == NULL);
+#if LWIP_HAVE_LOOPIF
+ fail_unless(netif_list != NULL); /* the loopif */
+ fail_unless(netif_list->next == NULL);
+#else
+ fail_unless(netif_list == NULL);
+#endif
+}
+/* Setups/teardown functions */
+
+static void
+udp_setup(void)
+{
+ udp_remove_all();
+ default_netif_add();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+static void
+udp_teardown(void)
+{
+ udp_remove_all();
+ default_netif_remove();
+ lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT));
+}
+
+
+/* Test functions */
+
+START_TEST(test_udp_new_remove)
+{
+ struct udp_pcb* pcb;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(MEMP_STATS_GET(used, MEMP_UDP_PCB) == 0);
+
+ pcb = udp_new();
+ fail_unless(pcb != NULL);
+ if (pcb != NULL) {
+ fail_unless(MEMP_STATS_GET(used, MEMP_UDP_PCB) == 1);
+ udp_remove(pcb);
+ fail_unless(MEMP_STATS_GET(used, MEMP_UDP_PCB) == 0);
+ }
+}
+END_TEST
+
+static void test_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+ const ip_addr_t *addr, u16_t port)
+{
+ struct test_udp_rxdata *ctr = (struct test_udp_rxdata *)arg;
+
+ LWIP_UNUSED_ARG(addr);
+ LWIP_UNUSED_ARG(port);
+
+ fail_unless(arg != NULL);
+ fail_unless(ctr->pcb == pcb);
+
+ ctr->rx_cnt++;
+ ctr->rx_bytes += p->tot_len;
+
+ if (p != NULL) {
+ pbuf_free(p);
+ }
+}
+
+static struct pbuf *
+test_udp_create_test_packet(u16_t length, u16_t port, u32_t dst_addr)
+{
+ err_t err;
+ u8_t ret;
+ struct udp_hdr *uh;
+ struct ip_hdr *ih;
+ struct pbuf *p;
+ const u8_t test_data[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+
+ p = pbuf_alloc(PBUF_TRANSPORT, length, PBUF_POOL);
+ fail_unless(p != NULL);
+ if (p == NULL) {
+ return NULL;
+ }
+ fail_unless(p->next == NULL);
+ err = pbuf_take(p, test_data, length);
+ fail_unless(err == ERR_OK);
+
+ /* add UDP header */
+ ret = pbuf_add_header(p, sizeof(struct udp_hdr));
+ fail_unless(!ret);
+ uh = (struct udp_hdr *)p->payload;
+ uh->chksum = 0;
+ uh->dest = uh->src = lwip_htons(port);
+ uh->len = lwip_htons(p->tot_len);
+ /* add IPv4 header */
+ ret = pbuf_add_header(p, sizeof(struct ip_hdr));
+ fail_unless(!ret);
+ ih = (struct ip_hdr *)p->payload;
+ memset(ih, 0, sizeof(*ih));
+ ih->dest.addr = dst_addr;
+ ih->_len = lwip_htons(p->tot_len);
+ ih->_ttl = 32;
+ ih->_proto = IP_PROTO_UDP;
+ IPH_VHL_SET(ih, 4, sizeof(struct ip_hdr) / 4);
+ IPH_CHKSUM_SET(ih, inet_chksum(ih, sizeof(struct ip_hdr)));
+ return p;
+}
+
+/* bind 2 pcbs to specific netif IP and test which one gets broadcasts */
+START_TEST(test_udp_broadcast_rx_with_2_netifs)
+{
+ err_t err;
+ struct udp_pcb *pcb1, *pcb2;
+ const u16_t port = 12345;
+ struct test_udp_rxdata ctr1, ctr2;
+ struct pbuf *p;
+#if SO_REUSE
+ struct udp_pcb *pcb_any;
+ struct test_udp_rxdata ctr_any;
+#endif
+ LWIP_UNUSED_ARG(_i);
+
+ pcb1 = udp_new();
+ fail_unless(pcb1 != NULL);
+ pcb2 = udp_new();
+ fail_unless(pcb2 != NULL);
+
+#if SO_REUSE
+ pcb_any = udp_new();
+ fail_unless(pcb_any != NULL);
+
+ ip_set_option(pcb1, SOF_REUSEADDR);
+ ip_set_option(pcb2, SOF_REUSEADDR);
+ ip_set_option(pcb_any, SOF_REUSEADDR);
+
+ err = udp_bind(pcb_any, NULL, port);
+ fail_unless(err == ERR_OK);
+ memset(&ctr_any, 0, sizeof(ctr_any));
+ ctr_any.pcb = pcb_any;
+ udp_recv(pcb_any, test_recv, &ctr_any);
+#endif
+
+ err = udp_bind(pcb1, &test_netif1.ip_addr, port);
+ fail_unless(err == ERR_OK);
+ err = udp_bind(pcb2, &test_netif2.ip_addr, port);
+ fail_unless(err == ERR_OK);
+
+ memset(&ctr1, 0, sizeof(ctr1));
+ ctr1.pcb = pcb1;
+ memset(&ctr2, 0, sizeof(ctr2));
+ ctr2.pcb = pcb2;
+
+ udp_recv(pcb1, test_recv, &ctr1);
+ udp_recv(pcb2, test_recv, &ctr2);
+
+ /* unicast to netif1 */
+ p = test_udp_create_test_packet(16, port, test_ipaddr1.addr);
+ EXPECT_RET(p != NULL);
+ err = ip4_input(p, &test_netif1);
+ fail_unless(err == ERR_OK);
+ fail_unless(ctr1.rx_cnt == 1);
+ fail_unless(ctr1.rx_bytes == 16);
+ fail_unless(ctr2.rx_cnt == 0);
+#if SO_REUSE
+ fail_unless(ctr_any.rx_cnt == 0);
+#endif
+ ctr1.rx_cnt = ctr1.rx_bytes = 0;
+
+ /* unicast to netif2 */
+ p = test_udp_create_test_packet(16, port, test_ipaddr2.addr);
+ EXPECT_RET(p != NULL);
+ err = ip4_input(p, &test_netif2);
+ fail_unless(err == ERR_OK);
+ fail_unless(ctr2.rx_cnt == 1);
+ fail_unless(ctr2.rx_bytes == 16);
+ fail_unless(ctr1.rx_cnt == 0);
+#if SO_REUSE
+ fail_unless(ctr_any.rx_cnt == 0);
+#endif
+ ctr2.rx_cnt = ctr2.rx_bytes = 0;
+
+ /* broadcast to netif1-broadcast, input to netif2 */
+ p = test_udp_create_test_packet(16, port, test_ipaddr1.addr | ~test_netmask1.addr);
+ EXPECT_RET(p != NULL);
+ err = ip4_input(p, &test_netif2);
+ fail_unless(err == ERR_OK);
+ fail_unless(ctr1.rx_cnt == 1);
+ fail_unless(ctr1.rx_bytes == 16);
+ fail_unless(ctr2.rx_cnt == 0);
+#if SO_REUSE
+ fail_unless(ctr_any.rx_cnt == 0);
+#endif
+ ctr1.rx_cnt = ctr1.rx_bytes = 0;
+
+ /* broadcast to netif2-broadcast, input to netif1 */
+ p = test_udp_create_test_packet(16, port, test_ipaddr2.addr | ~test_netmask2.addr);
+ EXPECT_RET(p != NULL);
+ err = ip4_input(p, &test_netif1);
+ fail_unless(err == ERR_OK);
+ fail_unless(ctr2.rx_cnt == 1);
+ fail_unless(ctr2.rx_bytes == 16);
+ fail_unless(ctr1.rx_cnt == 0);
+#if SO_REUSE
+ fail_unless(ctr_any.rx_cnt == 0);
+#endif
+ ctr2.rx_cnt = ctr2.rx_bytes = 0;
+
+ /* broadcast to global-broadcast, input to netif1 */
+ p = test_udp_create_test_packet(16, port, 0xffffffff);
+ EXPECT_RET(p != NULL);
+ err = ip4_input(p, &test_netif1);
+ fail_unless(err == ERR_OK);
+ fail_unless(ctr1.rx_cnt == 1);
+ fail_unless(ctr1.rx_bytes == 16);
+ fail_unless(ctr2.rx_cnt == 0);
+#if SO_REUSE
+ fail_unless(ctr_any.rx_cnt == 0);
+#endif
+ ctr1.rx_cnt = ctr1.rx_bytes = 0;
+
+ /* broadcast to global-broadcast, input to netif2 */
+ p = test_udp_create_test_packet(16, port, 0xffffffff);
+ EXPECT_RET(p != NULL);
+ err = ip4_input(p, &test_netif2);
+ fail_unless(err == ERR_OK);
+ fail_unless(ctr2.rx_cnt == 1);
+ fail_unless(ctr2.rx_bytes == 16);
+ fail_unless(ctr1.rx_cnt == 0);
+#if SO_REUSE
+ fail_unless(ctr_any.rx_cnt == 0);
+#endif
+ ctr2.rx_cnt = ctr2.rx_bytes = 0;
+}
+END_TEST
+
+START_TEST(test_udp_bind)
+{
+ struct udp_pcb* pcb1;
+ struct udp_pcb* pcb2;
+ ip_addr_t ip1;
+ ip_addr_t ip2;
+ err_t err1;
+ err_t err2;
+ LWIP_UNUSED_ARG(_i);
+
+ /* bind on same port using different IP address types */
+ ip_addr_set_any_val(0, ip1);
+ ip_addr_set_any_val(1, ip2);
+
+ pcb1 = udp_new_ip_type(IPADDR_TYPE_V4);
+ pcb2 = udp_new_ip_type(IPADDR_TYPE_V6);
+
+ err1 = udp_bind(pcb1, &ip1, 2105);
+ err2 = udp_bind(pcb2, &ip2, 2105);
+
+ fail_unless(err1 == ERR_OK);
+ fail_unless(err2 == ERR_OK);
+
+ udp_remove(pcb1);
+ udp_remove(pcb2);
+
+ /* bind on same port using SAME IPv4 address type */
+ ip_addr_set_any_val(0, ip1);
+ ip_addr_set_any_val(0, ip2);
+
+ pcb1 = udp_new_ip_type(IPADDR_TYPE_V4);
+ pcb2 = udp_new_ip_type(IPADDR_TYPE_V4);
+
+ err1 = udp_bind(pcb1, &ip1, 2105);
+ err2 = udp_bind(pcb2, &ip2, 2105);
+
+ fail_unless(err1 == ERR_OK);
+ fail_unless(err2 == ERR_USE);
+
+ udp_remove(pcb1);
+ udp_remove(pcb2);
+
+ /* bind on same port using SAME IPv6 address type */
+ ip_addr_set_any_val(1, ip1);
+ ip_addr_set_any_val(1, ip2);
+
+ pcb1 = udp_new_ip_type(IPADDR_TYPE_V6);
+ pcb2 = udp_new_ip_type(IPADDR_TYPE_V6);
+
+ err1 = udp_bind(pcb1, &ip1, 2105);
+ err2 = udp_bind(pcb2, &ip2, 2105);
+
+ fail_unless(err1 == ERR_OK);
+ fail_unless(err2 == ERR_USE);
+
+ udp_remove(pcb1);
+ udp_remove(pcb2);
+
+ /* Bind with different IP address type */
+ ip_addr_set_any_val(0, ip1);
+ ip_addr_set_any_val(1, ip2);
+
+ pcb1 = udp_new_ip_type(IPADDR_TYPE_V6);
+ pcb2 = udp_new_ip_type(IPADDR_TYPE_V4);
+
+ err1 = udp_bind(pcb1, &ip1, 2105);
+ err2 = udp_bind(pcb2, &ip2, 2105);
+
+ fail_unless(err1 == ERR_OK);
+ fail_unless(err2 == ERR_OK);
+
+ udp_remove(pcb1);
+ udp_remove(pcb2);
+
+ /* Bind with different IP numbers */
+ IP_ADDR4(&ip1, 1, 2, 3, 4);
+ IP_ADDR4(&ip2, 4, 3, 2, 1);
+
+ pcb1 = udp_new_ip_type(IPADDR_TYPE_V6);
+ pcb2 = udp_new_ip_type(IPADDR_TYPE_V4);
+
+ err1 = udp_bind(pcb1, &ip1, 2105);
+ err2 = udp_bind(pcb2, &ip2, 2105);
+
+ fail_unless(err1 == ERR_OK);
+ fail_unless(err2 == ERR_OK);
+
+ udp_remove(pcb1);
+ udp_remove(pcb2);
+
+ /* Bind with same IP numbers */
+ IP_ADDR4(&ip1, 1, 2, 3, 4);
+ IP_ADDR4(&ip2, 1, 2, 3, 4);
+
+ pcb1 = udp_new_ip_type(IPADDR_TYPE_V6);
+ pcb2 = udp_new_ip_type(IPADDR_TYPE_V4);
+
+ err1 = udp_bind(pcb1, &ip1, 2105);
+ err2 = udp_bind(pcb2, &ip2, 2105);
+
+ fail_unless(err1 == ERR_OK);
+ fail_unless(err2 == ERR_USE);
+
+ udp_remove(pcb1);
+ udp_remove(pcb2);
+
+ /* bind on same port using ANY + IPv4 */
+ ip1 = *IP_ANY_TYPE;
+ IP_ADDR4(&ip2, 1, 2, 3, 4);
+
+ pcb1 = udp_new_ip_type(IPADDR_TYPE_ANY);
+ pcb2 = udp_new_ip_type(IPADDR_TYPE_V4);
+
+ err1 = udp_bind(pcb1, &ip1, 2105);
+ err2 = udp_bind(pcb2, &ip2, 2105);
+
+ fail_unless(err1 == ERR_OK);
+ fail_unless(err2 == ERR_USE);
+
+ udp_remove(pcb1);
+ udp_remove(pcb2);
+}
+END_TEST
+
+/** Create the suite including all tests for this module */
+Suite *
+udp_suite(void)
+{
+ testfunc tests[] = {
+ TESTFUNC(test_udp_new_remove),
+ TESTFUNC(test_udp_broadcast_rx_with_2_netifs),
+ TESTFUNC(test_udp_bind)
+ };
+ return create_suite("UDP", tests, sizeof(tests)/sizeof(testfunc), udp_setup, udp_teardown);
+}
diff --git a/test/unit/udp/test_udp.h b/test/unit/udp/test_udp.h
new file mode 100644
index 00000000000..5426bf42e72
--- /dev/null
+++ b/test/unit/udp/test_udp.h
@@ -0,0 +1,8 @@
+#ifndef LWIP_HDR_TEST_UDP_H
+#define LWIP_HDR_TEST_UDP_H
+
+#include "../lwip_check.h"
+
+Suite* udp_suite(void);
+
+#endif