+ case SYN_RECEIVED:
+ // Send SYNACK again
+ pkt->hdr.seq = c->snd.nxt;
+ pkt->hdr.ack = c->rcv.nxt;
+ pkt->hdr.ctl = SYN | ACK;
+ print_packet(c, "rtrx", pkt, sizeof(pkt->hdr));
+ utcp->send(utcp, pkt, sizeof(pkt->hdr));
+ break;
+
+ case ESTABLISHED:
+ case FIN_WAIT_1:
+ case CLOSE_WAIT:
+ case CLOSING:
+ case LAST_ACK:
+ // Send unacked data again.
+ pkt->hdr.seq = c->snd.una;
+ pkt->hdr.ack = c->rcv.nxt;
+ pkt->hdr.ctl = ACK;
+ uint32_t len = seqdiff(c->snd.last, c->snd.una);
+
+ if(len > utcp->mtu) {
+ len = utcp->mtu;
+ }
+
+ if(fin_wanted(c, c->snd.una + len)) {
+ len--;
+ pkt->hdr.ctl |= FIN;
+ }
+
+ c->snd.nxt = c->snd.una + len;
+
+ // RFC 5681 slow start after timeout
+ c->snd.ssthresh = max(c->snd.cwnd / 2, utcp->mtu * 2); // eq. 4
+ c->snd.cwnd = utcp->mtu;
+ debug_cwnd(c);
+
+ buffer_copy(&c->sndbuf, pkt->data, 0, len);
+ print_packet(c, "rtrx", pkt, sizeof(pkt->hdr) + len);
+ utcp->send(utcp, pkt, sizeof(pkt->hdr) + len);
+ break;
+
+ case CLOSED:
+ case LISTEN:
+ case TIME_WAIT:
+ case FIN_WAIT_2:
+ // We shouldn't need to retransmit anything in this state.
+#ifdef UTCP_DEBUG
+ abort();
+#endif
+ stop_retransmit_timer(c);
+ goto cleanup;
+ }
+
+ start_retransmit_timer(c);
+ utcp->rto *= 2;
+
+ if(utcp->rto > MAX_RTO) {
+ utcp->rto = MAX_RTO;
+ }
+
+ c->rtt_start.tv_sec = 0; // invalidate RTT timer
+
+cleanup:
+ free(pkt);
+}
+
+/* Update receive buffer and SACK entries after consuming data.
+ *
+ * Situation:
+ *
+ * |.....0000..1111111111.....22222......3333|
+ * |---------------^
+ *
+ * 0..3 represent the SACK entries. The ^ indicates up to which point we want
+ * to remove data from the receive buffer. The idea is to substract "len"
+ * from the offset of all the SACK entries, and then remove/cut down entries
+ * that are shifted to before the start of the receive buffer.
+ *
+ * There are three cases:
+ * - the SACK entry is after ^, in that case just change the offset.
+ * - the SACK entry starts before and ends after ^, so we have to
+ * change both its offset and size.
+ * - the SACK entry is completely before ^, in that case delete it.
+ */
+static void sack_consume(struct utcp_connection *c, size_t len) {
+ debug(c, "sack_consume %lu\n", (unsigned long)len);
+
+ if(len > c->rcvbuf.used) {
+ debug(c, "all SACK entries consumed\n");
+ c->sacks[0].len = 0;
+ return;
+ }
+
+ buffer_get(&c->rcvbuf, NULL, len);
+
+ for(int i = 0; i < NSACKS && c->sacks[i].len;) {
+ if(len < c->sacks[i].offset) {
+ c->sacks[i].offset -= len;
+ i++;
+ } else if(len < c->sacks[i].offset + c->sacks[i].len) {
+ c->sacks[i].len -= len - c->sacks[i].offset;
+ c->sacks[i].offset = 0;
+ i++;
+ } else {
+ if(i < NSACKS - 1) {
+ memmove(&c->sacks[i], &c->sacks[i + 1], (NSACKS - 1 - i) * sizeof(c->sacks)[i]);
+ c->sacks[NSACKS - 1].len = 0;
+ } else {
+ c->sacks[i].len = 0;
+ break;
+ }
+ }
+ }
+
+ for(int i = 0; i < NSACKS && c->sacks[i].len; i++) {
+ debug(c, "SACK[%d] offset %u len %u\n", i, c->sacks[i].offset, c->sacks[i].len);
+ }
+}
+
+static void handle_out_of_order(struct utcp_connection *c, uint32_t offset, const void *data, size_t len) {
+ debug(c, "out of order packet, offset %u\n", offset);
+ // Packet loss or reordering occured. Store the data in the buffer.
+ ssize_t rxd = buffer_put_at(&c->rcvbuf, offset, data, len);
+
+ if(rxd < 0 || (size_t)rxd < len) {
+ abort();
+ }
+
+ // Make note of where we put it.
+ for(int i = 0; i < NSACKS; i++) {
+ if(!c->sacks[i].len) { // nothing to merge, add new entry
+ debug(c, "new SACK entry %d\n", i);
+ c->sacks[i].offset = offset;
+ c->sacks[i].len = rxd;