Index: uspace/srv/net/tl/tcp/conn.c
===================================================================
--- uspace/srv/net/tl/tcp/conn.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
+++ uspace/srv/net/tl/tcp/conn.c	(revision 0093ab6197b0d433bdcee794c4f447e8c8343015)
@@ -38,4 +38,5 @@
 #include <errno.h>
 #include <io/log.h>
+#include <macros.h>
 #include <stdlib.h>
 #include "conn.h"
@@ -46,4 +47,6 @@
 #include "tqueue.h"
 
+#define RCV_BUF_SIZE 4096
+
 LIST_INITIALIZE(conn_list);
 
@@ -54,8 +57,22 @@
 	tcp_conn_t *conn;
 
+	/* Allocate connection structure */
 	conn = calloc(1, sizeof(tcp_conn_t));
 	if (conn == NULL)
 		return NULL;
 
+	/* Allocate receive buffer */
+	conn->rcv_buf_size = RCV_BUF_SIZE;
+	conn->rcv_buf_used = 0;
+	conn->rcv_buf = calloc(1, conn->rcv_buf_size);
+	if (conn->rcv_buf == NULL) {
+		free(conn);
+		return NULL;
+	}
+
+	/* Set up receive window. */
+	conn->rcv_wnd = conn->rcv_buf_size;
+
+	/* Initialize incoming segment queue */
 	tcp_iqueue_init(&conn->incoming, conn);
 
@@ -175,4 +192,6 @@
 
 	tcp_tqueue_ctrl_seg(conn, CTL_SYN | CTL_ACK /* XXX */);
+
+	tcp_segment_delete(seg);
 }
 
@@ -225,4 +244,6 @@
 		tcp_tqueue_ctrl_seg(conn, CTL_SYN | CTL_ACK /* XXX */);
 	}
+
+	tcp_segment_delete(seg);
 }
 
@@ -288,10 +309,21 @@
 static cproc_t tcp_conn_seg_proc_ack_est(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	log_msg(LVL_DEBUG, "tcp_conn_seg_proc_ack_est(%p, %p)", conn, seg);
+
+	log_msg(LVL_DEBUG, "SEG.ACK=%u, SND.UNA=%u, SND.NXT=%u",
+	    (unsigned)seg->ack, (unsigned)conn->snd_una,
+	    (unsigned)conn->snd_nxt);
+
 	if (!seq_no_ack_acceptable(conn, seg->ack)) {
+		log_msg(LVL_DEBUG, "ACK not acceptable.");
 		if (!seq_no_ack_duplicate(conn, seg->ack)) {
+			log_msg(LVL_WARN, "Not acceptable, not duplicate. "
+			    "Send ACK and drop.");
 			/* Not acceptable, not duplicate. Send ACK and drop. */
 			tcp_tqueue_ctrl_seg(conn, CTL_ACK);
 			tcp_segment_delete(seg);
 			return cp_done;
+		} else {
+			log_msg(LVL_DEBUG, "Ignoring duplicate ACK.");
 		}
 	} else {
@@ -401,6 +433,12 @@
 }
 
+/** Process segment text. */
 static cproc_t tcp_conn_seg_proc_text(tcp_conn_t *conn, tcp_segment_t *seg)
 {
+	size_t text_size;
+	size_t xfer_size;
+
+	log_msg(LVL_DEBUG, "tcp_conn_seg_proc_text(%p, %p)", conn, seg);
+
 	switch (conn->cstate) {
 	case st_established:
@@ -422,5 +460,43 @@
 	}
 
-	/* TODO Process segment text */
+	/*
+	 * Process segment text
+	 */
+	assert(seq_no_segment_ready(conn, seg));
+
+	/* Trim anything outside our receive window */
+	tcp_conn_trim_seg_to_wnd(conn, seg);
+
+	/* Determine how many bytes to copy */
+	text_size = tcp_segment_text_size(seg);
+	xfer_size = min(text_size, conn->rcv_buf_size - conn->rcv_buf_used);
+
+	/* Copy data to receive buffer */
+	tcp_segment_text_copy(seg, conn->rcv_buf, xfer_size);
+
+	/* Advance RCV.NXT */
+	conn->rcv_nxt += xfer_size;
+
+	/* Update receive window. XXX Not an efficient strategy. */
+	conn->rcv_wnd -= xfer_size;
+
+	/* XXX Signal user that some data arrived. */
+
+	/* Send ACK */
+	if (xfer_size > 0)
+		tcp_tqueue_ctrl_seg(conn, CTL_ACK);
+
+	/* Anything left in the segment? (text, FIN) */
+	if (xfer_size < seg->len) {
+		/*
+		 * Some text or control remains. Insert remainder back
+		 * into the incoming queue.
+		 */
+		tcp_conn_trim_seg_to_wnd(conn, seg);
+		tcp_iqueue_insert_seg(&conn->incoming, seg);
+
+		return cp_done;
+	}
+
 	return cp_continue;
 }
@@ -472,4 +548,6 @@
 	if (tcp_conn_seg_proc_fin(conn, seg) == cp_done)
 		return;
+
+	tcp_segment_delete(seg);
 }
 
