Index: uspace/srv/net/tl/tcp/conn.c
===================================================================
--- uspace/srv/net/tl/tcp/conn.c	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/conn.c	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -48,4 +48,5 @@
 
 #define RCV_BUF_SIZE 4096
+#define SND_BUF_SIZE 4096
 
 LIST_INITIALIZE(conn_list);
@@ -77,4 +78,13 @@
 	}
 
+	/** Allocate send buffer */
+	conn->snd_buf_size = SND_BUF_SIZE;
+	conn->snd_buf_used = 0;
+	conn->snd_buf = calloc(1, conn->snd_buf_size);
+	if (conn->snd_buf == NULL) {
+		free(conn);
+		return NULL;
+	}
+
 	/* Set up receive window. */
 	conn->rcv_wnd = conn->rcv_buf_size;
@@ -171,4 +181,31 @@
 
 	return NULL;
+}
+
+/** Determine if SYN has been received.
+ *
+ * @param conn	Connection
+ * @return	@c true if SYN has been received, @c false otherwise.
+ */
+bool tcp_conn_got_syn(tcp_conn_t *conn)
+{
+	switch (conn->cstate) {
+	case st_listen:
+	case st_syn_sent:
+		return false;
+	case st_syn_received:
+	case st_established:
+	case st_fin_wait_1:
+	case st_fin_wait_2:
+	case st_close_wait:
+	case st_closing:
+	case st_last_ack:
+	case st_time_wait:
+		return true;
+	case st_closed:
+		assert(false);
+	}
+
+	assert(false);
 }
 
@@ -273,4 +310,15 @@
 
 	log_msg(LVL_DEBUG, "Sent SYN, got SYN.");
+
+	/*
+	 * Surprisingly the spec does not deal with initial window setting.
+	 * Set SND.WND = SEG.WND and set SND.WL1 so that next segment
+	 * will always be accepted as new window setting.
+	 */
+	log_msg(LVL_DEBUG, "SND.WND := %" PRIu32 ", SND.WL1 := %" PRIu32 ", "
+	    "SND.WL2 = %" PRIu32, seg->wnd, seg->seq, seg->seq);
+	conn->snd_wnd = seg->wnd;
+	conn->snd_wl1 = seg->seq;
+	conn->snd_wl2 = seg->seq;
 
 	if (seq_no_syn_acked(conn)) {
@@ -618,4 +666,6 @@
 	tcp_segment_text_copy(seg, conn->rcv_buf, xfer_size);
 
+	log_msg(LVL_DEBUG, "Received %zu bytes of data.", xfer_size);
+
 	/* Advance RCV.NXT */
 	conn->rcv_nxt += xfer_size;
@@ -764,5 +814,5 @@
 /** Compute flipped socket pair for response.
  *
- * Flipped socket pair has local and foreign sockes exchanged.
+ * Flipped socket pair has local and foreign sockets exchanged.
  *
  * @param sp		Socket pair
Index: uspace/srv/net/tl/tcp/conn.h
===================================================================
--- uspace/srv/net/tl/tcp/conn.h	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/conn.h	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -36,4 +36,5 @@
 #define CONN_H
 
+#include <bool.h>
 #include "tcp_type.h"
 
@@ -42,4 +43,5 @@
 extern void tcp_conn_sync(tcp_conn_t *);
 extern tcp_conn_t *tcp_conn_find(tcp_sockpair_t *);
+extern bool tcp_conn_got_syn(tcp_conn_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 *);
Index: uspace/srv/net/tl/tcp/segment.c
===================================================================
--- uspace/srv/net/tl/tcp/segment.c	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/segment.c	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -53,7 +53,7 @@
 }
 
-/** Create a control segment.
- *
-  * @return	Control segment
+/** Create a control-only segment.
+ *
+  * @return	Segment
  */
 tcp_segment_t *tcp_segment_make_ctrl(tcp_control_t ctrl)
@@ -89,4 +89,34 @@
 	return rseg;
 }
+
+/** Create a control segment.
+ *
+  * @return	Segment
+ */
+tcp_segment_t *tcp_segment_make_data(tcp_control_t ctrl, void *data,
+    size_t size)
+{
+	tcp_segment_t *seg;
+
+	assert(size > 0);
+
+	seg = tcp_segment_new();
+	if (seg == NULL)
+		return NULL;
+
+	seg->ctrl = ctrl;
+	seg->len = seq_no_control_len(ctrl) + size;
+
+	seg->dfptr = seg->data = malloc(size);
+	if (seg->dfptr == NULL) {
+		free(seg);
+		return NULL;
+	}
+
+	memcpy(seg->data, data, size);
+
+	return seg;
+}
+
 
 /** Trim segment from left and right by the specified amount.
Index: uspace/srv/net/tl/tcp/segment.h
===================================================================
--- uspace/srv/net/tl/tcp/segment.h	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/segment.h	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -43,4 +43,5 @@
 extern tcp_segment_t *tcp_segment_make_ctrl(tcp_control_t);
 extern tcp_segment_t *tcp_segment_make_rst(tcp_segment_t *);
+extern tcp_segment_t *tcp_segment_make_data(tcp_control_t, void *, size_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);
Index: uspace/srv/net/tl/tcp/state.c
===================================================================
--- uspace/srv/net/tl/tcp/state.c	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/state.c	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -36,7 +36,10 @@
 
 #include <io/log.h>
+#include <macros.h>
+#include <mem.h>
 #include "conn.h"
 #include "state.h"
 #include "tcp_type.h"
+#include "tqueue.h"
 
 /*
@@ -78,5 +81,24 @@
 void tcp_uc_send(tcp_conn_t *conn, void *data, size_t size, xflags_t flags)
 {
+	size_t buf_free;
+	size_t xfer_size;
+
 	log_msg(LVL_DEBUG, "tcp_uc_send()");
+
+	while (size > 0) {
+		buf_free = conn->snd_buf_size - conn->snd_buf_used;
+		while (buf_free == 0)
+			tcp_tqueue_new_data(conn);
+
+		xfer_size = min(size, buf_free);
+
+		/* Copy data to buffer */
+		memcpy(conn->snd_buf + conn->snd_buf_used, data, xfer_size);
+		data += xfer_size;
+		conn->snd_buf_used += xfer_size;
+		size -= xfer_size;
+	}
+
+	tcp_tqueue_new_data(conn);
 }
 
