<commit_before>/*
 * Copyright (c) 2018-2020 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <logging/log.h>
LOG_MODULE_REGISTER(net_tcp, CONFIG_NET_TCP_LOG_LEVEL);

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr.h>
#include <net/net_pkt.h>
#include <net/net_context.h>
#include <net/udp.h>
#include "ipv4.h"
#include "ipv6.h"
#include "connection.h"
#include "net_stats.h"
#include "net_private.h"
#include "tcp2_priv.h"

static int tcp_rto = CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT;
static int tcp_retries = 3;
static int tcp_window = NET_IPV6_MTU;

static sys_slist_t tcp_conns = SYS_SLIST_STATIC_INIT(&tcp_conns);

static K_MEM_SLAB_DEFINE(tcp_conns_slab, sizeof(struct tcp),
				CONFIG_NET_MAX_CONTEXTS, 4);

static void tcp_in(struct tcp *conn, struct net_pkt *pkt);
static size_t tcp_data_len(struct net_pkt *pkt);
int net_tcp_finalize(struct net_pkt *pkt);

int (*tcp_send_cb)(struct net_pkt *pkt) = NULL;
size_t (*tcp_recv_cb)(struct tcp *conn, struct net_pkt *pkt) = NULL;

static struct tcphdr *th_get(struct net_pkt *pkt)
{
	NET_PKT_DATA_ACCESS_DEFINE(th_access, struct tcphdr);

	net_pkt_cursor_init(pkt);
	net_pkt_set_overwrite(pkt, true);

	/* net_pkt_ip_hdr_len(), net_pkt_ip_opts_len() account for IPv4/IPv6 */

	net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) +
		     net_pkt_ip_opts_len(pkt));

	return net_pkt_get_data(pkt, &th_access);
}

static size_t tcp_endpoint_len(sa_family_t af)
{
	return (af == AF_INET) ? sizeof(struct sockaddr_in) :
		sizeof(struct sockaddr_in6);
}

static int tcp_endpoint_set(union tcp_endpoint *ep, struct net_pkt *pkt,
			    enum pkt_addr src)
{
	int ret = 0;

	switch (net_pkt_family(pkt)) {
	case AF_INET:
		if (IS_ENABLED(CONFIG_NET_IPV4)) {
			struct net_ipv4_hdr *ip = NET_IPV4_HDR(pkt);
			struct tcphdr *th = th_get(pkt);

			memset(ep, 0, sizeof(*ep));

			ep->sin.sin_port = src == TCP_EP_SRC ? th->th_sport :
							       th->th_dport;
			net_ipaddr_copy(&ep->sin.sin_addr,
					src == TCP_EP_SRC ?
							&ip->src : &ip->dst);
			ep->sa.sa_family = AF_INET;
		} else {
			ret = -EINVAL;
		}

		break;

	case AF_INET6:
		if (IS_ENABLED(CONFIG_NET_IPV6)) {
			struct net_ipv6_hdr *ip = NET_IPV6_HDR(pkt);
			struct tcphdr *th = th_get(pkt);

			memset(ep, 0, sizeof(*ep));

			ep->sin6.sin6_port = src == TCP_EP_SRC ? th->th_sport :
								 th->th_dport;
			net_ipaddr_copy(&ep->sin6.sin6_addr,
					src == TCP_EP_SRC ?
							&ip->src : &ip->dst);
			ep->sa.sa_family = AF_INET6;
		} else {
			ret = -EINVAL;
		}

		break;

	default:
		NET_ERR("Unknown address family: %hu", net_pkt_family(pkt));
		ret = -EINVAL;
	}

	return ret;
}

static const char *tcp_flags(u8_t flags)
{
#define BUF_SIZE 25 /* 6 * 4 + 1 */
	static char buf[BUF_SIZE];
	int len = 0;

	buf[0] = '\0';

	if (flags) {
		if (flags & SYN) {
			len += snprintk(buf + len, BUF_SIZE - len, "SYN,");
		}
		if (flags & FIN) {
			len += snprintk(buf + len, BUF_SIZE - len, "FIN,");
		}
		if (flags & ACK) {
			len += snprintk(buf + len, BUF_SIZE - len, "ACK,");
		}
		if (flags & PSH) {
			len += snprintk(buf + len, BUF_SIZE - len, "PSH,");
		}
		if (flags & RST) {
			len += snprintk(buf + len, BUF_SIZE - len, "RST,");
		}
		if (flags & URG) {
			len += snprintk(buf + len, BUF_SIZE - len, "URG,");
		}

		buf[len - 1] = '\0'; /* delete the last comma */
	}
#undef BUF_SIZE
	return buf;
}

static const char *tcp_th(struct net_pkt *pkt)
{
#define BUF_SIZE 80
	static char buf[BUF_SIZE];
	int len = 0;
	struct tcphdr *th = th_get(pkt);

	buf[0] = '\0';

	if (th->th_off < 5) {
		len += snprintk(buf + len, BUF_SIZE - len,
				"bogus th_off: %hu", (u16_t)th->th_off);
		goto end;
	}

	len += snprintk(buf + len, BUF_SIZE - len,
			"%s Seq=%u", tcp_flags(th->th_flags), th_seq(th));

	if (th->th_flags & ACK) {
		len += snprintk(buf + len, BUF_SIZE - len,
				" Ack=%u", th_ack(th));
	}

	len += snprintk(buf + len, BUF_SIZE - len,
			" Len=%ld", (long)tcp_data_len(pkt));
end:
#undef BUF_SIZE
	return buf;
}

static void tcp_send(struct net_pkt *pkt)
{
	NET_DBG("%s", log_strdup(tcp_th(pkt)));

	tcp_pkt_ref(pkt);

	if (tcp_send_cb) {
		if (tcp_send_cb(pkt) < 0) {
			NET_ERR("net_send_data()");
			tcp_pkt_unref(pkt);
		}
		goto out;
	}

	if (net_send_data(pkt) < 0) {
		NET_ERR("net_send_data()");
		tcp_pkt_unref(pkt);
	}
out:
	tcp_pkt_unref(pkt);
}

static void tcp_send_queue_flush(struct tcp *conn)
{
	struct net_pkt *pkt;

	if (k_delayed_work_remaining_get(&conn->send_timer)) {
		k_delayed_work_cancel(&conn->send_timer);
	}

	while ((pkt = tcp_slist(&conn->send_queue, get,
				struct net_pkt, next))) {
		tcp_pkt_unref(pkt);
	}
}

static int tcp_conn_unref(struct tcp *conn)
{
	int ref_count = atomic_dec(&conn->ref_count) - 1;
	int key;

	NET_DBG("conn: %p, ref_count=%d", conn, ref_count);

	if (ref_count) {
		tp_out(net_context_get_family(conn->context), conn->iface,
		       "TP_TRACE", "event", "CONN_DELETE");
		goto out;
	}

	key = irq_lock();

	if (conn->context->conn_handler) {
		net_conn_unregister(conn->context->conn_handler);
		conn->context->conn_handler = NULL;
	}

	if (conn->context->recv_cb) {
		conn->context->recv_cb(conn->context, NULL, NULL, NULL,
					-ECONNRESET, conn->recv_user_data);
	}

	conn->context->tcp = NULL;

	net_context_unref(conn->context);

	tcp_send_queue_flush(conn);

	k_delayed_work_cancel(&conn->timewait_timer);

	memset(conn, 0, sizeof(*conn));

	sys_slist_find_and_remove(&tcp_conns, (sys_snode_t *)conn);

	k_mem_slab_free(&tcp_conns_slab, (void **)&conn);

	irq_unlock(key);
out:
	return ref_count;
}

