734 lines
25 KiB
C
734 lines
25 KiB
C
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <stdalign.h>
|
|
#include "endian.h"
|
|
#include "tcp.h"
|
|
|
|
#ifdef TCP_DEBUG
|
|
#include <stdio.h>
|
|
#define TCP_DEBUG_LOG(fmt, ...) fprintf(stderr, "TCP :: " fmt "\n", ## __VA_ARGS__)
|
|
#else
|
|
#define TCP_DEBUG_LOG(...)
|
|
#endif
|
|
|
|
#define SEGMENT_OFFSET(seg) (cpu_is_little_endian() ? (seg)->offset2 : (seg)->offset1)
|
|
|
|
void tcp_init(tcp_state_t *tcp_state, ip_address_t ip, tcp_callbacks_t callbacks)
|
|
{
|
|
tcp_state->ip = ip;
|
|
tcp_state->callbacks = callbacks;
|
|
|
|
for (size_t i = 0; i < TCP_MAX_SOCKETS-1; i++)
|
|
tcp_state->connection_pool[i].next = tcp_state->connection_pool + i+1;
|
|
tcp_state->connection_pool[TCP_MAX_SOCKETS-1].next = NULL;
|
|
tcp_state->free_connection_list = tcp_state->connection_pool;
|
|
|
|
for (size_t i = 0; i < TCP_MAX_LISTENERS-1; i++)
|
|
tcp_state->listener_pool[i].next = tcp_state->listener_pool + i+1;
|
|
tcp_state->listener_pool[TCP_MAX_LISTENERS-1].next = NULL;
|
|
tcp_state->free_listener_list = tcp_state->listener_pool;
|
|
tcp_state->used_listener_list = NULL;
|
|
}
|
|
|
|
void tcp_free(tcp_state_t *tcp_state)
|
|
{
|
|
// Destroy all listening connections
|
|
while (tcp_state->used_listener_list != NULL)
|
|
tcp_listener_destroy(tcp_state->used_listener_list);
|
|
}
|
|
|
|
void tcp_seconds_passed(tcp_state_t *state, size_t seconds)
|
|
{
|
|
(void) state;
|
|
(void) seconds;
|
|
}
|
|
|
|
static tcp_connection_t*
|
|
connection_create(tcp_listener_t *listener,
|
|
uint32_t seq_no, uint32_t ack_no,
|
|
ip_address_t peer_ip, uint16_t peer_port)
|
|
{
|
|
tcp_state_t *state = listener->state;
|
|
|
|
// Pop a connection structure from the free list
|
|
tcp_connection_t *connection;
|
|
{
|
|
if (state->free_connection_list == NULL)
|
|
// ERROR: Reached connection limit
|
|
return NULL;
|
|
connection = state->free_connection_list;
|
|
state->free_connection_list = connection->next;
|
|
}
|
|
|
|
// Initialize connection structure
|
|
{
|
|
connection->listener = listener;
|
|
|
|
connection->callback_data = NULL;
|
|
connection->callback_ready_to_recv = NULL;
|
|
connection->callback_ready_to_send = NULL;
|
|
|
|
connection->state = TCP_STATE_CLOSED;
|
|
|
|
connection->peer_port = peer_port;
|
|
connection->peer_ip = peer_ip;
|
|
|
|
connection->rcv_unread = ack_no;
|
|
connection->rcv_nxt = ack_no;
|
|
connection->rcv_wnd = TCP_INPUT_BUFFER_SIZE;
|
|
|
|
connection->snd_una = seq_no;
|
|
connection->snd_wnd = 0;
|
|
connection->snd_nxt = 0;
|
|
|
|
connection->prev = NULL;
|
|
connection->next = NULL;
|
|
}
|
|
|
|
// Appent to the list of not yet established connections
|
|
if (listener->non_established_list)
|
|
listener->non_established_list->prev = connection;
|
|
connection->next = listener->non_established_list;
|
|
listener->non_established_list = connection;
|
|
|
|
return connection;
|
|
}
|
|
|
|
static tcp_listener_t*
|
|
find_listener_with_port(tcp_state_t *state, uint16_t port)
|
|
{
|
|
tcp_listener_t *cursor = state->used_listener_list;
|
|
while (cursor) {
|
|
if (cursor->port == port)
|
|
return cursor;
|
|
cursor = cursor->next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static uint32_t choose_sequence_no()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
ip_address_t src_addr;
|
|
ip_address_t dst_addr;
|
|
uint8_t reserved;
|
|
uint8_t protocol;
|
|
uint16_t tcp_length;
|
|
} tcp_pseudoheader_t;
|
|
|
|
static uint16_t
|
|
calculate_checksum(const slice_list_t *slices, size_t num_slices)
|
|
{
|
|
uint32_t sum = 0xffff;
|
|
|
|
for (size_t slice_idx = 0; slice_idx < num_slices; slice_idx++) {
|
|
|
|
const uint16_t *src = slices[slice_idx].src;
|
|
const size_t len = slices[slice_idx].len;
|
|
|
|
for (size_t i = 0; i < len/2; i++) {
|
|
sum += net_to_cpu_u16(src[i]);
|
|
if (sum > 0xffff)
|
|
sum -= 0xffff;
|
|
}
|
|
|
|
if (len & 1) {
|
|
alignas(uint16_t) uint8_t temp[2];
|
|
|
|
temp[0] = ((uint8_t*) slices[slice_idx].src)[len-1];
|
|
temp[1] = 0;
|
|
|
|
uint16_t temp2 = *(uint16_t*) temp;
|
|
sum += net_to_cpu_u16(temp2);
|
|
if (sum > 0xffff)
|
|
sum -= 0xffff;
|
|
}
|
|
}
|
|
|
|
return cpu_to_net_u16(~sum);
|
|
}
|
|
|
|
static void emit_segment(tcp_connection_t *connection, bool ack, bool syn, size_t payload)
|
|
{
|
|
tcp_listener_t *listener = connection->listener;
|
|
tcp_state_t *state = listener->state;
|
|
|
|
size_t payload_being_sent = MIN(payload, connection->snd_wnd);
|
|
size_t total_segment_size = sizeof(tcp_segment_t) + payload_being_sent;
|
|
|
|
uint8_t flags = 0;
|
|
uint32_t ack_no = 0;
|
|
if (ack) {
|
|
flags |= TCP_FLAG_ACK;
|
|
ack_no = connection->rcv_nxt;
|
|
}
|
|
if (syn)
|
|
flags |= TCP_FLAG_SYN;
|
|
|
|
uint32_t seq_no = connection->snd_una;
|
|
//if (payload_being_sent > 0)
|
|
// seq_no++;
|
|
|
|
int offset = 5; // No options
|
|
tcp_segment_t header = {
|
|
.src_port = cpu_to_net_u16(listener->port),
|
|
.dst_port = cpu_to_net_u16(connection->peer_port),
|
|
.flags = flags,
|
|
.seq_no = cpu_to_net_u32(seq_no),
|
|
.ack_no = cpu_to_net_u32(ack_no),
|
|
.offset1 = cpu_is_little_endian() ? 0 : offset,
|
|
.offset2 = cpu_is_little_endian() ? offset : 0,
|
|
.window = cpu_to_net_u16(connection->rcv_wnd), // Why is a 32 bit integer being backed into a 16 bit?
|
|
.checksum = 0, // Will be calculated later
|
|
.urgent_pointer = 0,
|
|
};
|
|
|
|
tcp_pseudoheader_t pseudo_header = {
|
|
.src_addr = state->ip,
|
|
.dst_addr = connection->peer_ip,
|
|
.reserved = 0,
|
|
.protocol = 6, // TCP
|
|
.tcp_length = cpu_to_net_u16(total_segment_size),
|
|
};
|
|
|
|
header.checksum = calculate_checksum((slice_list_t[]) {
|
|
{&pseudo_header, sizeof(tcp_pseudoheader_t)},
|
|
{&header, sizeof(tcp_segment_t)},
|
|
{connection->out_buffer, connection->snd_wnd},
|
|
}, 3);
|
|
|
|
int result = state->callbacks.send(state->callbacks.data, connection->peer_ip, (slice_list_t[]) {
|
|
{&header, sizeof(tcp_segment_t)},
|
|
{connection->out_buffer, payload_being_sent},
|
|
}, 2);
|
|
|
|
if (result < 0) {
|
|
// It wasn't possible to send out bytes. We'll try again later!
|
|
} else {
|
|
size_t actually_sent_bytes = (size_t) result;
|
|
|
|
if (actually_sent_bytes < sizeof(tcp_segment_t)) {
|
|
// Not even the TCP header was sent. I hope this
|
|
// doesn't ever happen!
|
|
assert(0);
|
|
} else {
|
|
size_t actually_sent_payload_bytes = actually_sent_bytes - sizeof(tcp_segment_t);
|
|
connection->snd_nxt = MAX(connection->snd_nxt, connection->snd_una + actually_sent_payload_bytes);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void handle_received_data(tcp_connection_t *connection,
|
|
const void *data, size_t size)
|
|
{
|
|
size_t considered = MIN(size, connection->rcv_wnd);
|
|
|
|
if (considered > 0) {
|
|
size_t input_buffer_usage = TCP_INPUT_BUFFER_SIZE - connection->rcv_wnd;
|
|
memcpy(connection->in_buffer + input_buffer_usage, data, considered);
|
|
connection->rcv_wnd -= considered;
|
|
connection->rcv_nxt += considered;
|
|
|
|
emit_segment(connection, true, false, SIZE_MAX);
|
|
|
|
// Data is ready to be received by the parent application
|
|
if (connection->callback_ready_to_recv)
|
|
connection->callback_ready_to_recv(connection->callback_data);
|
|
}
|
|
}
|
|
|
|
static tcp_connection_t*
|
|
find_connection_associated_to_listener(tcp_listener_t *listener,
|
|
ip_address_t peer_ip, uint16_t peer_port)
|
|
{
|
|
tcp_connection_t *connection;
|
|
|
|
// Check in the accepted list
|
|
connection = listener->accepted_list;
|
|
while (connection) {
|
|
if (connection->peer_port == peer_port && connection->peer_ip == peer_ip)
|
|
break;
|
|
connection = connection->next;
|
|
}
|
|
if (connection) {
|
|
// accepted=true
|
|
} else {
|
|
// accepted=false
|
|
connection = listener->non_accepted_queue_head;
|
|
while (connection) {
|
|
if (connection->peer_port == peer_port && connection->peer_ip == peer_ip)
|
|
break;
|
|
connection = connection->next;
|
|
}
|
|
}
|
|
if (connection) {
|
|
// established=true
|
|
} else {
|
|
// established=false
|
|
connection = listener->non_established_list;
|
|
while (connection) {
|
|
if (connection->peer_port == peer_port && connection->peer_ip == peer_ip)
|
|
break;
|
|
connection = connection->next;
|
|
}
|
|
}
|
|
return connection;
|
|
}
|
|
|
|
static void
|
|
move_from_non_established_list_to_non_accepted_queue(tcp_connection_t *connection)
|
|
{
|
|
tcp_listener_t *listener = connection->listener;
|
|
|
|
// Unlink the structure from the non established list
|
|
{
|
|
if (connection->prev)
|
|
connection->prev->next = connection->next;
|
|
else
|
|
listener->non_established_list = connection->next;
|
|
|
|
if (connection->next)
|
|
connection->next->prev = connection->prev;
|
|
}
|
|
|
|
// Push it into the non accepted queue
|
|
connection->prev = NULL;
|
|
connection->next = listener->non_accepted_queue_head;
|
|
if (listener->non_accepted_queue_head)
|
|
listener->non_accepted_queue_head->prev = connection;
|
|
else
|
|
listener->non_accepted_queue_tail = connection;
|
|
listener->non_accepted_queue_head = connection;
|
|
}
|
|
|
|
void tcp_process_segment(tcp_state_t *state, ip_address_t sender,
|
|
tcp_segment_t *segment, size_t len)
|
|
{
|
|
TCP_DEBUG_LOG("Received TCP segment");
|
|
|
|
assert(len >= sizeof(tcp_segment_t));
|
|
size_t data_offset = SEGMENT_OFFSET(segment) * sizeof(uint32_t); // Length (in bytes) of the TCP header,
|
|
// comprehensive of options.
|
|
|
|
size_t options_len = data_offset - sizeof(tcp_segment_t); // The number of bytes of the options is
|
|
// the size of the whole header minus the
|
|
// size of the header without options.
|
|
size_t payload_size = len - data_offset;
|
|
void *payload_addr = (uint8_t*) segment + data_offset; // The segment->payload doesn't refer to the
|
|
// first byte of the payload but to the first
|
|
// byte of the options!! Use this variable to
|
|
// get the payload.
|
|
|
|
uint16_t reordered_dst_port = net_to_cpu_u16(segment->dst_port);
|
|
uint16_t reordered_src_port = net_to_cpu_u16(segment->src_port);
|
|
|
|
tcp_listener_t *listener = find_listener_with_port(state, reordered_dst_port);
|
|
if (listener == NULL) {
|
|
// No connection is listening on this port. Silently drop the segment
|
|
TCP_DEBUG_LOG("Segment sent to port %d, which is closed", reordered_dst_port);
|
|
return;
|
|
}
|
|
|
|
tcp_connection_t *connection = find_connection_associated_to_listener(listener, sender, reordered_src_port);
|
|
if (!connection) {
|
|
// Something sent to an open listener.
|
|
|
|
// We expect it to be a request to connect,
|
|
// which means that the segment should have
|
|
// the SYN flag high (and only that one).
|
|
// If that's true, a connection object must
|
|
// be instanciated and a SYN|ACK message sent.
|
|
//
|
|
// Alongside the SYN, some payload may be
|
|
// associated with the message. Though we must
|
|
// make sure that this data isn't delivered to
|
|
// the parent application until the connection
|
|
// is fully established.
|
|
//
|
|
// From RFC 793, section 3.4:
|
|
//
|
|
// > Several examples of connection initiation follow. Although these
|
|
// > examples do not show connection synchronization using data-carrying
|
|
// > segments, this is perfectly legitimate, so long as the receiving TCP
|
|
// > doesn't deliver the data to the user until it is clear the data is
|
|
// > valid (i.e., the data must be buffered at the receiver until the
|
|
// > connection reaches the ESTABLISHED state). The three-way handshake
|
|
// > reduces the possibility of false connections.
|
|
//
|
|
// (https://www.ietf.org/rfc/rfc793.txt)
|
|
|
|
if (!(segment->flags & TCP_FLAG_SYN)) {
|
|
// Received message isn't SYN. Ignore the segment.
|
|
TCP_DEBUG_LOG("Connection request is missing the SYN flag");
|
|
return;
|
|
}
|
|
|
|
if (segment->flags & ~TCP_FLAG_SYN)
|
|
TCP_DEBUG_LOG("Connection request segment has flags other than SYN set");
|
|
|
|
if (payload_size > 0) {
|
|
TCP_DEBUG_LOG("Connection request segment has some payload. "
|
|
"This is a valid behaviour but we don't handle "
|
|
"that case yet. Droppong the connection");
|
|
return;
|
|
}
|
|
|
|
uint32_t seq_no = choose_sequence_no();
|
|
uint32_t ack_no = net_to_cpu_u32(segment->seq_no)+1;
|
|
|
|
connection = connection_create(listener, seq_no, ack_no, sender, reordered_src_port);
|
|
if (connection == NULL) {
|
|
TCP_DEBUG_LOG("Connection limit reached");
|
|
// Should we let the peer know what happened?
|
|
return;
|
|
}
|
|
connection->state = TCP_STATE_SYN_RCVD;
|
|
|
|
emit_segment(connection, true, true, 0);
|
|
connection->snd_una++;
|
|
|
|
TCP_DEBUG_LOG("Connection request handled");
|
|
|
|
} else {
|
|
// Something sent to an already instanciated
|
|
// connection. Since there is an instance, it
|
|
// means that at least the first SYN was received
|
|
// and a SYN|ACK message was sent, so the
|
|
// first state of a connection is SYN_RCVD
|
|
switch (connection->state) {
|
|
|
|
case TCP_STATE_CLOSED:
|
|
// This state is only used for uninitialized
|
|
// connection structures. If the code behaves
|
|
// well, this code should be unreachable.
|
|
assert(0);
|
|
break;
|
|
|
|
case TCP_STATE_SYN_SENT:
|
|
// This is the state where we sent SYN to initiate
|
|
// the connection with a peer acting as server.
|
|
// At the moment "microtcp_connect" isn't implemented
|
|
// so this state can never be reached.
|
|
assert(0); // UNREACHABLE
|
|
break;
|
|
|
|
case TCP_STATE_SYN_RCVD:
|
|
|
|
// At this point a SYN was received and a SYN|ACK sent.
|
|
// We expect an ACK to establish the connection.
|
|
|
|
if (!(segment->flags & TCP_FLAG_ACK))
|
|
// This isn't what we expected. Ignore it (this is
|
|
// probably not the best action)
|
|
return;
|
|
|
|
if (segment->flags & ~TCP_FLAG_ACK)
|
|
TCP_DEBUG_LOG("Incoming segment has flags other than ACK set when just an ACK was expected");
|
|
if (payload_size > 0)
|
|
TCP_DEBUG_LOG("Incoming segment has a payload alongside the ACK for "
|
|
"the SYN we sent. This is valid TCP but we don't support "
|
|
"this case yet. We'll just ignore the data. Hopefully "
|
|
"the peer will retransmit it");
|
|
// The connection is now established.
|
|
connection->state = TCP_STATE_ESTAB;
|
|
|
|
move_from_non_established_list_to_non_accepted_queue(connection);
|
|
|
|
if (listener->callback_ready_to_accept)
|
|
listener->callback_ready_to_accept(listener->callback_data);
|
|
break;
|
|
|
|
case TCP_STATE_ESTAB:
|
|
if (segment->flags & TCP_FLAG_ACK) {
|
|
|
|
uint32_t ack_no = net_to_cpu_u32(segment->ack_no);
|
|
|
|
if (ack_no <= connection->snd_una)
|
|
TCP_DEBUG_LOG("Received segment acknowledged again %d", ack_no);
|
|
else {
|
|
if (ack_no > connection->snd_nxt) {
|
|
// Peer ACKed unsent data. The right course of action
|
|
// is probably to drop the connection.
|
|
TCP_DEBUG_LOG("Received segment acknowledged unsent data "
|
|
"with sequence number %d, but %d still wasn't sent",
|
|
ack_no, connection->snd_nxt);
|
|
return; // For now we'll just ignore the segment.
|
|
}
|
|
|
|
size_t newly_acked_bytes = ack_no - connection->snd_una;
|
|
memmove(connection->out_buffer, connection->out_buffer + newly_acked_bytes, connection->snd_wnd - newly_acked_bytes);
|
|
connection->snd_wnd -= newly_acked_bytes;
|
|
connection->snd_una = ack_no;
|
|
|
|
// Now there's space available in the output buffer
|
|
if (connection->callback_ready_to_send)
|
|
connection->callback_ready_to_send(connection->callback_data);
|
|
}
|
|
}
|
|
|
|
handle_received_data(connection, payload_addr, payload_size);
|
|
|
|
if (segment->flags & TCP_FLAG_FIN) {
|
|
|
|
// Send ACK for the FIN
|
|
emit_segment(connection, true, false, 0);
|
|
connection->snd_una++; // emit_segment doesn't increment the "snd_una" for ghost bytes
|
|
|
|
connection->state = TCP_STATE_CLOSE_WAIT;
|
|
}
|
|
break;
|
|
|
|
case TCP_STATE_FIN_WAIT_1:break;
|
|
case TCP_STATE_FIN_WAIT_2:break;
|
|
case TCP_STATE_CLOSE_WAIT:break;
|
|
case TCP_STATE_LAST_ACK:break;
|
|
case TCP_STATE_TIME_WAIT:break;
|
|
}
|
|
}
|
|
}
|
|
|
|
tcp_listener_t*
|
|
tcp_listener_create(tcp_state_t *state, uint16_t port, void *callback_data,
|
|
void (*callback_ready_to_accept)(void*))
|
|
{
|
|
if (find_listener_with_port(state, port)) {
|
|
// ERROR: A connection is already listening on this port
|
|
TCP_DEBUG_LOG("Faile to create listener on port %d because there already exists one", port);
|
|
return NULL;
|
|
}
|
|
|
|
// Pop a listener connection structure from the free list
|
|
if (state->free_listener_list == NULL) {
|
|
// ERROR: Reached listener connection limit
|
|
TCP_DEBUG_LOG("TCP connection limit");
|
|
return NULL;
|
|
}
|
|
tcp_listener_t *listener = state->free_listener_list;
|
|
state->free_listener_list = listener->next;
|
|
|
|
// Initialize listener structure
|
|
listener->state = state;
|
|
listener->port = port;
|
|
listener->accepted_list = NULL;
|
|
listener->non_established_list = NULL;
|
|
listener->non_accepted_queue_head = NULL;
|
|
listener->non_accepted_queue_tail = NULL;
|
|
listener->callback_data = callback_data;
|
|
listener->callback_ready_to_accept = callback_ready_to_accept;
|
|
|
|
// Push listener connection structure to the used list
|
|
listener->prev = NULL;
|
|
listener->next = state->used_listener_list;
|
|
if (state->used_listener_list)
|
|
state->used_listener_list->prev = listener;
|
|
state->used_listener_list = listener;
|
|
|
|
return listener;
|
|
}
|
|
|
|
void tcp_listener_destroy(tcp_listener_t *listener)
|
|
{
|
|
// TODO: Close all connections
|
|
|
|
tcp_state_t *state = listener->state;
|
|
|
|
// Pop listener from used list
|
|
{
|
|
// Update the reference to the listener of
|
|
// the one that precedes it in the list
|
|
if (listener->prev)
|
|
listener->prev->next = listener->next;
|
|
else
|
|
state->used_listener_list = listener->next;
|
|
|
|
// Update the reference to the listener of
|
|
// the one that follows it in the list
|
|
if (listener->next != NULL)
|
|
listener->next->prev = listener->prev;
|
|
}
|
|
|
|
// Push the listener in the free list
|
|
listener->next = state->free_listener_list;
|
|
state->free_listener_list = listener;
|
|
}
|
|
|
|
tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener, void *callback_data,
|
|
void (*callback_ready_to_recv)(void*),
|
|
void (*callback_ready_to_send)(void*))
|
|
{
|
|
(void) listener;
|
|
|
|
if (!listener->non_accepted_queue_head)
|
|
// Nothing to accept
|
|
return NULL;
|
|
|
|
tcp_connection_t *connection;
|
|
|
|
// Pop connection from the tail of the accept queue
|
|
{
|
|
connection = listener->non_accepted_queue_tail;
|
|
if (connection->prev)
|
|
// ->prev isn't NULL so this isn't the first element of
|
|
// the queue. Therefore we need to update the ->next pointer
|
|
// of the previous node.
|
|
connection->prev->next = NULL;
|
|
else
|
|
// ->prev is NULL so this is the first element of the queue.
|
|
// We need to update the reference to the head of the list
|
|
listener->non_accepted_queue_head = NULL;
|
|
|
|
assert(connection->next == NULL); // This is the last element of the queue so
|
|
// it has no following node.
|
|
listener->non_accepted_queue_tail = NULL;
|
|
}
|
|
|
|
// Push it to the head of the accepted list
|
|
{
|
|
connection->prev = NULL;
|
|
connection->next = listener->accepted_list;
|
|
|
|
if (listener->accepted_list)
|
|
listener->accepted_list->prev = connection;
|
|
listener->accepted_list = connection;
|
|
}
|
|
|
|
connection->callback_data = callback_data;
|
|
connection->callback_ready_to_recv = callback_ready_to_recv;
|
|
connection->callback_ready_to_send = callback_ready_to_send;
|
|
|
|
return connection;
|
|
}
|
|
|
|
#ifdef TCP_DEBUG
|
|
static bool connection_was_accepted(tcp_connection_t *connection)
|
|
{
|
|
// Get the listener that's associated to
|
|
// the connection and iterate over it's
|
|
// accepted connections list to make sure
|
|
// that is contains the connection.
|
|
|
|
tcp_listener_t *listener = connection->listener;
|
|
|
|
tcp_connection_t *cursor = listener->accepted_list;
|
|
|
|
while (cursor) {
|
|
if (cursor == connection)
|
|
return true;
|
|
cursor = cursor->next;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
void tcp_connection_destroy(tcp_connection_t *connection)
|
|
{
|
|
// NOTE: This can only be called when the
|
|
// connection was accepted, so it must
|
|
// be a node of the accepted_list.
|
|
|
|
#ifdef TCP_DEBUG
|
|
assert(connection_was_accepted(connection));
|
|
#endif
|
|
|
|
// Make sure the connection was first finished
|
|
// by being moved from the idle list to the
|
|
// waiting-for-fin list.
|
|
tcp_connection_finish(connection);
|
|
|
|
tcp_listener_t *listener = connection->listener;
|
|
|
|
// Pop connection from the waiting-for-fin list
|
|
if (connection->prev)
|
|
connection->prev->next = connection->next;
|
|
else
|
|
listener->accepted_list = connection->next;
|
|
|
|
// Push it into the free connection list
|
|
tcp_state_t *state = listener->state;
|
|
|
|
connection->prev = NULL;
|
|
connection->next = state->free_connection_list;
|
|
state->free_connection_list = connection;
|
|
}
|
|
|
|
size_t tcp_connection_recv(tcp_connection_t *connection,
|
|
void *dst, size_t len)
|
|
{
|
|
size_t unread = connection->rcv_nxt - connection->rcv_unread;
|
|
size_t num = MIN(len, unread);
|
|
memcpy(dst, connection->in_buffer, num);
|
|
|
|
|
|
size_t input_buffer_usage = TCP_INPUT_BUFFER_SIZE - connection->rcv_wnd;
|
|
|
|
memmove(connection->in_buffer, connection->in_buffer + num, input_buffer_usage - num);
|
|
connection->rcv_unread += num;
|
|
connection->rcv_wnd += num;
|
|
|
|
assert(connection->rcv_wnd <= TCP_INPUT_BUFFER_SIZE);
|
|
|
|
return num;
|
|
}
|
|
|
|
static size_t
|
|
append_to_output_buffer(tcp_connection_t *connection,
|
|
const void *src, size_t len)
|
|
{
|
|
size_t capacity = TCP_OUTPUT_BUFFER_SIZE - connection->snd_wnd;
|
|
size_t num = MIN(len, capacity);
|
|
|
|
memcpy(connection->out_buffer + connection->snd_wnd, src, num);
|
|
connection->snd_wnd += num;
|
|
|
|
return num;
|
|
}
|
|
|
|
size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len)
|
|
{
|
|
size_t num = append_to_output_buffer(connection, src, len);
|
|
emit_segment(connection, false, false, SIZE_MAX);
|
|
return num;
|
|
}
|
|
|
|
void tcp_connection_finish(tcp_connection_t *connection)
|
|
{
|
|
/*
|
|
if (!connection->read_only) {
|
|
|
|
// Move connection from idle list to
|
|
// waiting-for-fin list
|
|
|
|
tcp_listener_t *listener = connection->listener;
|
|
|
|
// Pop it from the idle list
|
|
{
|
|
if (connection->prev)
|
|
connection->prev->next = connection->next;
|
|
else
|
|
listener->connections = connection->next;
|
|
|
|
if (connection->next)
|
|
connection->next->prev = connection->prev;
|
|
|
|
connection->prev = NULL;
|
|
connection->next = NULL;
|
|
}
|
|
|
|
#warning "The FIN segment should be sent here"
|
|
|
|
// Push it to the waiting-for-fin list
|
|
{
|
|
connection->prev = NULL;
|
|
connection->next = listener->connections_waiting_for_fin;
|
|
listener->connections_waiting_for_fin = connection;
|
|
}
|
|
|
|
// Now mark the connection as read-only
|
|
connection->read_only = true;
|
|
}
|
|
*/
|
|
} |