Index: uspace/srv/net/tl/tcp/tcp_type.h
===================================================================
--- uspace/srv/net/tl/tcp/tcp_type.h	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/tcp_type.h	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -115,4 +115,10 @@
 	size_t rcv_buf_used;
 
+	/** Send buffer */
+	uint8_t *snd_buf;
+	/** Send buffer size */
+	size_t snd_buf_size;
+	size_t snd_buf_used;
+
 	/** Send unacknowledged */
 	uint32_t snd_una;
Index: uspace/srv/net/tl/tcp/test.c
===================================================================
--- uspace/srv/net/tl/tcp/test.c	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/test.c	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -39,4 +39,5 @@
 #include <stdio.h>
 #include <thread.h>
+#include <str.h>
 #include "state.h"
 #include "tcp_type.h"
@@ -59,4 +60,5 @@
 	tcp_conn_t *conn;
 	tcp_sock_t sock;
+	const char *msg = "Hello World!";
 
 	printf("test_cli()\n");
@@ -67,4 +69,7 @@
 	async_usleep(1000*1000*3);
 	tcp_uc_open(1024, &sock, ap_active, &conn);
+
+	async_usleep(1000*1000*10);
+	tcp_uc_send(conn, (void *)msg, str_size(msg), 0);
 }
 
Index: uspace/srv/net/tl/tcp/tqueue.c
===================================================================
--- uspace/srv/net/tl/tcp/tqueue.c	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/tqueue.c	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -37,4 +37,7 @@
 #include <byteorder.h>
 #include <io/log.h>
+#include <macros.h>
+#include <mem.h>
+#include "conn.h"
 #include "header.h"
 #include "rqueue.h"
@@ -50,6 +53,4 @@
 
 	seg = tcp_segment_make_ctrl(ctrl);
-	seg->seq = conn->snd_nxt;
-	seg->ack = conn->rcv_nxt;
 	tcp_tqueue_seg(conn, seg);
 }
@@ -60,6 +61,57 @@
 	/* XXX queue */
 
+	/*
+	 * Always send ACK once we have received SYN, except for RST segments.
+	 * (Spec says we should always send ACK once connection has been
+	 * established.)
+	 */
+	if (tcp_conn_got_syn(conn) && (seg->ctrl & CTL_RST) == 0)
+		seg->ctrl |= CTL_ACK;
+
+	seg->seq = conn->snd_nxt;
+	seg->wnd = conn->rcv_wnd;
+
+	if ((seg->ctrl & CTL_ACK) != 0)
+		seg->ack = conn->rcv_nxt;
+	else
+		seg->ack = 0;
+
 	conn->snd_nxt += seg->len;
+
 	tcp_transmit_segment(&conn->ident, seg);
+}
+
+/** Transmit data from the send buffer.
+ *
+ * @param conn	Connection
+ */
+void tcp_tqueue_new_data(tcp_conn_t *conn)
+{
+	size_t data_size;
+	tcp_segment_t *seg;
+
+	log_msg(LVL_DEBUG, "tcp_tqueue_new_data()");
+
+	data_size = min(conn->snd_buf_used, conn->snd_wnd);
+	log_msg(LVL_DEBUG, "conn->snd_buf_used = %zu, SND.WND = %zu, "
+	    "data_size = %zu", conn->snd_buf_used, conn->snd_wnd, data_size);
+
+	if (data_size == 0)
+		return;
+
+	/* XXX Do not always send immediately */
+
+	seg = tcp_segment_make_data(0, conn->snd_buf, data_size);
+	if (seg == NULL) {
+		log_msg(LVL_ERROR, "Memory allocation failure.");
+		return;
+	}
+
+	/* Remove data from send buffer */
+	memmove(conn->snd_buf, conn->snd_buf + data_size,
+	    conn->snd_buf_used - data_size);
+	conn->snd_buf_used -= data_size;
+
+	tcp_tqueue_seg(conn, seg);
 }
 
Index: uspace/srv/net/tl/tcp/tqueue.h
===================================================================
--- uspace/srv/net/tl/tcp/tqueue.h	(revision 032bbe7a596e67983f2f901811ef9dd4defcd985)
+++ uspace/srv/net/tl/tcp/tqueue.h	(revision 32105348bab1db087504047a62dc8abdcfe3ec3f)
@@ -41,4 +41,5 @@
 extern void tcp_tqueue_ctrl_seg(tcp_conn_t *, tcp_control_t);
 extern void tcp_tqueue_seg(tcp_conn_t *, tcp_segment_t *);
+extern void tcp_tqueue_new_data(tcp_conn_t *);
 extern void tcp_tqueue_remove_acked(tcp_conn_t *);
 extern void tcp_transmit_segment(tcp_sockpair_t *, tcp_segment_t *);
