diff options
author | Tom Rini <trini@konsulko.com> | 2024-10-16 08:10:14 -0600 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2024-10-16 08:10:14 -0600 |
commit | f3f86fd1fe0fb288356bff78f8a6fa2edf89e3fc (patch) | |
tree | f0a99ea87d92f63895a6d053e3185838ebecf2d0 /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')
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(ðarphdr->sipaddr, adr, sizeof(ip4_addr_t)); + SMEMCPY(ðarphdr->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 |