@@ -498,4 +576,12 @@
 }
 
+void tcp_conn_trim_seg_to_wnd(tcp_conn_t *conn, tcp_segment_t *seg)
+{
+	uint32_t left, right;
+
+	seq_no_seg_trim_calc(conn, seg, &left, &right);
+	tcp_segment_trim(seg, left, right);
+}
+
 void tcp_unexpected_segment(tcp_sockpair_t *sp, tcp_segment_t *seg)
 {
Index: uspace/srv/net/tl/tcp/conn.h
===================================================================
--- uspace/srv/net/tl/tcp/conn.h	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
+++ uspace/srv/net/tl/tcp/conn.h	(revision 0093ab6197b0d433bdcee794c4f447e8c8343015)
@@ -43,4 +43,5 @@
 extern tcp_conn_t *tcp_conn_find(tcp_sockpair_t *);
 extern void tcp_conn_segment_arrived(tcp_conn_t *, tcp_segment_t *);
+extern void tcp_conn_trim_seg_to_wnd(tcp_conn_t *, tcp_segment_t *);
 extern void tcp_unexpected_segment(tcp_sockpair_t *, tcp_segment_t *);
 extern void tcp_sockpair_flipped(tcp_sockpair_t *, tcp_sockpair_t *);
Index: uspace/srv/net/tl/tcp/header.c
===================================================================
--- uspace/srv/net/tl/tcp/header.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
+++ uspace/srv/net/tl/tcp/header.c	(revision 0093ab6197b0d433bdcee794c4f447e8c8343015)
@@ -61,5 +61,5 @@
 
 	/* XXX This will only work as long as we don't have any header options */
-	phdr->tcp_length = sizeof(tcp_header_t) + tcp_segment_data_len(seg);
+	phdr->tcp_length = sizeof(tcp_header_t) + tcp_segment_text_size(seg);
 }
 
Index: uspace/srv/net/tl/tcp/segment.c
===================================================================
--- uspace/srv/net/tl/tcp/segment.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
+++ uspace/srv/net/tl/tcp/segment.c	(revision 0093ab6197b0d433bdcee794c4f447e8c8343015)
@@ -35,4 +35,5 @@
  */
 
+#include <mem.h>
 #include <stdlib.h>
 #include "segment.h"
@@ -79,5 +80,67 @@
 }
 
-size_t tcp_segment_data_len(tcp_segment_t *seg)
+/** Trim segment to the specified interval.
+ *
+ * Trim any text or control whose sequence number is outside of [lo, hi)
+ * interval.
+ */
+void tcp_segment_trim(tcp_segment_t *seg, uint32_t left, uint32_t right)
+{
+	uint32_t t_size;
+
+	assert(left + right <= seg->len);
+
+	/* Special case, entire segment trimmed from left */
+	if (left == seg->len) {
+		seg->seq = seg->seq + seg->len;
+		seg->len = 0;
+		return;
+	}
+
+	/* Special case, entire segment trimmed from right */
+	if (right == seg->len) {
+		seg->len = 0;
+		return;
+	}
+
+	/* General case */
+
+	t_size = tcp_segment_text_size(seg);
+
+	if (left > 0 && (seg->ctrl & CTL_SYN) != 0) {
+		/* Trim SYN */
+		seg->ctrl &= ~CTL_SYN;
+		seg->seq++;
+		seg->len--;
+		left--;
+	}
+
+	if (right > 0 && (seg->ctrl & CTL_FIN) != 0) {
+		/* Trim FIN */
+		seg->ctrl &= ~CTL_FIN;
+		seg->len--;
+		right--;
+	}
+
+	if (left > 0 || right > 0) {
+		/* Trim segment text */
+		assert(left + right <= t_size);
+
+		seg->data += left;
+		seg->len -= left + right;
+	}
+}
+
+/** Copy out text data from segment.
+ *
+ */
+void tcp_segment_text_copy(tcp_segment_t *seg, void *buf, size_t size)
+{
+	assert(size <= tcp_segment_text_size(seg));
+	memcpy(buf, seg->data, size);
+}
+
+/** Return number of bytes in segment text. */
+size_t tcp_segment_text_size(tcp_segment_t *seg)
 {
 	return seg->len - seq_no_control_len(seg->ctrl);
Index: uspace/srv/net/tl/tcp/segment.h
===================================================================
--- uspace/srv/net/tl/tcp/segment.h	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
+++ uspace/srv/net/tl/tcp/segment.h	(revision 0093ab6197b0d433bdcee794c4f447e8c8343015)
@@ -36,4 +36,5 @@
 #define SEGMENT_H
 
+#include <sys/types.h>
 #include "tcp_type.h"
 
@@ -42,5 +43,7 @@
 extern tcp_segment_t *tcp_segment_make_ctrl(tcp_control_t);
 extern tcp_segment_t *tcp_segment_make_rst(tcp_segment_t *);
-extern size_t tcp_segment_data_len(tcp_segment_t *);
+extern void tcp_segment_trim(tcp_segment_t *, uint32_t, uint32_t);
+extern void tcp_segment_text_copy(tcp_segment_t *, void *, size_t);
+extern size_t tcp_segment_text_size(tcp_segment_t *);
 
 
Index: uspace/srv/net/tl/tcp/seq_no.c
===================================================================
--- uspace/srv/net/tl/tcp/seq_no.c	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
+++ uspace/srv/net/tl/tcp/seq_no.c	(revision 0093ab6197b0d433bdcee794c4f447e8c8343015)
@@ -71,5 +71,5 @@
  *
  * ACK is duplicate if it refers to a sequence number that has
- * aleady been acked (SEG.ACK < SND.UNA).
+ * aleady been acked (SEG.ACK <= SND.UNA).
  */
 bool seq_no_ack_duplicate(tcp_conn_t *conn, uint32_t seg_ack)
@@ -81,8 +81,9 @@
 	 * equivalent of SEG.ACK < SND.UNA. Thus we do it
 	 * on a best-effort basis, based on the difference.
-	 * [-2^31, 0) means less-than, [0, 2^31) means greater-than.
+	 * [-2^31, 0) means less-than, 0 means equal, [0, 2^31)
+	 * means greater-than. Less-than or equal means duplicate.
 	 */
 	diff = seg_ack - conn->snd_una;
-	return (diff & (0x1 << 31)) != 0;
+	return diff == 0 || (diff & (0x1 << 31)) != 0;
 }
 
