summaryrefslogtreecommitdiff
path: root/test/unit/tcp
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2024-10-16 08:10:14 -0600
committerTom Rini <trini@konsulko.com>2024-10-16 08:10:14 -0600
commitf3f86fd1fe0fb288356bff78f8a6fa2edf89e3fc (patch)
treef0a99ea87d92f63895a6d053e3185838ebecf2d0 /test/unit/tcp
Squashed 'lib/lwip/lwip/' content from commit 0a0452b2c39b
git-subtree-dir: lib/lwip/lwip git-subtree-split: 0a0452b2c39bdd91e252aef045c115f88f6ca773
Diffstat (limited to 'test/unit/tcp')
-rw-r--r--test/unit/tcp/tcp_helper.c325
-rw-r--r--test/unit/tcp/tcp_helper.h58
-rw-r--r--test/unit/tcp/test_tcp.c1700
-rw-r--r--test/unit/tcp/test_tcp.h8
-rw-r--r--test/unit/tcp/test_tcp_oos.c1018
-rw-r--r--test/unit/tcp/test_tcp_oos.h8
-rw-r--r--test/unit/tcp/test_tcp_state.c665
-rw-r--r--test/unit/tcp/test_tcp_state.h8
8 files changed, 3790 insertions, 0 deletions
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