+/**
+ * Send Window Update ACK,
+ * ensuring that it will be sent once, if RWND became non-zero,
+ * after zero RWND has been advertised in ACK before
+ */
+void
+tcp_send_window_update_ack (tcp_connection_t * tc)
+{
+ tcp_worker_ctx_t *wrk = tcp_get_worker (tc->c_thread_index);
+ u32 win;
+
+ if (tcp_zero_rwnd_sent (tc))
+ {
+ win = tcp_window_to_advertise (tc, tc->state);
+ if (win > 0)
+ {
+ tcp_zero_rwnd_sent_off (tc);
+ tcp_program_ack (wrk, tc);
+ }
+ }
+}
+
+/**
+ * Allocate a new buffer and build a new tcp segment
+ *
+ * @param wrk tcp worker
+ * @param tc connection for which the segment will be allocated
+ * @param offset offset of the first byte in the tx fifo
+ * @param max_deq_byte segment size
+ * @param[out] b pointer to buffer allocated
+ *
+ * @return the number of bytes in the segment or 0 if buffer cannot be
+ * allocated or no data available
+ */
+static int
+tcp_prepare_segment (tcp_worker_ctx_t * wrk, tcp_connection_t * tc,
+ u32 offset, u32 max_deq_bytes, vlib_buffer_t ** b)
+{
+ u32 bytes_per_buffer = vnet_get_tcp_main ()->bytes_per_buffer;
+ vlib_main_t *vm = wrk->vm;
+ u32 bi, seg_size;
+ int n_bytes = 0;
+ u8 *data;
+
+ seg_size = max_deq_bytes + TRANSPORT_MAX_HDRS_LEN;
+
+ /*
+ * Prepare options
+ */
+ tc->snd_opts_len = tcp_make_options (tc, &tc->snd_opts, tc->state);
+
+ /*
+ * Allocate and fill in buffer(s)
+ */
+
+ /* Easy case, buffer size greater than mss */
+ if (PREDICT_TRUE (seg_size <= bytes_per_buffer))
+ {
+ if (PREDICT_FALSE (!vlib_buffer_alloc (vm, &bi, 1)))
+ return 0;
+ *b = vlib_get_buffer (vm, bi);
+ data = tcp_init_buffer (vm, *b);
+ n_bytes = session_tx_fifo_peek_bytes (&tc->connection, data, offset,
+ max_deq_bytes);
+ ASSERT (n_bytes == max_deq_bytes);
+ b[0]->current_length = n_bytes;
+ tcp_push_hdr_i (tc, *b, tc->snd_una + offset, /* compute opts */ 0,
+ /* burst */ 0, /* update_snd_nxt */ 0);
+ }
+ /* Split mss into multiple buffers */
+ else
+ {
+ u32 chain_bi = ~0, n_bufs_per_seg, n_bufs;
+ u16 n_peeked, len_to_deq;
+ vlib_buffer_t *chain_b, *prev_b;
+ int i;
+
+ /* Make sure we have enough buffers */
+ n_bufs_per_seg = ceil ((double) seg_size / bytes_per_buffer);
+ vec_validate_aligned (wrk->tx_buffers, n_bufs_per_seg - 1,
+ CLIB_CACHE_LINE_BYTES);
+ n_bufs = vlib_buffer_alloc (vm, wrk->tx_buffers, n_bufs_per_seg);
+ if (PREDICT_FALSE (n_bufs != n_bufs_per_seg))
+ {
+ if (n_bufs)
+ vlib_buffer_free (vm, wrk->tx_buffers, n_bufs);
+ return 0;
+ }
+
+ *b = vlib_get_buffer (vm, wrk->tx_buffers[--n_bufs]);
+ data = tcp_init_buffer (vm, *b);
+ n_bytes = session_tx_fifo_peek_bytes (&tc->connection, data, offset,
+ bytes_per_buffer -
+ TRANSPORT_MAX_HDRS_LEN);
+ b[0]->current_length = n_bytes;
+ b[0]->flags |= VLIB_BUFFER_TOTAL_LENGTH_VALID;
+ b[0]->total_length_not_including_first_buffer = 0;
+ max_deq_bytes -= n_bytes;
+
+ chain_b = *b;
+ for (i = 1; i < n_bufs_per_seg; i++)
+ {
+ prev_b = chain_b;
+ len_to_deq = clib_min (max_deq_bytes, bytes_per_buffer);
+ chain_bi = wrk->tx_buffers[--n_bufs];
+ chain_b = vlib_get_buffer (vm, chain_bi);
+ chain_b->current_data = 0;
+ data = vlib_buffer_get_current (chain_b);
+ n_peeked = session_tx_fifo_peek_bytes (&tc->connection, data,
+ offset + n_bytes,
+ len_to_deq);
+ ASSERT (n_peeked == len_to_deq);
+ n_bytes += n_peeked;
+ chain_b->current_length = n_peeked;
+ chain_b->next_buffer = 0;
+
+ /* update previous buffer */
+ prev_b->next_buffer = chain_bi;
+ prev_b->flags |= VLIB_BUFFER_NEXT_PRESENT;
+
+ max_deq_bytes -= n_peeked;
+ b[0]->total_length_not_including_first_buffer += n_peeked;
+ }
+
+ tcp_push_hdr_i (tc, *b, tc->snd_una + offset, /* compute opts */ 0,
+ /* burst */ 0, /* update_snd_nxt */ 0);
+
+ if (PREDICT_FALSE (n_bufs))
+ {
+ clib_warning ("not all buffers consumed");
+ vlib_buffer_free (vm, wrk->tx_buffers, n_bufs);
+ }
+ }
+
+ ASSERT (n_bytes > 0);
+ ASSERT (((*b)->current_data + (*b)->current_length) <= bytes_per_buffer);
+
+ return n_bytes;
+}
+
+/**
+ * Build a retransmit segment