@@ -176,4 +177,34 @@
 }
 
+/** Calculate the amount of trim needed to fit segment in receive window. */
+extern void seq_no_seg_trim_calc(tcp_conn_t *conn, tcp_segment_t *seg,
+    uint32_t *left, uint32_t *right)
+{
+	assert(seq_no_segment_acceptable(conn, seg));
+
+	/*
+	 * If RCV.NXT is between SEG.SEQ and RCV.NXT+RCV.WND, then
+	 * left trim amount is positive
+	 */
+	if (seq_no_lt_le(seg->seq, conn->rcv_nxt,
+	    conn->rcv_nxt + conn->rcv_wnd)) {
+		*left = conn->rcv_nxt - seg->seq;
+	} else {
+		*left = 0;
+	}
+
+	/*
+	 * If SEG.SEQ+SEG.LEN is between SEG.SEQ and RCV.NXT+RCV.WND,
+	 * then right trim is zero.
+	 */
+	if (seq_no_lt_le(seg->seq - 1, seg->seq + seg->len,
+	    conn->rcv_nxt + conn->rcv_wnd)) {
+		*right = 0;
+	} else {
+		*right = (seg->seq + seg->len) -
+		    (conn->rcv_nxt + conn->rcv_wnd);
+	}
+}
+
 /**
  * @}
Index: uspace/srv/net/tl/tcp/seq_no.h
===================================================================
--- uspace/srv/net/tl/tcp/seq_no.h	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
+++ uspace/srv/net/tl/tcp/seq_no.h	(revision 0093ab6197b0d433bdcee794c4f447e8c8343015)
@@ -46,4 +46,6 @@
 extern bool seq_no_segment_ready(tcp_conn_t *, tcp_segment_t *);
 extern bool seq_no_segment_acceptable(tcp_conn_t *, tcp_segment_t *);
+extern void seq_no_seg_trim_calc(tcp_conn_t *, tcp_segment_t *, uint32_t *,
+    uint32_t *);
 
 extern uint32_t seq_no_control_len(tcp_control_t);
Index: uspace/srv/net/tl/tcp/tcp_type.h
===================================================================
--- uspace/srv/net/tl/tcp/tcp_type.h	(revision 4c55a64c9c0a99298966be5936bc0318dc7f0c39)
+++ uspace/srv/net/tl/tcp/tcp_type.h	(revision 0093ab6197b0d433bdcee794c4f447e8c8343015)
@@ -109,4 +109,10 @@
 	tcp_iqueue_t incoming;
 
+	/** Receive buffer */
+	uint8_t *rcv_buf;
+	/** Receive buffer size */
+	size_t rcv_buf_size;
+	size_t rcv_buf_used;
+
 	/** Send unacknowledged */
 	uint32_t snd_una;
@@ -153,6 +159,8 @@
 	uint32_t up;
 
-	/** Segment data */
+	/** Segment data, may be moved when trimming segment */
 	void *data;
+	/** Segment data, original pointer used to free data */
+	void *dfptr;
 } tcp_segment_t;
 
