summaryrefslogtreecommitdiff
path: root/ext/lwip/test
diff options
context:
space:
mode:
Diffstat (limited to 'ext/lwip/test')
-rw-r--r--ext/lwip/test/unit/core/test_mem.c73
-rw-r--r--ext/lwip/test/unit/core/test_mem.h8
-rw-r--r--ext/lwip/test/unit/etharp/test_etharp.c262
-rw-r--r--ext/lwip/test/unit/etharp/test_etharp.h8
-rw-r--r--ext/lwip/test/unit/lwip_check.h37
-rw-r--r--ext/lwip/test/unit/lwip_unittests.c45
-rw-r--r--ext/lwip/test/unit/lwipopts.h50
-rw-r--r--ext/lwip/test/unit/tcp/tcp_helper.c294
-rw-r--r--ext/lwip/test/unit/tcp/tcp_helper.h52
-rw-r--r--ext/lwip/test/unit/tcp/test_tcp.c667
-rw-r--r--ext/lwip/test/unit/tcp/test_tcp.h8
-rw-r--r--ext/lwip/test/unit/tcp/test_tcp_oos.c944
-rw-r--r--ext/lwip/test/unit/tcp/test_tcp_oos.h8
-rw-r--r--ext/lwip/test/unit/udp/test_udp.c68
-rw-r--r--ext/lwip/test/unit/udp/test_udp.h8
15 files changed, 2532 insertions, 0 deletions
diff --git a/ext/lwip/test/unit/core/test_mem.c b/ext/lwip/test/unit/core/test_mem.c
new file mode 100644
index 00000000..d3a5d540
--- /dev/null
+++ b/ext/lwip/test/unit/core/test_mem.c
@@ -0,0 +1,73 @@
+#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
+#if LWIP_DNS
+#error "This test needs DNS turned off (as it mallocs on init)"
+#endif
+
+/* Setups/teardown functions */
+
+static void
+mem_setup(void)
+{
+}
+
+static void
+mem_teardown(void)
+{
+}
+
+
+/* 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);
+
+#if LWIP_DNS
+ fail("This test needs DNS turned off (as it mallocs on init)");
+#endif
+
+ 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
+
+
+/** Create the suite including all tests for this module */
+Suite *
+mem_suite(void)
+{
+ TFun tests[] = {
+ test_mem_one
+ };
+ return create_suite("MEM", tests, sizeof(tests)/sizeof(TFun), mem_setup, mem_teardown);
+}
diff --git a/ext/lwip/test/unit/core/test_mem.h b/ext/lwip/test/unit/core/test_mem.h
new file mode 100644
index 00000000..13803edc
--- /dev/null
+++ b/ext/lwip/test/unit/core/test_mem.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_MEM_H__
+#define __TEST_MEM_H__
+
+#include "../lwip_check.h"
+
+Suite *mem_suite(void);
+
+#endif
diff --git a/ext/lwip/test/unit/etharp/test_etharp.c b/ext/lwip/test/unit/etharp/test_etharp.c
new file mode 100644
index 00000000..cbbc9502
--- /dev/null
+++ b/ext/lwip/test/unit/etharp/test_etharp.c
@@ -0,0 +1,262 @@
+#include "test_etharp.h"
+
+#include "lwip/udp.h"
+#include "netif/etharp.h"
+#include "lwip/stats.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 ip_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(ip_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(/*HWTYPE_ETHERNET*/ 1);
+ etharphdr->proto = htons(ETHTYPE_IP);
+ etharphdr->hwlen = ETHARP_HWADDR_LEN;
+ etharphdr->protolen = sizeof(ip_addr_t);
+ etharphdr->opcode = htons(ARP_REPLY);
+
+ SMEMCPY(&etharphdr->sipaddr, adr, sizeof(ip_addr_t));
+ SMEMCPY(&etharphdr->dipaddr, &test_ipaddr, sizeof(ip_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();
+}
+
+static void
+etharp_teardown(void)
+{
+ etharp_remove_all();
+ default_netif_remove();
+}
+
+
+/* Test functions */
+
+START_TEST(test_etharp_table)
+{
+#if ETHARP_SUPPORT_STATIC_ENTRIES
+ err_t err;
+#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
+ s8_t idx;
+ ip_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) {
+ ip_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 err = udp_sendto(pcb, p, &adrs[i], 123);
+ fail_unless(err == 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 err = udp_sendto(pcb, p, &adrs[i], 123);
+ fail_unless(err == 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)
+{
+ TFun tests[] = {
+ test_etharp_table
+ };
+ return create_suite("ETHARP", tests, sizeof(tests)/sizeof(TFun), etharp_setup, etharp_teardown);
+}
diff --git a/ext/lwip/test/unit/etharp/test_etharp.h b/ext/lwip/test/unit/etharp/test_etharp.h
new file mode 100644
index 00000000..96e00c3b
--- /dev/null
+++ b/ext/lwip/test/unit/etharp/test_etharp.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_ETHARP_H__
+#define __TEST_ETHARP_H__
+
+#include "../lwip_check.h"
+
+Suite* etharp_suite(void);
+
+#endif
diff --git a/ext/lwip/test/unit/lwip_check.h b/ext/lwip/test/unit/lwip_check.h
new file mode 100644
index 00000000..e27f55ae
--- /dev/null
+++ b/ext/lwip/test/unit/lwip_check.h
@@ -0,0 +1,37 @@
+#ifndef __LWIP_CHECK_H__
+#define __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)
+
+/** typedef for a function returning a test suite */
+typedef Suite* (suite_getter_fn)(void);
+
+/** Create a test suite */
+static Suite* create_suite(const char* name, TFun *tests, size_t num_tests, SFun setup, SFun teardown)
+{
+ size_t i;
+ Suite *s = suite_create(name);
+
+ for(i = 0; i < num_tests; i++) {
+ /* Core test case */
+ TCase *tc_core = tcase_create("Core");
+ if ((setup != NULL) || (teardown != NULL)) {
+ tcase_add_checked_fixture(tc_core, setup, teardown);
+ }
+ tcase_add_test(tc_core, tests[i]);
+ suite_add_tcase(s, tc_core);
+ }
+ return s;
+}
+
+#endif /* __LWIP_CHECK_H__ */
diff --git a/ext/lwip/test/unit/lwip_unittests.c b/ext/lwip/test/unit/lwip_unittests.c
new file mode 100644
index 00000000..4f537e66
--- /dev/null
+++ b/ext/lwip/test/unit/lwip_unittests.c
@@ -0,0 +1,45 @@
+#include "lwip_check.h"
+
+#include "udp/test_udp.h"
+#include "tcp/test_tcp.h"
+#include "tcp/test_tcp_oos.h"
+#include "core/test_mem.h"
+#include "etharp/test_etharp.h"
+
+#include "lwip/init.h"
+
+
+int main()
+{
+ int number_failed;
+ SRunner *sr;
+ size_t i;
+ suite_getter_fn* suites[] = {
+ udp_suite,
+ tcp_suite,
+ tcp_oos_suite,
+ mem_suite,
+ etharp_suite
+ };
+ size_t num = sizeof(suites)/sizeof(void*);
+ LWIP_ASSERT("No suites defined", num > 0);
+
+ lwip_init();
+
+ sr = srunner_create((suites[0])());
+ 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/ext/lwip/test/unit/lwipopts.h b/ext/lwip/test/unit/lwipopts.h
new file mode 100644
index 00000000..88e76d7a
--- /dev/null
+++ b/ext/lwip/test/unit/lwipopts.h
@@ -0,0 +1,50 @@
+/*
+ * 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 __LWIPOPTS_H__
+#define __LWIPOPTS_H__
+
+/* Prevent having to link sys_arch.c (we don't test the API layers in unit tests) */
+#define NO_SYS 1
+#define LWIP_NETCONN 0
+#define LWIP_SOCKET 0
+
+/* 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)
+
+/* Minimal changes to opt.h required for etharp unit tests: */
+#define ETHARP_SUPPORT_STATIC_ENTRIES 1
+
+#endif /* __LWIPOPTS_H__ */
diff --git a/ext/lwip/test/unit/tcp/tcp_helper.c b/ext/lwip/test/unit/tcp/tcp_helper.c
new file mode 100644
index 00000000..dd550f1b
--- /dev/null
+++ b/ext/lwip/test/unit/tcp/tcp_helper.c
@@ -0,0 +1,294 @@
+#include "tcp_helper.h"
+
+#include "lwip/tcp_impl.h"
+#include "lwip/stats.h"
+#include "lwip/pbuf.h"
+#include "lwip/inet_chksum.h"
+
+#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS
+#error "This tests needs TCP- and MEMP-statistics enabled"
+#endif
+
+/** 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;
+ 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_active_pcbs);
+ tcp_remove(tcp_tw_pcbs);
+ fail_unless(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+ fail_unless(lwip_stats.memp[MEMP_TCP_PCB_LISTEN].used == 0);
+ fail_unless(lwip_stats.memp[MEMP_TCP_SEG].used == 0);
+ fail_unless(lwip_stats.memp[MEMP_PBUF_POOL].used == 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);
+
+ 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 = p->payload;
+ /* fill IP header */
+ iphdr->dest.addr = dst_ip->addr;
+ iphdr->src.addr = 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 = 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, data_len);
+ /* let p point to TCP header again */
+ pbuf_header(p, sizeof(struct tcp_hdr));
+ }
+
+ /* calculate checksum */
+
+ tcphdr->chksum = inet_chksum_pseudo(p, src_ip, dst_ip,
+ IP_PROTO_TCP, p->tot_len);
+
+ 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, ip_addr_t* local_ip,
+ ip_addr_t* remote_ip, u16_t local_port, u16_t remote_port)
+{
+ /* @todo: are these all states? */
+ /* @todo: remove from previous list */
+ pcb->state = state;
+ if (state == ESTABLISHED) {
+ TCP_REG(&tcp_active_pcbs, pcb);
+ pcb->local_ip.addr = local_ip->addr;
+ pcb->local_port = local_port;
+ pcb->remote_ip.addr = remote_ip->addr;
+ pcb->remote_port = remote_port;
+ } else if(state == LISTEN) {
+ TCP_REG(&tcp_listen_pcbs.pcbs, pcb);
+ pcb->local_ip.addr = local_ip->addr;
+ pcb->local_port = local_port;
+ } else if(state == TIME_WAIT) {
+ TCP_REG(&tcp_tw_pcbs, pcb);
+ pcb->local_ip.addr = local_ip->addr;
+ pcb->local_port = local_port;
+ pcb->remote_ip.addr = remote_ip->addr;
+ pcb->remote_port = remote_port;
+ } else {
+ fail();
+ }
+}
+
+void
+test_tcp_counters_err(void* arg, err_t err)
+{
+ struct test_tcp_counters* 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 = 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 = 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;
+ ip_addr_copy(current_iphdr_dest, iphdr->dest);
+ ip_addr_copy(current_iphdr_src, iphdr->src);
+ current_netif = inp;
+ current_header = iphdr;
+
+ tcp_input(p, inp);
+
+ current_iphdr_dest.addr = 0;
+ current_iphdr_src.addr = 0;
+ current_netif = NULL;
+ current_header = NULL;
+}
+
+static err_t test_tcp_netif_output(struct netif *netif, struct pbuf *p,
+ ip_addr_t *ipaddr)
+{
+ struct test_tcp_txcounters *txcounters = (struct test_tcp_txcounters*)netif->state;
+ LWIP_UNUSED_ARG(ipaddr);
+ 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,
+ ip_addr_t *ip_addr, ip_addr_t *netmask)
+{
+ struct netif *n;
+ memset(netif, 0, sizeof(struct netif));
+ memset(txcounters, 0, sizeof(struct test_tcp_txcounters));
+ netif->output = test_tcp_netif_output;
+ netif->state = txcounters;
+ netif->flags |= NETIF_FLAG_UP;
+ ip_addr_copy(netif->netmask, *netmask);
+ ip_addr_copy(netif->ip_addr, *ip_addr);
+ for (n = netif_list; n != NULL; n = n->next) {
+ if (n == netif) {
+ return;
+ }
+ }
+ netif->next = NULL;
+ netif_list = netif;
+}
diff --git a/ext/lwip/test/unit/tcp/tcp_helper.h b/ext/lwip/test/unit/tcp/tcp_helper.h
new file mode 100644
index 00000000..4a72c935
--- /dev/null
+++ b/ext/lwip/test/unit/tcp/tcp_helper.h
@@ -0,0 +1,52 @@
+#ifndef __TCP_HELPER_H__
+#define __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;
+};
+
+/* 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, ip_addr_t* local_ip,
+ 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,
+ ip_addr_t *ip_addr, ip_addr_t *netmask);
+
+
+#endif
diff --git a/ext/lwip/test/unit/tcp/test_tcp.c b/ext/lwip/test/unit/tcp/test_tcp.c
new file mode 100644
index 00000000..6fd5be50
--- /dev/null
+++ b/ext/lwip/test/unit/tcp/test_tcp.c
@@ -0,0 +1,667 @@
+#include "test_tcp.h"
+
+#include "lwip/tcp_impl.h"
+#include "lwip/stats.h"
+#include "tcp_helper.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
+
+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 void
+tcp_setup(void)
+{
+ /* reset iss to default (6510) */
+ tcp_ticks = 0;
+ tcp_ticks = 0 - (tcp_next_iss() - 6510);
+ tcp_next_iss();
+ tcp_ticks = 0;
+
+ test_tcp_timer = 0;
+ tcp_remove_all();
+}
+
+static void
+tcp_teardown(void)
+{
+ netif_list = NULL;
+ tcp_remove_all();
+}
+
+
+/* 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(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+
+ pcb = tcp_new();
+ fail_unless(pcb != NULL);
+ if (pcb != NULL) {
+ fail_unless(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ fail_unless(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+ }
+}
+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};
+ ip_addr_t remote_ip, local_ip;
+ u16_t data_len;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ struct netif netif;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ memset(&netif, 0, sizeof(netif));
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ 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, &local_ip, &remote_ip, local_port, 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 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[] = {21, 22, 23, 24};
+ ip_addr_t remote_ip, local_ip, netmask;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ err_t err;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ IP4_ADDR(&netmask, 255, 255, 255, 0);
+ test_tcp_init_netif(&netif, &txcounters, &local_ip, &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, &local_ip, &remote_ip, local_port, 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 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;
+ ip_addr_t remote_ip, local_ip, netmask;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ err_t err;
+#define SEQNO1 (0xFFFFFF00 - TCP_MSS)
+#define ISS 6510
+ u16_t i, sent_total = 0;
+ u32_t seqnos[] = {
+ SEQNO1,
+ SEQNO1 + (1 * TCP_MSS),
+ SEQNO1 + (2 * TCP_MSS),
+ SEQNO1 + (3 * TCP_MSS),
+ SEQNO1 + (4 * TCP_MSS),
+ SEQNO1 + (5 * TCP_MSS)};
+ LWIP_UNUSED_ARG(_i);
+
+ for (i = 0; i < sizeof(tx_data); i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ IP4_ADDR(&netmask, 255, 255, 255, 0);
+ test_tcp_init_netif(&netif, &txcounters, &local_ip, &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);
+ EXPECT(pcb->lastack == SEQNO1);
+ tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, 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(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 */
+ 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 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;
+ ip_addr_t remote_ip, local_ip, netmask;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ err_t err;
+#define SEQNO1 (0xFFFFFF00 - TCP_MSS)
+#define ISS 6510
+ u16_t i, sent_total = 0;
+ u32_t seqnos[] = {
+ SEQNO1,
+ SEQNO1 + (1 * TCP_MSS),
+ SEQNO1 + (2 * TCP_MSS),
+ SEQNO1 + (3 * TCP_MSS),
+ SEQNO1 + (4 * TCP_MSS),
+ SEQNO1 + (5 * TCP_MSS)};
+ LWIP_UNUSED_ARG(_i);
+
+ for (i = 0; i < sizeof(tx_data); i++) {
+ tx_data[i] = (u8_t)i;
+ }
+
+ /* initialize local vars */
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ IP4_ADDR(&netmask, 255, 255, 255, 0);
+ test_tcp_init_netif(&netif, &txcounters, &local_ip, &netmask);
+ memset(&counters, 0, sizeof(counters));
+
+ /* create and initialize the pcb */
+ tcp_ticks = 0;
+ tcp_ticks = 0 - tcp_next_iss();
+ tcp_ticks = SEQNO1 - tcp_next_iss();
+ pcb = test_tcp_new_counters_pcb(&counters);
+ EXPECT_RET(pcb != NULL);
+ EXPECT(pcb->lastack == SEQNO1);
+ tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, local_port, 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(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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 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;
+ ip_addr_t remote_ip, local_ip, netmask;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ err_t err;
+ u16_t sent_total, i;
+ 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 */
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ IP4_ADDR(&netmask, 255, 255, 255, 0);
+ test_tcp_init_netif(&netif, &txcounters, &local_ip, &netmask);
+ memset(&counters, 0, sizeof(counters));
+ memset(&txcounters, 0, sizeof(txcounters));
+
+ /* 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, local_port, 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);
+ EXPECT(pcb->persist_backoff == 1);
+ }
+
+ /* 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT_RET(lwip_stats.memp[MEMP_TCP_PCB].used == 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
+
+/** Create the suite including all tests for this module */
+Suite *
+tcp_suite(void)
+{
+ TFun tests[] = {
+ test_tcp_new_abort,
+ test_tcp_recv_inseq,
+ test_tcp_fast_retx_recover,
+ test_tcp_fast_rexmit_wraparound,
+ test_tcp_rto_rexmit_wraparound,
+ test_tcp_tx_full_window_lost_from_unacked,
+ test_tcp_tx_full_window_lost_from_unsent
+ };
+ return create_suite("TCP", tests, sizeof(tests)/sizeof(TFun), tcp_setup, tcp_teardown);
+}
diff --git a/ext/lwip/test/unit/tcp/test_tcp.h b/ext/lwip/test/unit/tcp/test_tcp.h
new file mode 100644
index 00000000..f1c4a463
--- /dev/null
+++ b/ext/lwip/test/unit/tcp/test_tcp.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_TCP_H__
+#define __TEST_TCP_H__
+
+#include "../lwip_check.h"
+
+Suite *tcp_suite(void);
+
+#endif
diff --git a/ext/lwip/test/unit/tcp/test_tcp_oos.c b/ext/lwip/test/unit/tcp/test_tcp_oos.c
new file mode 100644
index 00000000..764de1c4
--- /dev/null
+++ b/ext/lwip/test/unit/tcp/test_tcp_oos.c
@@ -0,0 +1,944 @@
+#include "test_tcp_oos.h"
+
+#include "lwip/tcp_impl.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;
+}
+
+/** 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;
+}
+
+/** 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 void
+tcp_oos_setup(void)
+{
+ tcp_remove_all();
+}
+
+static void
+tcp_oos_teardown(void)
+{
+ tcp_remove_all();
+}
+
+
+
+/* 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};
+ ip_addr_t remote_ip, local_ip;
+ u16_t data_len;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ struct netif netif;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ memset(&netif, 0, sizeof(netif));
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ 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, &local_ip, &remote_ip, local_port, 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 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};
+ ip_addr_t remote_ip, local_ip;
+ u16_t data_len;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ struct netif netif;
+ LWIP_UNUSED_ARG(_i);
+
+ /* initialize local vars */
+ memset(&netif, 0, sizeof(netif));
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ 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, &local_ip, &remote_ip, local_port, 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+}
+END_TEST
+
+static char data_full_wnd[TCP_WND];
+
+/** 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;
+ ip_addr_t remote_ip, local_ip;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ 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 */
+ memset(&netif, 0, sizeof(netif));
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ /* 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, local_port, 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 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_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))
+ int i, k;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *p_ovr;
+ ip_addr_t remote_ip, local_ip;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ 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 */
+ memset(&netif, 0, sizeof(netif));
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ /* 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, local_port, 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+#endif /* TCP_OOSEQ_MAX_BYTES && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + 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_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN))
+ int i;
+ struct test_tcp_counters counters;
+ struct tcp_pcb* pcb;
+ struct pbuf *p_ovr;
+ ip_addr_t remote_ip, local_ip;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ 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 */
+ memset(&netif, 0, sizeof(netif));
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ /* 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, local_port, 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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 0);
+#endif /* TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + 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;
+ ip_addr_t remote_ip, local_ip;
+ u16_t remote_port = 0x100, local_port = 0x101;
+ 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;
+ int last_dropped = 0;
+
+ for(i = 0; i < sizeof(data_full_wnd); i++) {
+ data_full_wnd[i] = (char)i;
+ }
+
+ /* initialize local vars */
+ memset(&netif, 0, sizeof(netif));
+ IP4_ADDR(&local_ip, 192, 168, 1, 1);
+ IP4_ADDR(&remote_ip, 192, 168, 1, 2);
+ /* 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, local_port, 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;
+ last_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;
+ }
+ last_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;
+ }
+ last_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;
+ }
+ last_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(lwip_stats.memp[MEMP_TCP_PCB].used == 1);
+ tcp_abort(pcb);
+ EXPECT(lwip_stats.memp[MEMP_TCP_PCB].used == 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)
+{
+ TFun tests[] = {
+ test_tcp_recv_ooseq_FIN_OOSEQ,
+ test_tcp_recv_ooseq_FIN_INSEQ,
+ test_tcp_recv_ooseq_overrun_rxwin,
+ test_tcp_recv_ooseq_max_bytes,
+ test_tcp_recv_ooseq_max_pbufs,
+ test_tcp_recv_ooseq_double_FIN_0,
+ test_tcp_recv_ooseq_double_FIN_1,
+ test_tcp_recv_ooseq_double_FIN_2,
+ test_tcp_recv_ooseq_double_FIN_3,
+ test_tcp_recv_ooseq_double_FIN_4,
+ test_tcp_recv_ooseq_double_FIN_5,
+ test_tcp_recv_ooseq_double_FIN_6,
+ test_tcp_recv_ooseq_double_FIN_7,
+ test_tcp_recv_ooseq_double_FIN_8,
+ test_tcp_recv_ooseq_double_FIN_9,
+ test_tcp_recv_ooseq_double_FIN_10,
+ test_tcp_recv_ooseq_double_FIN_11,
+ test_tcp_recv_ooseq_double_FIN_12,
+ test_tcp_recv_ooseq_double_FIN_13,
+ test_tcp_recv_ooseq_double_FIN_14,
+ test_tcp_recv_ooseq_double_FIN_15
+ };
+ return create_suite("TCP_OOS", tests, sizeof(tests)/sizeof(TFun), tcp_oos_setup, tcp_oos_teardown);
+}
diff --git a/ext/lwip/test/unit/tcp/test_tcp_oos.h b/ext/lwip/test/unit/tcp/test_tcp_oos.h
new file mode 100644
index 00000000..5e411f00
--- /dev/null
+++ b/ext/lwip/test/unit/tcp/test_tcp_oos.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_TCP_OOS_H__
+#define __TEST_TCP_OOS_H__
+
+#include "../lwip_check.h"
+
+Suite *tcp_oos_suite(void);
+
+#endif
diff --git a/ext/lwip/test/unit/udp/test_udp.c b/ext/lwip/test/unit/udp/test_udp.c
new file mode 100644
index 00000000..a2f02af0
--- /dev/null
+++ b/ext/lwip/test/unit/udp/test_udp.c
@@ -0,0 +1,68 @@
+#include "test_udp.h"
+
+#include "lwip/udp.h"
+#include "lwip/stats.h"
+
+#if !LWIP_STATS || !UDP_STATS || !MEMP_STATS
+#error "This tests needs UDP- and MEMP-statistics enabled"
+#endif
+
+/* 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(lwip_stats.memp[MEMP_UDP_PCB].used == 0);
+}
+
+/* Setups/teardown functions */
+
+static void
+udp_setup(void)
+{
+ udp_remove_all();
+}
+
+static void
+udp_teardown(void)
+{
+ udp_remove_all();
+}
+
+
+/* Test functions */
+
+START_TEST(test_udp_new_remove)
+{
+ struct udp_pcb* pcb;
+ LWIP_UNUSED_ARG(_i);
+
+ fail_unless(lwip_stats.memp[MEMP_UDP_PCB].used == 0);
+
+ pcb = udp_new();
+ fail_unless(pcb != NULL);
+ if (pcb != NULL) {
+ fail_unless(lwip_stats.memp[MEMP_UDP_PCB].used == 1);
+ udp_remove(pcb);
+ fail_unless(lwip_stats.memp[MEMP_UDP_PCB].used == 0);
+ }
+}
+END_TEST
+
+
+/** Create the suite including all tests for this module */
+Suite *
+udp_suite(void)
+{
+ TFun tests[] = {
+ test_udp_new_remove,
+ };
+ return create_suite("UDP", tests, sizeof(tests)/sizeof(TFun), udp_setup, udp_teardown);
+}
diff --git a/ext/lwip/test/unit/udp/test_udp.h b/ext/lwip/test/unit/udp/test_udp.h
new file mode 100644
index 00000000..93353682
--- /dev/null
+++ b/ext/lwip/test/unit/udp/test_udp.h
@@ -0,0 +1,8 @@
+#ifndef __TEST_UDP_H__
+#define __TEST_UDP_H__
+
+#include "../lwip_check.h"
+
+Suite* udp_suite(void);
+
+#endif