int net_tcp_unref(struct net_context *context)
{
	int ref_count = 0;

	NET_DBG("context: %p, conn: %p", context, context->tcp);

	if (context->tcp) {
		ref_count = tcp_conn_unref(context->tcp);
	}

	return ref_count;
}

static void tcp_send_process(struct k_work *work)
{
	struct tcp *conn = CONTAINER_OF(work, struct tcp, send_timer);
	struct net_pkt *pkt = tcp_slist(&conn->send_queue, peek_head,
					struct net_pkt, next);

	NET_DBG("%s %s", log_strdup(tcp_th(pkt)), conn->in_retransmission ?
		"in_retransmission" : "");

	if (conn->in_retransmission) {
		if (conn->send_retries > 0) {
			tcp_send(tcp_pkt_clone(pkt));
			conn->send_retries--;
		} else {
			tcp_conn_unref(conn);
			conn = NULL;
		}
	} else {
		u8_t fl = th_get(pkt)->th_flags;
		bool forget = ACK == fl || PSH == fl || (ACK | PSH) == fl ||
			RST & fl;

		pkt = forget ? tcp_slist(&conn->send_queue, get, struct net_pkt,
						next) : tcp_pkt_clone(pkt);
		tcp_send(pkt);

		if (forget == false && !k_delayed_work_remaining_get(
				&conn->send_timer)) {
			conn->send_retries = tcp_retries;
			conn->in_retransmission = true;
		}
	}

	if (conn && conn->in_retransmission) {
		k_delayed_work_submit(&conn->send_timer, K_MSEC(tcp_rto));
	}
}

static void tcp_send_timer_cancel(struct tcp *conn)
{
	NET_ASSERT(conn->in_retransmission == true, "Not in retransmission");

	k_delayed_work_cancel(&conn->send_timer);

	{
		struct net_pkt *pkt = tcp_slist(&conn->send_queue, get,
						struct net_pkt, next);
		NET_DBG("%s", log_strdup(tcp_th(pkt)));
		tcp_pkt_unref(pkt);
	}

	if (sys_slist_is_empty(&conn->send_queue)) {
		conn->in_retransmission = false;
	} else {
		conn->send_retries = tcp_retries;
		k_delayed_work_submit(&conn->send_timer, K_MSEC(tcp_rto));
	}
}

static const char *tcp_state_to_str(enum tcp_state state, bool prefix)
{
	const char *s = NULL;
#define _(_x) case _x: do { s = #_x; goto out; } while (0)
	switch (state) {
	_(TCP_LISTEN);
	_(TCP_SYN_SENT);
	_(TCP_SYN_RECEIVED);
	_(TCP_ESTABLISHED);
	_(TCP_FIN_WAIT_1);
	_(TCP_FIN_WAIT_2);
	_(TCP_CLOSE_WAIT);
	_(TCP_CLOSING);
	_(TCP_LAST_ACK);
	_(TCP_TIME_WAIT);
	_(TCP_CLOSED);
	}
#undef _
	NET_ASSERT(s, "Invalid TCP state: %u", state);
out:
	return prefix ? s : (s + 4);
}

static const char *tcp_conn_state(struct tcp *conn, struct net_pkt *pkt)
{
#define BUF_SIZE 80
	static char buf[BUF_SIZE];

	snprintk(buf, BUF_SIZE, "%s [%s Seq=%u Ack=%u]", pkt ? tcp_th(pkt) : "",
			tcp_state_to_str(conn->state, false),
			conn->seq, conn->ack);
#undef BUF_SIZE
	return buf;
}

static u8_t *tcp_options_get(struct net_pkt *pkt, int tcp_options_len)
{
	static u8_t options[40]; /* TCP header max options size is 40 */
	struct net_pkt_cursor backup;

	net_pkt_cursor_backup(pkt, &backup);
	net_pkt_cursor_init(pkt);
	net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt) +
		     sizeof(struct tcphdr));
	net_pkt_read(pkt, options, tcp_options_len);
	net_pkt_cursor_restore(pkt, &backup);

	return options;
}

static bool tcp_options_check(struct tcp_options *recv_options,
			      struct net_pkt *pkt, ssize_t len)
{
	bool result = len > 0 && ((len % 4) == 0) ? true : false;
	u8_t *options = tcp_options_get(pkt, len);
	u8_t opt, opt_len;

	NET_DBG("len=%zd", len);

	recv_options->mss_found = false;
	recv_options->wnd_found = false;

	for ( ; len >= 1; options += opt_len, len -= opt_len) {
		opt = options[0];

		if (opt == TCPOPT_END) {
			break;
		} else if (opt == TCPOPT_NOP) {
			opt_len = 1;
			continue;
		} else {
			if (len < 2) { /* Only END and NOP can have length 1 */
				NET_ERR("Illegal option %d with length %zd",
					opt, len);
				result = false;
				break;
			}
			opt_len = options[1];
		}
		NET_DBG("opt: %hu, opt_len: %hu", (u16_t)opt, (u16_t)opt_len);

		if (opt_len < 2 || opt_len > len) {
			result = false;
			break;
		}

		switch (opt) {
		case TCPOPT_MAXSEG:
			if (opt_len != 4) {
				result = false;
				goto end;
			}

			recv_options->mss = opt;
			recv_options->mss_found = true;
			break;
		case TCPOPT_WINDOW:
			if (opt_len != 3) {
				result = false;
				goto end;
			}

			recv_options->window = opt;
			recv_options->wnd_found = true;
			break;
		default:
			continue;
		}
	}
end:
	if (false == result) {
		NET_WARN("Invalid TCP options");
	}

	return result;
}

static size_t tcp_data_len(struct net_pkt *pkt)
{
	struct tcphdr *th = th_get(pkt);
	size_t tcp_options_len = (th->th_off - 5) * 4;
	ssize_t len = net_pkt_get_len(pkt) - net_pkt_ip_hdr_len(pkt) -
		net_pkt_ip_opts_len(pkt) - sizeof(*th) - tcp_options_len;

	return len > 0 ? len : 0;
}

static size_t tcp_data_get(struct tcp *conn, struct net_pkt *pkt)
{
	ssize_t len = tcp_data_len(pkt);

	if (tcp_recv_cb) {
		tcp_recv_cb(conn, pkt);
		goto out;
	}

	if (len > 0) {
		if (conn->context->recv_cb) {
			struct net_pkt *up = net_pkt_clone(pkt, K_NO_WAIT);

			net_pkt_cursor_init(up);
			net_pkt_set_overwrite(up, true);

			net_pkt_skip(up, net_pkt_get_len(up) - len);

			net_context_packet_received(
				(struct net_conn *)conn->context->conn_handler,
				up, NULL, NULL, conn->recv_user_data);
		}
	}
 out:
	return len;
}

static int tcp_finalize_pkt(struct net_pkt *pkt)
{
	net_pkt_cursor_init(pkt);

	if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) {
		return net_ipv4_finalize(pkt, IPPROTO_TCP);
	}

	if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) {
		return net_ipv6_finalize(pkt, IPPROTO_TCP);
	}

	return -EINVAL;
}

static int tcp_header_add(struct tcp *conn, struct net_pkt *pkt, u8_t flags)
{
	NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct tcphdr);
	struct tcphdr *th;

	th = (struct tcphdr *)net_pkt_get_data(pkt, &tcp_access);
	if (!th) {
		return -ENOBUFS;
	}

	memset(th, 0, sizeof(struct tcphdr));

	th->th_sport = conn->src.sin.sin_port;
	th->th_dport = conn->dst.sin.sin_port;

	th->th_off = 5;
	th->th_flags = flags;
	th->th_win = htons(conn->win);
	th->th_seq = htonl(conn->seq);

	if (ACK & flags) {
		th->th_ack = htonl(conn->ack);
	}

	return net_pkt_set_data(pkt, &tcp_access);
}

static int ip_header_add(struct tcp *conn, struct net_pkt *pkt)
{
	if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) {
		return net_context_create_ipv4_new(conn->context, pkt,
						&conn->src.sin.sin_addr,
						&conn->dst.sin.sin_addr);
	}

	if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) {
		return net_context_create_ipv6_new(conn->context, pkt,
						&conn->src.sin6.sin6_addr,
						&conn->dst.sin6.sin6_addr);
	}

	return -EINVAL;
}

static void tcp_out(struct tcp *conn, u8_t flags, ...)
{
	struct net_pkt *pkt;
	size_t len = 0;
	int r;

	pkt = tcp_pkt_alloc(conn, sizeof(struct tcphdr));
	if (!pkt) {
		goto fail;
	}

	if (PSH & flags) {
		struct net_pkt *data_pkt;
		va_list ap;
		va_start(ap, flags);
		data_pkt = va_arg(ap, struct net_pkt *);
		va_end(ap);

		len = net_pkt_get_len(data_pkt);
		/* Append the data buffer to pkt */
		net_pkt_append_buffer(pkt, data_pkt->buffer);

		data_pkt->buffer = NULL;
		tcp_pkt_unref(data_pkt);
	}

	r = ip_header_add(conn, pkt);
	if (r < 0) {
		goto fail;
	}

	r = tcp_header_add(conn, pkt, flags);
	if (r < 0) {
		goto fail;
	}

	r = tcp_finalize_pkt(pkt);
	if (r < 0) {
		goto fail;
	}

	if (len) {
		conn_seq(conn, + len);
	}

	NET_DBG("%s", log_strdup(tcp_th(pkt)));

	if (tcp_send_cb) {
		tcp_send_cb(pkt);
		goto out;
	}

	sys_slist_append(&conn->send_queue, &pkt->next);

	tcp_send_process((struct k_work *)&conn->send_timer);
out:
	return;

fail:
	if (pkt) {
		tcp_pkt_unref(pkt);
	}
}

static void tcp_timewait_timeout(struct k_work *work)
{
	struct tcp *conn = CONTAINER_OF(work, struct tcp, timewait_timer);

	NET_DBG("conn: %p %s", conn, log_strdup(tcp_conn_state(conn, NULL)));

	tcp_conn_unref(conn);
}

static void tcp_conn_ref(struct tcp *conn)
{
	int ref_count = atomic_inc(&conn->ref_count) + 1;

	NET_DBG("conn: %p, ref_count: %d", conn, ref_count);
}

static struct tcp *tcp_conn_alloc(void)
{
	struct tcp *conn = NULL;
	int ret;

	ret = k_mem_slab_alloc(&tcp_conns_slab, (void **)&conn, K_NO_WAIT);
	if (ret) {
		goto out;
	}

	memset(conn, 0, sizeof(*conn));

	k_mutex_init(&conn->lock);

	conn->state = TCP_LISTEN;

	conn->win = tcp_window;

	sys_slist_init(&conn->send_queue);

	k_delayed_work_init(&conn->send_timer, tcp_send_process);

	k_delayed_work_init(&conn->timewait_timer, tcp_timewait_timeout);

	tcp_conn_ref(conn);

	sys_slist_append(&tcp_conns, (sys_snode_t *)conn);
out:
	NET_DBG("conn: %p", conn);

	return conn;
}

int net_tcp_get(struct net_context *context)
{
	int ret = 0, key = irq_lock();
	struct tcp *conn;

	conn = tcp_conn_alloc();
	if (conn == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	/* Mutually link the net_context and tcp connection */
	conn->context = context;
	context->tcp = conn;
out:
	irq_unlock(key);

	return ret;
}

static bool tcp_endpoint_cmp(union tcp_endpoint *ep, struct net_pkt *pkt,
			     enum pkt_addr which)
{
	union tcp_endpoint ep_tmp;

	if (tcp_endpoint_set(&ep_tmp, pkt, which) < 0) {
		return false;
	}

	return !memcmp(ep, &ep_tmp, tcp_endpoint_len(ep->sa.sa_family));
}

static bool tcp_conn_cmp(struct tcp *conn, struct net_pkt *pkt)
{
	return tcp_endpoint_cmp(&conn->src, pkt, TCP_EP_DST) &&
		tcp_endpoint_cmp(&conn->dst, pkt, TCP_EP_SRC);
}

static struct tcp *tcp_conn_search(struct net_pkt *pkt)
{
	bool found = false;
	struct tcp *conn;

	SYS_SLIST_FOR_EACH_CONTAINER(&tcp_conns, conn, next) {

		found = tcp_conn_cmp(conn, pkt);
		if (found) {
			break;
		}
	}

	return found ? conn : NULL;
}

static struct tcp *tcp_conn_new(struct net_pkt *pkt);

static enum net_verdict tcp_recv(struct net_conn *net_conn,
				 struct net_pkt *pkt,
				 union net_ip_header *ip,
				 union net_proto_header *proto,
				 void *user_data)
{
	struct tcp *conn;
	struct tcphdr *th;

	ARG_UNUSED(net_conn);
	ARG_UNUSED(proto);

	conn = tcp_conn_search(pkt);
	if (conn) {
		goto in;
	}

	th = th_get(pkt);

	if (th->th_flags & SYN && !(th->th_flags & ACK)) {
		struct tcp *conn_old = ((struct net_context *)user_data)->tcp;

		conn = tcp_conn_new(pkt);

		net_ipaddr_copy(&conn_old->context->remote, &conn->dst.sa);

		conn_old->accept_cb(conn->context,
				    &conn_old->context->remote,
				    sizeof(struct sockaddr), 0,
				    conn_old->context);
	}
 in:
	if (conn) {
		tcp_in(conn, pkt);
	}

	return NET_DROP;
}

/* Create a new tcp connection, as a part of it, create and register
 * net_context
 */
static struct tcp *tcp_conn_new(struct net_pkt *pkt)
{
	struct tcp *conn = NULL;
	struct net_context *context = NULL;
	sa_family_t af = net_pkt_family(pkt);
	struct sockaddr local_addr = { 0 };
	int ret;

	ret = net_context_get(af, SOCK_STREAM, IPPROTO_TCP, &context);
	if (ret < 0) {
		NET_ERR("net_context_get(): %d", ret);
		goto err;
	}

	conn = context->tcp;
	conn->iface = pkt->iface;

	net_context_set_family(conn->context, net_pkt_family(pkt));

	if (tcp_endpoint_set(&conn->dst, pkt, TCP_EP_SRC) < 0) {
		net_context_unref(context);
		conn = NULL;
		goto err;
	}

	if (tcp_endpoint_set(&conn->src, pkt, TCP_EP_DST) < 0) {
		net_context_unref(context);
		conn = NULL;
		goto err;
	}

	NET_DBG("conn: src: %s, dst: %s",
		log_strdup(net_sprint_addr(conn->src.sa.sa_family,
				(const void *)&conn->src.sin.sin_addr)),
		log_strdup(net_sprint_addr(conn->dst.sa.sa_family,
				(const void *)&conn->dst.sin.sin_addr)));

	memcpy(&context->remote, &conn->dst, sizeof(context->remote));
	context->flags |= NET_CONTEXT_REMOTE_ADDR_SET;

	net_sin_ptr(&context->local)->sin_family = af;

	local_addr.sa_family = net_context_get_family(context);

	if (IS_ENABLED(CONFIG_NET_IPV6) &&
	    net_context_get_family(context) == AF_INET6) {
		if (net_sin6_ptr(&context->local)->sin6_addr) {
			net_ipaddr_copy(&net_sin6(&local_addr)->sin6_addr,
				     net_sin6_ptr(&context->local)->sin6_addr);
		}
	} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
		   net_context_get_family(context) == AF_INET) {
		if (net_sin_ptr(&context->local)->sin_addr) {
			net_ipaddr_copy(&net_sin(&local_addr)->sin_addr,
				      net_sin_ptr(&context->local)->sin_addr);
		}
	}

	NET_DBG("context: local: %s, remote: %s",
		log_strdup(net_sprint_addr(
		      local_addr.sa_family,
		      (const void *)&net_sin(&local_addr)->sin_addr)),
		log_strdup(net_sprint_addr(
		      context->remote.sa_family,
		      (const void *)&net_sin(&context->remote)->sin_addr)));

	ret = net_conn_register(IPPROTO_TCP, af,
				&context->remote, &local_addr,
				ntohs(conn->dst.sin.sin_port),/* local port */
				ntohs(conn->src.sin.sin_port),/* remote port */
				tcp_recv, context,
				&context->conn_handler);
	if (ret < 0) {
		NET_ERR("net_conn_register(): %d", ret);
		net_context_unref(context);
		conn = NULL;
		goto err;
	}
err:
	return conn;
}

/* TCP state machine, everything happens here */
static void tcp_in(struct tcp *conn, struct net_pkt *pkt)
{
	struct tcphdr *th = pkt ? th_get(pkt) : NULL;
	u8_t next = 0, fl = th ? th->th_flags : 0;
	size_t tcp_options_len = th ? (th->th_off - 5) * 4 : 0;
	size_t len;

	k_mutex_lock(&conn->lock, K_FOREVER);

	NET_DBG("%s", log_strdup(tcp_conn_state(conn, pkt)));

	if (th && th->th_off < 5) {
		tcp_out(conn, RST);
		conn_state(conn, TCP_CLOSED);
		goto next_state;
	}

	if (tcp_options_len && !tcp_options_check(&conn->recv_options, pkt,
						  tcp_options_len)) {
		NET_DBG("DROP: Invalid TCP option list");
		tcp_out(conn, RST);
		conn_state(conn, TCP_CLOSED);
		goto next_state;
	}

	if (FL(&fl, &, RST)) {
		conn_state(conn, TCP_CLOSED);
	}
next_state:
	len = pkt ? tcp_data_len(pkt) : 0;

	switch (conn->state) {
	case TCP_LISTEN:
		if (FL(&fl, ==, SYN)) {
			conn_ack(conn, th_seq(th) + 1); /* capture peer's isn */
			tcp_out(conn, SYN | ACK);
			conn_seq(conn, + 1);
			next = TCP_SYN_RECEIVED;
		} else {
			tcp_out(conn, SYN);
			conn_seq(conn, + 1);
			next = TCP_SYN_SENT;
		}
		break;
	case TCP_SYN_RECEIVED:
		if (FL(&fl, &, ACK, th_ack(th) == conn->seq &&
				th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			next = TCP_ESTABLISHED;
			net_context_set_state(conn->context,
					      NET_CONTEXT_CONNECTED);
			if (len) {
				tcp_data_get(conn, pkt);
				conn_ack(conn, + len);
				tcp_out(conn, ACK);
			}
		}
		break;
	case TCP_SYN_SENT:
		/* if we are in SYN SENT and receive only a SYN without an
		 * ACK , shouldn't we go to SYN RECEIVED state? See Figure
		 * 6 of RFC 793
		 */
		if (FL(&fl, &, ACK, th && th_ack(th) == conn->seq)) {
			tcp_send_timer_cancel(conn);
			next = TCP_ESTABLISHED;
			net_context_set_state(conn->context,
					      NET_CONTEXT_CONNECTED);
			if (FL(&fl, &, PSH)) {
				tcp_data_get(conn, pkt);
			}
			if (FL(&fl, &, SYN)) {
				conn_ack(conn, th_seq(th) + 1);
				tcp_out(conn, ACK);
			}
		}
		break;
	case TCP_ESTABLISHED:
		/* full-close */
		if (th && FL(&fl, ==, (FIN | ACK), th_seq(th) == conn->ack)) {
			conn_ack(conn, + 1);
			tcp_out(conn, ACK);
			next = TCP_CLOSE_WAIT;
			break;
		}
		if (len) {
			if (th_seq(th) == conn->ack) {
				tcp_data_get(conn, pkt);
				conn_ack(conn, + len);
				tcp_out(conn, ACK);
			} else if (th_seq(th) < conn->ack) {
				tcp_out(conn, ACK); /* peer has resent */
			}
		}
		break; /* TODO: Catch all the rest here */
	case TCP_CLOSE_WAIT:
		tcp_out(conn, FIN | ACK);
		next = TCP_LAST_ACK;
		break;
	case TCP_LAST_ACK:
		if (FL(&fl, ==, ACK, th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			next = TCP_CLOSED;
		}
		break;
	case TCP_CLOSED:
		tcp_conn_unref(conn);
		break;
	case TCP_FIN_WAIT_1:
		if (th && FL(&fl, ==, (FIN | ACK), th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			conn_ack(conn, + 1);
			tcp_out(conn, ACK);
			next = TCP_TIME_WAIT;
		} else if (th && FL(&fl, ==, FIN, th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			conn_ack(conn, + 1);
			tcp_out(conn, ACK);
			next = TCP_CLOSING;
		} else if (th && FL(&fl, ==, ACK, th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			next = TCP_FIN_WAIT_2;
		}
		break;<commit_msg>net: tcp2: Fix FIN+ACK retransmissions in ESTABLISHED<commit_after>/*
 * Copyright (c) 2018-2020 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <logging/log.h>
LOG_MODULE_REGISTER(net_tcp, CONFIG_NET_TCP_LOG_LEVEL);

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <zephyr.h>
#include <net/net_pkt.h>
#include <net/net_context.h>
#include <net/udp.h>
#include "ipv4.h"
#include "ipv6.h"
#include "connection.h"
#include "net_stats.h"
#include "net_private.h"
#include "tcp2_priv.h"

static int tcp_rto = CONFIG_NET_TCP_INIT_RETRANSMISSION_TIMEOUT;
static int tcp_retries = 3;
static int tcp_window = NET_IPV6_MTU;

static sys_slist_t tcp_conns = SYS_SLIST_STATIC_INIT(&tcp_conns);

static K_MEM_SLAB_DEFINE(tcp_conns_slab, sizeof(struct tcp),
				CONFIG_NET_MAX_CONTEXTS, 4);

static void tcp_in(struct tcp *conn, struct net_pkt *pkt);
static size_t tcp_data_len(struct net_pkt *pkt);
int net_tcp_finalize(struct net_pkt *pkt);

int (*tcp_send_cb)(struct net_pkt *pkt) = NULL;
size_t (*tcp_recv_cb)(struct tcp *conn, struct net_pkt *pkt) = NULL;

static struct tcphdr *th_get(struct net_pkt *pkt)
{
	NET_PKT_DATA_ACCESS_DEFINE(th_access, struct tcphdr);

	net_pkt_cursor_init(pkt);
	net_pkt_set_overwrite(pkt, true);

	/* net_pkt_ip_hdr_len(), net_pkt_ip_opts_len() account for IPv4/IPv6 */

	net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) +
		     net_pkt_ip_opts_len(pkt));

	return net_pkt_get_data(pkt, &th_access);
}

static size_t tcp_endpoint_len(sa_family_t af)
{
	return (af == AF_INET) ? sizeof(struct sockaddr_in) :
		sizeof(struct sockaddr_in6);
}

static int tcp_endpoint_set(union tcp_endpoint *ep, struct net_pkt *pkt,
			    enum pkt_addr src)
{
	int ret = 0;

	switch (net_pkt_family(pkt)) {
	case AF_INET:
		if (IS_ENABLED(CONFIG_NET_IPV4)) {
			struct net_ipv4_hdr *ip = NET_IPV4_HDR(pkt);
			struct tcphdr *th = th_get(pkt);

			memset(ep, 0, sizeof(*ep));

			ep->sin.sin_port = src == TCP_EP_SRC ? th->th_sport :
							       th->th_dport;
			net_ipaddr_copy(&ep->sin.sin_addr,
					src == TCP_EP_SRC ?
							&ip->src : &ip->dst);
			ep->sa.sa_family = AF_INET;
		} else {
			ret = -EINVAL;
		}

		break;

	case AF_INET6:
		if (IS_ENABLED(CONFIG_NET_IPV6)) {
			struct net_ipv6_hdr *ip = NET_IPV6_HDR(pkt);
			struct tcphdr *th = th_get(pkt);

			memset(ep, 0, sizeof(*ep));

			ep->sin6.sin6_port = src == TCP_EP_SRC ? th->th_sport :
								 th->th_dport;
			net_ipaddr_copy(&ep->sin6.sin6_addr,
					src == TCP_EP_SRC ?
							&ip->src : &ip->dst);
			ep->sa.sa_family = AF_INET6;
		} else {
			ret = -EINVAL;
		}

		break;

	default:
		NET_ERR("Unknown address family: %hu", net_pkt_family(pkt));
		ret = -EINVAL;
	}

	return ret;
}

static const char *tcp_flags(u8_t flags)
{
#define BUF_SIZE 25 /* 6 * 4 + 1 */
	static char buf[BUF_SIZE];
	int len = 0;

	buf[0] = '\0';

	if (flags) {
		if (flags & SYN) {
			len += snprintk(buf + len, BUF_SIZE - len, "SYN,");
		}
		if (flags & FIN) {
			len += snprintk(buf + len, BUF_SIZE - len, "FIN,");
		}
		if (flags & ACK) {
			len += snprintk(buf + len, BUF_SIZE - len, "ACK,");
		}
		if (flags & PSH) {
			len += snprintk(buf + len, BUF_SIZE - len, "PSH,");
		}
		if (flags & RST) {
			len += snprintk(buf + len, BUF_SIZE - len, "RST,");
		}
		if (flags & URG) {
			len += snprintk(buf + len, BUF_SIZE - len, "URG,");
		}

		buf[len - 1] = '\0'; /* delete the last comma */
	}
#undef BUF_SIZE
	return buf;
}

static const char *tcp_th(struct net_pkt *pkt)
{
#define BUF_SIZE 80
	static char buf[BUF_SIZE];
	int len = 0;
	struct tcphdr *th = th_get(pkt);

	buf[0] = '\0';

	if (th->th_off < 5) {
		len += snprintk(buf + len, BUF_SIZE - len,
				"bogus th_off: %hu", (u16_t)th->th_off);
		goto end;
	}

	len += snprintk(buf + len, BUF_SIZE - len,
			"%s Seq=%u", tcp_flags(th->th_flags), th_seq(th));

	if (th->th_flags & ACK) {
		len += snprintk(buf + len, BUF_SIZE - len,
				" Ack=%u", th_ack(th));
	}

	len += snprintk(buf + len, BUF_SIZE - len,
			" Len=%ld", (long)tcp_data_len(pkt));
end:
#undef BUF_SIZE
	return buf;
}

static void tcp_send(struct net_pkt *pkt)
{
	NET_DBG("%s", log_strdup(tcp_th(pkt)));

	tcp_pkt_ref(pkt);

	if (tcp_send_cb) {
		if (tcp_send_cb(pkt) < 0) {
			NET_ERR("net_send_data()");
			tcp_pkt_unref(pkt);
		}
		goto out;
	}

	if (net_send_data(pkt) < 0) {
		NET_ERR("net_send_data()");
		tcp_pkt_unref(pkt);
	}
out:
	tcp_pkt_unref(pkt);
}

static void tcp_send_queue_flush(struct tcp *conn)
{
	struct net_pkt *pkt;

	if (k_delayed_work_remaining_get(&conn->send_timer)) {
		k_delayed_work_cancel(&conn->send_timer);
	}

	while ((pkt = tcp_slist(&conn->send_queue, get,
				struct net_pkt, next))) {
		tcp_pkt_unref(pkt);
	}
}

static int tcp_conn_unref(struct tcp *conn)
{
	int ref_count = atomic_dec(&conn->ref_count) - 1;
	int key;

	NET_DBG("conn: %p, ref_count=%d", conn, ref_count);

	if (ref_count) {
		tp_out(net_context_get_family(conn->context), conn->iface,
		       "TP_TRACE", "event", "CONN_DELETE");
		goto out;
	}

	key = irq_lock();

	if (conn->context->conn_handler) {
		net_conn_unregister(conn->context->conn_handler);
		conn->context->conn_handler = NULL;
	}

	if (conn->context->recv_cb) {
		conn->context->recv_cb(conn->context, NULL, NULL, NULL,
					-ECONNRESET, conn->recv_user_data);
	}

	conn->context->tcp = NULL;

	net_context_unref(conn->context);

	tcp_send_queue_flush(conn);

	k_delayed_work_cancel(&conn->timewait_timer);

	memset(conn, 0, sizeof(*conn));

	sys_slist_find_and_remove(&tcp_conns, (sys_snode_t *)conn);

	k_mem_slab_free(&tcp_conns_slab, (void **)&conn);

	irq_unlock(key);
out:
	return ref_count;
}

int net_tcp_unref(struct net_context *context)
{
	int ref_count = 0;

	NET_DBG("context: %p, conn: %p", context, context->tcp);

	if (context->tcp) {
		ref_count = tcp_conn_unref(context->tcp);
	}

	return ref_count;
}

static void tcp_send_process(struct k_work *work)
{
	struct tcp *conn = CONTAINER_OF(work, struct tcp, send_timer);
	struct net_pkt *pkt = tcp_slist(&conn->send_queue, peek_head,
					struct net_pkt, next);

	NET_DBG("%s %s", log_strdup(tcp_th(pkt)), conn->in_retransmission ?
		"in_retransmission" : "");

	if (conn->in_retransmission) {
		if (conn->send_retries > 0) {
			tcp_send(tcp_pkt_clone(pkt));
			conn->send_retries--;
		} else {
			tcp_conn_unref(conn);
			conn = NULL;
		}
	} else {
		u8_t fl = th_get(pkt)->th_flags;
		bool forget = ACK == fl || PSH == fl || (ACK | PSH) == fl ||
			RST & fl;

		pkt = forget ? tcp_slist(&conn->send_queue, get, struct net_pkt,
						next) : tcp_pkt_clone(pkt);
		tcp_send(pkt);

		if (forget == false && !k_delayed_work_remaining_get(
				&conn->send_timer)) {
			conn->send_retries = tcp_retries;
			conn->in_retransmission = true;
		}
	}

	if (conn && conn->in_retransmission) {
		k_delayed_work_submit(&conn->send_timer, K_MSEC(tcp_rto));
	}
}

static void tcp_send_timer_cancel(struct tcp *conn)
{
	NET_ASSERT(conn->in_retransmission == true, "Not in retransmission");

	k_delayed_work_cancel(&conn->send_timer);

	{
		struct net_pkt *pkt = tcp_slist(&conn->send_queue, get,
						struct net_pkt, next);
		NET_DBG("%s", log_strdup(tcp_th(pkt)));
		tcp_pkt_unref(pkt);
	}

	if (sys_slist_is_empty(&conn->send_queue)) {
		conn->in_retransmission = false;
	} else {
		conn->send_retries = tcp_retries;
		k_delayed_work_submit(&conn->send_timer, K_MSEC(tcp_rto));
	}
}

static const char *tcp_state_to_str(enum tcp_state state, bool prefix)
{
	const char *s = NULL;
#define _(_x) case _x: do { s = #_x; goto out; } while (0)
	switch (state) {
	_(TCP_LISTEN);
	_(TCP_SYN_SENT);
	_(TCP_SYN_RECEIVED);
	_(TCP_ESTABLISHED);
	_(TCP_FIN_WAIT_1);
	_(TCP_FIN_WAIT_2);
	_(TCP_CLOSE_WAIT);
	_(TCP_CLOSING);
	_(TCP_LAST_ACK);
	_(TCP_TIME_WAIT);
	_(TCP_CLOSED);
	}
#undef _
	NET_ASSERT(s, "Invalid TCP state: %u", state);
out:
	return prefix ? s : (s + 4);
}

static const char *tcp_conn_state(struct tcp *conn, struct net_pkt *pkt)
{
#define BUF_SIZE 80
	static char buf[BUF_SIZE];

	snprintk(buf, BUF_SIZE, "%s [%s Seq=%u Ack=%u]", pkt ? tcp_th(pkt) : "",
			tcp_state_to_str(conn->state, false),
			conn->seq, conn->ack);
#undef BUF_SIZE
	return buf;
}

static u8_t *tcp_options_get(struct net_pkt *pkt, int tcp_options_len)
{
	static u8_t options[40]; /* TCP header max options size is 40 */
	struct net_pkt_cursor backup;

	net_pkt_cursor_backup(pkt, &backup);
	net_pkt_cursor_init(pkt);
	net_pkt_skip(pkt, net_pkt_ip_hdr_len(pkt) + net_pkt_ip_opts_len(pkt) +
		     sizeof(struct tcphdr));
	net_pkt_read(pkt, options, tcp_options_len);
	net_pkt_cursor_restore(pkt, &backup);

	return options;
}

static bool tcp_options_check(struct tcp_options *recv_options,
			      struct net_pkt *pkt, ssize_t len)
{
	bool result = len > 0 && ((len % 4) == 0) ? true : false;
	u8_t *options = tcp_options_get(pkt, len);
	u8_t opt, opt_len;

	NET_DBG("len=%zd", len);

	recv_options->mss_found = false;
	recv_options->wnd_found = false;

	for ( ; len >= 1; options += opt_len, len -= opt_len) {
		opt = options[0];

		if (opt == TCPOPT_END) {
			break;
		} else if (opt == TCPOPT_NOP) {
			opt_len = 1;
			continue;
		} else {
			if (len < 2) { /* Only END and NOP can have length 1 */
				NET_ERR("Illegal option %d with length %zd",
					opt, len);
				result = false;
				break;
			}
			opt_len = options[1];
		}
		NET_DBG("opt: %hu, opt_len: %hu", (u16_t)opt, (u16_t)opt_len);

		if (opt_len < 2 || opt_len > len) {
			result = false;
			break;
		}

		switch (opt) {
		case TCPOPT_MAXSEG:
			if (opt_len != 4) {
				result = false;
				goto end;
			}

			recv_options->mss = opt;
			recv_options->mss_found = true;
			break;
		case TCPOPT_WINDOW:
			if (opt_len != 3) {
				result = false;
				goto end;
			}

			recv_options->window = opt;
			recv_options->wnd_found = true;
			break;
		default:
			continue;
		}
	}
end:
	if (false == result) {
		NET_WARN("Invalid TCP options");
	}

	return result;
}

static size_t tcp_data_len(struct net_pkt *pkt)
{
	struct tcphdr *th = th_get(pkt);
	size_t tcp_options_len = (th->th_off - 5) * 4;
	ssize_t len = net_pkt_get_len(pkt) - net_pkt_ip_hdr_len(pkt) -
		net_pkt_ip_opts_len(pkt) - sizeof(*th) - tcp_options_len;

	return len > 0 ? len : 0;
}

static size_t tcp_data_get(struct tcp *conn, struct net_pkt *pkt)
{
	ssize_t len = tcp_data_len(pkt);

	if (tcp_recv_cb) {
		tcp_recv_cb(conn, pkt);
		goto out;
	}

	if (len > 0) {
		if (conn->context->recv_cb) {
			struct net_pkt *up = net_pkt_clone(pkt, K_NO_WAIT);

			net_pkt_cursor_init(up);
			net_pkt_set_overwrite(up, true);

			net_pkt_skip(up, net_pkt_get_len(up) - len);

			net_context_packet_received(
				(struct net_conn *)conn->context->conn_handler,
				up, NULL, NULL, conn->recv_user_data);
		}
	}
 out:
	return len;
}

static int tcp_finalize_pkt(struct net_pkt *pkt)
{
	net_pkt_cursor_init(pkt);

	if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) {
		return net_ipv4_finalize(pkt, IPPROTO_TCP);
	}

	if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) {
		return net_ipv6_finalize(pkt, IPPROTO_TCP);
	}

	return -EINVAL;
}

static int tcp_header_add(struct tcp *conn, struct net_pkt *pkt, u8_t flags)
{
	NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct tcphdr);
	struct tcphdr *th;

	th = (struct tcphdr *)net_pkt_get_data(pkt, &tcp_access);
	if (!th) {
		return -ENOBUFS;
	}

	memset(th, 0, sizeof(struct tcphdr));

	th->th_sport = conn->src.sin.sin_port;
	th->th_dport = conn->dst.sin.sin_port;

	th->th_off = 5;
	th->th_flags = flags;
	th->th_win = htons(conn->win);
	th->th_seq = htonl(conn->seq);

	if (ACK & flags) {
		th->th_ack = htonl(conn->ack);
	}

	return net_pkt_set_data(pkt, &tcp_access);
}

static int ip_header_add(struct tcp *conn, struct net_pkt *pkt)
{
	if (IS_ENABLED(CONFIG_NET_IPV4) && net_pkt_family(pkt) == AF_INET) {
		return net_context_create_ipv4_new(conn->context, pkt,
						&conn->src.sin.sin_addr,
						&conn->dst.sin.sin_addr);
	}

	if (IS_ENABLED(CONFIG_NET_IPV6) && net_pkt_family(pkt) == AF_INET6) {
		return net_context_create_ipv6_new(conn->context, pkt,
						&conn->src.sin6.sin6_addr,
						&conn->dst.sin6.sin6_addr);
	}

	return -EINVAL;
}

static void tcp_out(struct tcp *conn, u8_t flags, ...)
{
	struct net_pkt *pkt;
	size_t len = 0;
	int r;

	pkt = tcp_pkt_alloc(conn, sizeof(struct tcphdr));
	if (!pkt) {
		goto fail;
	}

	if (PSH & flags) {
		struct net_pkt *data_pkt;
		va_list ap;
		va_start(ap, flags);
		data_pkt = va_arg(ap, struct net_pkt *);
		va_end(ap);

		len = net_pkt_get_len(data_pkt);
		/* Append the data buffer to pkt */
		net_pkt_append_buffer(pkt, data_pkt->buffer);

		data_pkt->buffer = NULL;
		tcp_pkt_unref(data_pkt);
	}

	r = ip_header_add(conn, pkt);
	if (r < 0) {
		goto fail;
	}

	r = tcp_header_add(conn, pkt, flags);
	if (r < 0) {
		goto fail;
	}

	r = tcp_finalize_pkt(pkt);
	if (r < 0) {
		goto fail;
	}

	if (len) {
		conn_seq(conn, + len);
	}

	NET_DBG("%s", log_strdup(tcp_th(pkt)));

	if (tcp_send_cb) {
		tcp_send_cb(pkt);
		goto out;
	}

	sys_slist_append(&conn->send_queue, &pkt->next);

	tcp_send_process((struct k_work *)&conn->send_timer);
out:
	return;

fail:
	if (pkt) {
		tcp_pkt_unref(pkt);
	}
}

static void tcp_timewait_timeout(struct k_work *work)
{
	struct tcp *conn = CONTAINER_OF(work, struct tcp, timewait_timer);

	NET_DBG("conn: %p %s", conn, log_strdup(tcp_conn_state(conn, NULL)));

	tcp_conn_unref(conn);
}

static void tcp_conn_ref(struct tcp *conn)
{
	int ref_count = atomic_inc(&conn->ref_count) + 1;

	NET_DBG("conn: %p, ref_count: %d", conn, ref_count);
}

static struct tcp *tcp_conn_alloc(void)
{
	struct tcp *conn = NULL;
	int ret;

	ret = k_mem_slab_alloc(&tcp_conns_slab, (void **)&conn, K_NO_WAIT);
	if (ret) {
		goto out;
	}

	memset(conn, 0, sizeof(*conn));

	k_mutex_init(&conn->lock);

	conn->state = TCP_LISTEN;

	conn->win = tcp_window;

	sys_slist_init(&conn->send_queue);

	k_delayed_work_init(&conn->send_timer, tcp_send_process);

	k_delayed_work_init(&conn->timewait_timer, tcp_timewait_timeout);

	tcp_conn_ref(conn);

	sys_slist_append(&tcp_conns, (sys_snode_t *)conn);
out:
	NET_DBG("conn: %p", conn);

	return conn;
}

int net_tcp_get(struct net_context *context)
{
	int ret = 0, key = irq_lock();
	struct tcp *conn;

	conn = tcp_conn_alloc();
	if (conn == NULL) {
		ret = -ENOMEM;
		goto out;
	}

	/* Mutually link the net_context and tcp connection */
	conn->context = context;
	context->tcp = conn;
out:
	irq_unlock(key);

	return ret;
}

static bool tcp_endpoint_cmp(union tcp_endpoint *ep, struct net_pkt *pkt,
			     enum pkt_addr which)
{
	union tcp_endpoint ep_tmp;

	if (tcp_endpoint_set(&ep_tmp, pkt, which) < 0) {
		return false;
	}

	return !memcmp(ep, &ep_tmp, tcp_endpoint_len(ep->sa.sa_family));
}

static bool tcp_conn_cmp(struct tcp *conn, struct net_pkt *pkt)
{
	return tcp_endpoint_cmp(&conn->src, pkt, TCP_EP_DST) &&
		tcp_endpoint_cmp(&conn->dst, pkt, TCP_EP_SRC);
}

static struct tcp *tcp_conn_search(struct net_pkt *pkt)
{
	bool found = false;
	struct tcp *conn;

	SYS_SLIST_FOR_EACH_CONTAINER(&tcp_conns, conn, next) {

		found = tcp_conn_cmp(conn, pkt);
		if (found) {
			break;
		}
	}

	return found ? conn : NULL;
}

static struct tcp *tcp_conn_new(struct net_pkt *pkt);

static enum net_verdict tcp_recv(struct net_conn *net_conn,
				 struct net_pkt *pkt,
				 union net_ip_header *ip,
				 union net_proto_header *proto,
				 void *user_data)
{
	struct tcp *conn;
	struct tcphdr *th;

	ARG_UNUSED(net_conn);
	ARG_UNUSED(proto);

	conn = tcp_conn_search(pkt);
	if (conn) {
		goto in;
	}

	th = th_get(pkt);

	if (th->th_flags & SYN && !(th->th_flags & ACK)) {
		struct tcp *conn_old = ((struct net_context *)user_data)->tcp;

		conn = tcp_conn_new(pkt);

		net_ipaddr_copy(&conn_old->context->remote, &conn->dst.sa);

		conn_old->accept_cb(conn->context,
				    &conn_old->context->remote,
				    sizeof(struct sockaddr), 0,
				    conn_old->context);
	}
 in:
	if (conn) {
		tcp_in(conn, pkt);
	}

	return NET_DROP;
}

/* Create a new tcp connection, as a part of it, create and register
 * net_context
 */
static struct tcp *tcp_conn_new(struct net_pkt *pkt)
{
	struct tcp *conn = NULL;
	struct net_context *context = NULL;
	sa_family_t af = net_pkt_family(pkt);
	struct sockaddr local_addr = { 0 };
	int ret;

	ret = net_context_get(af, SOCK_STREAM, IPPROTO_TCP, &context);
	if (ret < 0) {
		NET_ERR("net_context_get(): %d", ret);
		goto err;
	}

	conn = context->tcp;
	conn->iface = pkt->iface;

	net_context_set_family(conn->context, net_pkt_family(pkt));

	if (tcp_endpoint_set(&conn->dst, pkt, TCP_EP_SRC) < 0) {
		net_context_unref(context);
		conn = NULL;
		goto err;
	}

	if (tcp_endpoint_set(&conn->src, pkt, TCP_EP_DST) < 0) {
		net_context_unref(context);
		conn = NULL;
		goto err;
	}

	NET_DBG("conn: src: %s, dst: %s",
		log_strdup(net_sprint_addr(conn->src.sa.sa_family,
				(const void *)&conn->src.sin.sin_addr)),
		log_strdup(net_sprint_addr(conn->dst.sa.sa_family,
				(const void *)&conn->dst.sin.sin_addr)));

	memcpy(&context->remote, &conn->dst, sizeof(context->remote));
	context->flags |= NET_CONTEXT_REMOTE_ADDR_SET;

	net_sin_ptr(&context->local)->sin_family = af;

	local_addr.sa_family = net_context_get_family(context);

	if (IS_ENABLED(CONFIG_NET_IPV6) &&
	    net_context_get_family(context) == AF_INET6) {
		if (net_sin6_ptr(&context->local)->sin6_addr) {
			net_ipaddr_copy(&net_sin6(&local_addr)->sin6_addr,
				     net_sin6_ptr(&context->local)->sin6_addr);
		}
	} else if (IS_ENABLED(CONFIG_NET_IPV4) &&
		   net_context_get_family(context) == AF_INET) {
		if (net_sin_ptr(&context->local)->sin_addr) {
			net_ipaddr_copy(&net_sin(&local_addr)->sin_addr,
				      net_sin_ptr(&context->local)->sin_addr);
		}
	}

	NET_DBG("context: local: %s, remote: %s",
		log_strdup(net_sprint_addr(
		      local_addr.sa_family,
		      (const void *)&net_sin(&local_addr)->sin_addr)),
		log_strdup(net_sprint_addr(
		      context->remote.sa_family,
		      (const void *)&net_sin(&context->remote)->sin_addr)));

	ret = net_conn_register(IPPROTO_TCP, af,
				&context->remote, &local_addr,
				ntohs(conn->dst.sin.sin_port),/* local port */
				ntohs(conn->src.sin.sin_port),/* remote port */
				tcp_recv, context,
				&context->conn_handler);
	if (ret < 0) {
		NET_ERR("net_conn_register(): %d", ret);
		net_context_unref(context);
		conn = NULL;
		goto err;
	}
err:
	return conn;
}

/* TCP state machine, everything happens here */
static void tcp_in(struct tcp *conn, struct net_pkt *pkt)
{
	struct tcphdr *th = pkt ? th_get(pkt) : NULL;
	u8_t next = 0, fl = th ? th->th_flags : 0;
	size_t tcp_options_len = th ? (th->th_off - 5) * 4 : 0;
	size_t len;

	k_mutex_lock(&conn->lock, K_FOREVER);

	NET_DBG("%s", log_strdup(tcp_conn_state(conn, pkt)));

	if (th && th->th_off < 5) {
		tcp_out(conn, RST);
		conn_state(conn, TCP_CLOSED);
		goto next_state;
	}

	if (tcp_options_len && !tcp_options_check(&conn->recv_options, pkt,
						  tcp_options_len)) {
		NET_DBG("DROP: Invalid TCP option list");
		tcp_out(conn, RST);
		conn_state(conn, TCP_CLOSED);
		goto next_state;
	}

	if (FL(&fl, &, RST)) {
		conn_state(conn, TCP_CLOSED);
	}
next_state:
	len = pkt ? tcp_data_len(pkt) : 0;

	switch (conn->state) {
	case TCP_LISTEN:
		if (FL(&fl, ==, SYN)) {
			conn_ack(conn, th_seq(th) + 1); /* capture peer's isn */
			tcp_out(conn, SYN | ACK);
			conn_seq(conn, + 1);
			next = TCP_SYN_RECEIVED;
		} else {
			tcp_out(conn, SYN);
			conn_seq(conn, + 1);
			next = TCP_SYN_SENT;
		}
		break;
	case TCP_SYN_RECEIVED:
		if (FL(&fl, &, ACK, th_ack(th) == conn->seq &&
				th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			next = TCP_ESTABLISHED;
			net_context_set_state(conn->context,
					      NET_CONTEXT_CONNECTED);
			if (len) {
				tcp_data_get(conn, pkt);
				conn_ack(conn, + len);
				tcp_out(conn, ACK);
			}
		}
		break;
	case TCP_SYN_SENT:
		/* if we are in SYN SENT and receive only a SYN without an
		 * ACK , shouldn't we go to SYN RECEIVED state? See Figure
		 * 6 of RFC 793
		 */
		if (FL(&fl, &, ACK, th && th_ack(th) == conn->seq)) {
			tcp_send_timer_cancel(conn);
			next = TCP_ESTABLISHED;
			net_context_set_state(conn->context,
					      NET_CONTEXT_CONNECTED);
			if (FL(&fl, &, PSH)) {
				tcp_data_get(conn, pkt);
			}
			if (FL(&fl, &, SYN)) {
				conn_ack(conn, th_seq(th) + 1);
				tcp_out(conn, ACK);
			}
		}
		break;
	case TCP_ESTABLISHED:
		/* full-close */
		if (th && FL(&fl, ==, (FIN | ACK), th_seq(th) == conn->ack)) {
			conn_ack(conn, + 1);
			tcp_out(conn, FIN | ACK);
			next = TCP_LAST_ACK;
			break;
		} else if (th && FL(&fl, ==, FIN, th_seq(th) == conn->ack)) {
			conn_ack(conn, + 1);
			tcp_out(conn, ACK);
			next = TCP_CLOSE_WAIT;
			break;
		}

		if (len) {
			if (th_seq(th) == conn->ack) {
				tcp_data_get(conn, pkt);
				conn_ack(conn, + len);
				tcp_out(conn, ACK);
			} else if (th_seq(th) < conn->ack) {
				tcp_out(conn, ACK); /* peer has resent */
			}
		}
		break;
	case TCP_CLOSE_WAIT:
		tcp_out(conn, FIN);
		next = TCP_LAST_ACK;
		break;
	case TCP_LAST_ACK:
		if (th && FL(&fl, ==, ACK, th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			next = TCP_CLOSED;
		}
		break;
	case TCP_CLOSED:
		tcp_conn_unref(conn);
		break;
	case TCP_FIN_WAIT_1:
		if (th && FL(&fl, ==, (FIN | ACK), th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			conn_ack(conn, + 1);
			tcp_out(conn, ACK);
			next = TCP_TIME_WAIT;
		} else if (th && FL(&fl, ==, FIN, th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			conn_ack(conn, + 1);
			tcp_out(conn, ACK);
			next = TCP_CLOSING;
		} else if (th && FL(&fl, ==, ACK, th_seq(th) == conn->ack)) {
			tcp_send_timer_cancel(conn);
			next = TCP_FIN_WAIT_2;
		}
		break;
{'fraction_non_alphanumeric': 0.15264087781290683, 'fraction_numerical': 0.007346103775339409, 'mean_word_length': 4.0359400608756735, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 382, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '879242', 'n_tokens_mistral': 19537, 'n_tokens_neox': 18330, 'n_words': 3602}