You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1161 lines
50 KiB
1161 lines
50 KiB
// etcp.c - ETCP Protocol Implementation (refactored and expanded based on etcp_protocol.txt) |
|
#include "etcp.h" |
|
#include "etcp_loadbalancer.h" |
|
#include "../lib/u_async.h" |
|
#include "../lib/debug_config.h" |
|
#include "crc32.h" |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <sys/time.h> |
|
#include <math.h> // For bandwidth calcs |
|
|
|
// Enable comprehensive debug output for ETCP module |
|
#define DEBUG_CATEGORY_ETCP_DETAILED 1 |
|
|
|
// Constants from spec (adjusted for completeness) |
|
#define MAX_INFLIGHT_BYTES 65536 // Initial window |
|
#define RETRANS_K1 2.0f // RTT multiplier for retrans timeout |
|
#define RETRANS_K2 1.5f // Jitter multiplier |
|
#define ACK_DELAY_TB 20 // ACK timer delay (2ms in 0.1ms units) |
|
#define BURST_DELAY_FACTOR 4 // Delay before burst |
|
#define BURST_SIZE 5 // Packets in burst (1 delayed + 4 burst) |
|
#define RTT_HISTORY_SIZE 10 // For jitter calc |
|
#define MAX_PENDING 32 // For ACKs/retrans |
|
#define SECTION_HEADER_SIZE 3 // type(1) + len(2) |
|
|
|
// Forward declaration for callback function |
|
static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg); |
|
|
|
// Timestamp diff (with wrap-around) |
|
static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: t1=%u, t2=%u", t1, t2); |
|
if (t1 >= t2) { |
|
uint16_t diff = t1 - t2; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: normal case, diff=%u", diff); |
|
return diff; |
|
} |
|
uint16_t diff = (0xFFFF - t2) + t1 + 1; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "timestamp_diff: wraparound case, diff=%u", diff); |
|
return diff; |
|
} |
|
|
|
// Get current time in 0.1ms units |
|
uint64_t get_current_time_units() { |
|
struct timeval tv; |
|
gettimeofday(&tv, NULL); |
|
uint64_t time_units = ((uint64_t)tv.tv_sec * 10000ULL) + (tv.tv_usec / 100); |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_time_units: tv_sec=%ld, tv_usec=%ld, result=%llu", |
|
tv.tv_sec, tv.tv_usec, (unsigned long long)time_units); |
|
return time_units; |
|
} |
|
|
|
uint16_t get_current_timestamp() { |
|
uint16_t timestamp = (uint16_t)(get_current_time_units() & 0xFFFF); |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "get_current_timestamp: result=%u", timestamp); |
|
return timestamp; |
|
} |
|
|
|
// Internal functions (expanded) |
|
static void retrans_timer_cb(void* arg); |
|
static void ack_timer_cb(void* arg); |
|
static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt); |
|
static void calculate_jitter(struct ETCP_CONN* etcp); |
|
static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp); |
|
static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
|
static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
|
static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
|
static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
|
static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
|
static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len); |
|
static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link); |
|
static void deliver_contiguous_packets(struct ETCP_CONN* etcp); |
|
static void retrans_check(struct ETCP_CONN* etcp); |
|
static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state); |
|
static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt); |
|
static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt); |
|
static void free_inflight_packet(inflight_packet_t* pkt); |
|
static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link); |
|
static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw); |
|
|
|
// Add RTT history to struct (for jitter) |
|
/// In etcp.h, add to ETCP_CONN: |
|
/// uint16_t rtt_history[RTT_HISTORY_SIZE]; |
|
/// uint8_t rtt_history_idx; |
|
|
|
//======================================================================================================================================================================= |
|
// Create ETCP connection (unchanged) |
|
struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: instance=%p", instance); |
|
|
|
struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); |
|
if (!etcp) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: failed to allocate ETCP_CONN"); |
|
return NULL; |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: allocated etcp=%p", etcp); |
|
|
|
etcp->instance = instance; |
|
etcp->input_queue = queue_new(instance->ua, instance->pkt_pool); |
|
etcp->output_queue = queue_new(instance->ua, instance->pkt_pool); |
|
etcp->mtu = 1500; // Default |
|
etcp->window_size = MAX_INFLIGHT_BYTES; |
|
etcp->next_tx_id = 1; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, mtu=%d, window_size=%u, next_tx_id=%u", |
|
etcp->mtu, etcp->window_size, etcp->next_tx_id); |
|
|
|
// Init crypto (as per original) |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: initializing crypto context"); |
|
if (sc_init_ctx(&etcp->crypto_ctx, &instance->my_keys) != SC_OK) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: crypto initialization failed"); |
|
goto conn_create_err; |
|
} |
|
|
|
// создаём очереди |
|
|
|
if (!etcp->inflight_pool=memory_pool_init(sizeof(struct INFLIGHT_PACKET))) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: inflight pool alloc error"); goto conn_create_err; } |
|
if (!etcp->input_send_q=queue_new(instance->ua, etcp->inflight_pool)) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: send q alloc error"); goto conn_create_err; } |
|
if (!etcp->input_wait_ack=queue_new(instance->ua, etcp->inflight_pool)) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connection_create: wait q alloc error"); goto conn_create_err; } |
|
|
|
// Add connection to instance list |
|
etcp->next = instance->connections; |
|
instance->connections = etcp; |
|
instance->connections_count++; |
|
|
|
// для простоты предлагаю если что-то не так - etcp_connection_close и он закрывает все что не null |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_create: successfully created ETCP connection %p", etcp); |
|
return etcp; |
|
conn_create_err: |
|
etcp_connection_close(etcp); |
|
return NULL; |
|
} |
|
//======================================================================================================================================================================= |
|
// Close connection (expanded free) |
|
void etcp_connection_close(struct ETCP_CONN* etcp) { |
|
if (!etcp) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_connection_close: called with NULL etcp"); |
|
return; |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: closing connection %p", etcp); |
|
|
|
// Remove connection from instance list |
|
if (etcp->instance) { |
|
struct ETCP_CONN** prev = &etcp->instance->connections; |
|
struct ETCP_CONN* cur = *prev; |
|
while (cur) { |
|
if (cur == etcp) { |
|
*prev = cur->next; |
|
etcp->instance->connections_count--; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: removed from instance list, count=%d", |
|
etcp->instance->connections_count); |
|
break; |
|
} |
|
prev = &cur->next; |
|
cur = cur->next; |
|
} |
|
} |
|
|
|
// Cancel timers |
|
if (etcp->retrans_timer) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling retrans_timer"); |
|
uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); |
|
} |
|
if (etcp->ack_timer) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: canceling ack_timer"); |
|
uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); |
|
} |
|
|
|
// Free inflight |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_ack_list"); |
|
inflight_packet_t* pkt = etcp->wait_ack_list; |
|
int wait_ack_count = 0; |
|
while (pkt) { |
|
inflight_packet_t* next = pkt->next; |
|
free_inflight_packet(pkt); |
|
pkt = next; |
|
wait_ack_count++; |
|
} |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_ack_list", wait_ack_count); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing wait_send_list"); |
|
pkt = etcp->wait_send_list; |
|
int wait_send_count = 0; |
|
while (pkt) { |
|
inflight_packet_t* next = pkt->next; |
|
free_inflight_packet(pkt); |
|
pkt = next; |
|
wait_send_count++; |
|
} |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from wait_send_list", wait_send_count); |
|
|
|
// Free rx_list |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing rx_list"); |
|
rx_packet_t* rx = etcp->rx_list; |
|
int rx_count = 0; |
|
while (rx) { |
|
rx_packet_t* next = rx->next; |
|
free(rx->data); |
|
free(rx); |
|
rx = next; |
|
rx_count++; |
|
} |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freed %d packets from rx_list", rx_count); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_close: freeing queues"); |
|
queue_free(etcp->input_queue); |
|
queue_free(etcp->output_queue); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_connection_close: connection %p closed successfully", etcp); |
|
free(etcp); |
|
} |
|
|
|
//======================================================================================================================================================================= |
|
|
|
static void free_inflight_packet(inflight_packet_t* pkt) { |
|
if (pkt) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "free_inflight_packet: freeing packet %p (id=%u, data_len=%u)", |
|
pkt, pkt->id, pkt->data_len); |
|
free(pkt->data); |
|
free(pkt); |
|
} |
|
} |
|
|
|
//======================================================================================================================================================================= |
|
|
|
// Reset connection (unchanged) |
|
void etcp_conn_reset(struct ETCP_CONN* etcp) { |
|
if (!etcp) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: called with NULL etcp"); |
|
return; |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: resetting connection %p", etcp); |
|
|
|
// Cancel timers |
|
if (etcp->retrans_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->retrans_timer); |
|
if (etcp->ack_timer) uasync_cancel_timeout(etcp->instance->ua, etcp->ack_timer); |
|
|
|
// Free lists |
|
inflight_packet_t* pkt = etcp->wait_ack_list; |
|
while (pkt) { |
|
inflight_packet_t* next = pkt->next; |
|
free_inflight_packet(pkt); |
|
pkt = next; |
|
} |
|
etcp->wait_ack_list = NULL; |
|
pkt = etcp->wait_send_list; |
|
while (pkt) { |
|
inflight_packet_t* next = pkt->next; |
|
free_inflight_packet(pkt); |
|
pkt = next; |
|
} |
|
etcp->wait_send_list = NULL; |
|
|
|
rx_packet_t* rx = etcp->rx_list; |
|
while (rx) { |
|
rx_packet_t* next = rx->next; |
|
free(rx->data); |
|
free(rx); |
|
rx = next; |
|
} |
|
etcp->rx_list = NULL; |
|
|
|
// Reset state |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: clearing packet lists and resetting state"); |
|
etcp->next_tx_id = 1; |
|
etcp->last_rx_id = 0; |
|
etcp->last_delivered_id = 0; |
|
etcp->unacked_bytes = 0; |
|
etcp->pending_ack_count = 0; |
|
etcp->pending_retrans_count = 0; |
|
etcp->rtt_last = 0; |
|
etcp->rtt_avg_10 = 0; |
|
etcp->rtt_avg_100 = 0; |
|
etcp->jitter = 0; |
|
etcp->burst_in_progress = 0; |
|
etcp->rtt_history_idx = 0; |
|
memset(etcp->rtt_history, 0, sizeof(etcp->rtt_history)); |
|
// Queues cleared externally if needed |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "etcp_conn_reset: connection %p reset completed", etcp); |
|
} |
|
|
|
//======================================================================================================================================================================= |
|
// PACKET INPUT processing |
|
//======================================================================================================================================================================= |
|
|
|
|
|
// Input decrypted packet (unchanged structure, but calls expanded processes) |
|
void etcp_conn_input(struct ETCP_DGRAM* pkt) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: pkt=%p, link=%p, etcp=%p", |
|
pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); |
|
|
|
if (!pkt || !pkt->link || !pkt->link->etcp) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_conn_input: invalid parameters (pkt=%p, link=%p, etcp=%p)", |
|
pkt, pkt ? pkt->link : NULL, pkt && pkt->link ? pkt->link->etcp : NULL); |
|
return; |
|
} |
|
|
|
struct ETCP_CONN* etcp = pkt->link->etcp; |
|
uint8_t* data = pkt->data; |
|
uint16_t len = pkt->data_len; |
|
uint16_t ts = pkt->timestamp; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_conn_input: processing packet - etcp=%p, data_len=%u, timestamp=%u, link=%p", |
|
etcp, len, ts, pkt->link); |
|
|
|
// Update link activity |
|
pkt->link->last_activity = get_current_time_units(); |
|
pkt->link->last_recv_local_time = pkt->link->last_activity; |
|
pkt->link->last_recv_timestamp = ts; |
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_conn_input: updated link activity - last_activity=%llu, last_recv_timestamp=%u", |
|
(unsigned long long)pkt->link->last_activity, pkt->link->last_recv_timestamp); |
|
|
|
// Parse sections |
|
uint16_t pos = 0; |
|
while (pos < len) { |
|
if (pos + SECTION_HEADER_SIZE > len) break; |
|
|
|
uint8_t type = data[pos]; |
|
uint16_t sec_len = (data[pos+1] << 8) | data[pos+2]; |
|
pos += SECTION_HEADER_SIZE; |
|
|
|
if (pos + sec_len > len) break; |
|
|
|
uint8_t* sec_data = data + pos; |
|
switch (type) { |
|
case ETCP_SECTION_ACK: |
|
process_section_ack(etcp, sec_data, sec_len); |
|
break; |
|
case ETCP_SECTION_RETRANS: |
|
process_section_retrans(etcp, sec_data, sec_len); |
|
break; |
|
case ETCP_SECTION_TIMESTAMP: |
|
process_section_timestamp(etcp, sec_data, sec_len); |
|
break; |
|
case ETCP_SECTION_PAYLOAD: |
|
process_section_payload(etcp, sec_data, sec_len); |
|
break; |
|
case ETCP_SECTION_MEAS_TS: |
|
process_section_meas_ts(etcp, sec_data, sec_len); |
|
break; |
|
case ETCP_SECTION_MEAS_RESP: |
|
process_section_meas_resp(etcp, sec_data, sec_len); |
|
break; |
|
default: |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Unknown section type: 0x%02x", type); |
|
break; |
|
} |
|
pos += sec_len; |
|
} |
|
|
|
// Post-processing |
|
detect_gaps_and_request_retrans(etcp); |
|
deliver_contiguous_packets(etcp); |
|
|
|
// Trigger if waiting |
|
if (etcp->wait_timeout_active) { |
|
etcp->wait_timeout_active = 0; |
|
// Assume load balancer polls again |
|
} |
|
} |
|
|
|
// Request next packet (expanded) |
|
// source 1: wait_ack_list |
|
// source 2: queue > wait_ack_list |
|
|
|
// Эта основная функция для формирования кодограмм на передачу из входящей очереди в UDP |
|
// она вызывается когда какой-либо из каналов освободился, либо появились данные во входящей очереди, либо сработал таймер ретрансмиссии |
|
// должна формировать пакеты до тех пор пока их может принимать loadbalancer |
|
struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: etcp=%p", etcp); |
|
|
|
if (!etcp) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: called with NULL etcp"); |
|
return NULL; |
|
} |
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: input_queue_count=%d, wait_send_list=%p, unacked_bytes=%u, window_size=%u", |
|
queue_entry_count(etcp->input_queue), etcp->wait_send_list, |
|
etcp->unacked_bytes, etcp->window_size); |
|
|
|
etcp_get_wait_list(); |
|
// Check waiting conditions |
|
if (queue_entry_count(etcp->input_queue) == 0 && etcp->wait_send_list == NULL) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no packets available, setting callback and returning NULL"); |
|
// Wait for input or retrans |
|
// Set callback for resume |
|
queue_set_callback(etcp->input_queue, input_queue_cb, etcp); // Define cb to resume |
|
return NULL; |
|
} |
|
|
|
if (etcp->unacked_bytes >= etcp->window_size) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: window full (unacked_bytes=%u >= window_size=%u), waiting", |
|
etcp->unacked_bytes, etcp->window_size); |
|
// Wait ACK or timeout |
|
etcp->wait_timeout_active = 1; |
|
return NULL; |
|
} |
|
|
|
if (etcp->unacked_bytes >= etcp->window_size) { |
|
// Wait ACK or timeout |
|
etcp->wait_timeout_active = 1; |
|
return NULL; |
|
} |
|
|
|
// Get packet (prefer retrans) |
|
inflight_packet_t* ipkt = etcp->wait_send_list; |
|
if (ipkt) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: using retransmission packet id=%u", ipkt->id); |
|
remove_inflight(etcp, ipkt); |
|
} else { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: getting new packet from input queue"); |
|
struct ll_entry* entry = queue_entry_get(etcp->input_queue); |
|
if (!entry) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no entry available from input queue"); |
|
return NULL; |
|
} |
|
|
|
ipkt = calloc(1, sizeof(inflight_packet_t)); |
|
if (!ipkt) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate inflight_packet_t"); |
|
queue_entry_put_first(etcp->input_queue, entry); |
|
return NULL; |
|
} |
|
|
|
ipkt->id = etcp->next_tx_id++; |
|
ipkt->data_len = ll_entry_size(entry); |
|
ipkt->data = malloc(ipkt->data_len); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: created new packet id=%u, data_len=%u", |
|
ipkt->id, ipkt->data_len); |
|
|
|
if (!ipkt->data) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate packet data buffer"); |
|
free(ipkt); |
|
queue_entry_put_first(etcp->input_queue, entry); |
|
return NULL; |
|
} |
|
memcpy(ipkt->data, ll_entry_data(entry), ipkt->data_len); |
|
ipkt->payload_len = ipkt->data_len; // Assume full is payload for window |
|
queue_entry_free(entry); |
|
} |
|
|
|
// Select link |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selecting link via loadbalancer"); |
|
struct ETCP_LINK* link = etcp_loadbalancer_select_link(etcp); |
|
if (!link) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: no suitable link available"); |
|
// Put back if new |
|
if (ipkt->send_count == 0) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: putting new packet back to input queue"); |
|
struct ll_entry* entry = queue_entry_new(ipkt->data_len); |
|
memcpy(ll_entry_data(entry), ipkt->data, ipkt->data_len); |
|
queue_entry_put_first(etcp->input_queue, entry); |
|
} |
|
free_inflight_packet(ipkt); |
|
return NULL; |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: selected link %p", link); |
|
|
|
// Build packet buf |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: building packet with optional sections"); |
|
uint8_t buf[1600]; |
|
uint16_t len = append_optional_sections(etcp, buf, link); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: optional sections added, len=%u", len); |
|
|
|
// Add payload (assume payload includes ID as first 2 bytes) |
|
uint16_t pay_len = ipkt->payload_len + 2; // ID |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload section - pay_len=%u (payload_len=%u + 2 for ID)", |
|
pay_len, ipkt->payload_len); |
|
|
|
if (len + SECTION_HEADER_SIZE + pay_len > sizeof(buf)) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: packet too large (%u + %u + %u > %u), dropping", |
|
len, SECTION_HEADER_SIZE, pay_len, (unsigned)sizeof(buf)); |
|
free_inflight_packet(ipkt); |
|
return NULL; |
|
} |
|
buf[len++] = ETCP_SECTION_PAYLOAD; |
|
buf[len++] = (pay_len >> 8) & 0xFF; |
|
buf[len++] = pay_len & 0xFF; |
|
buf[len++] = (ipkt->id >> 8) & 0xFF; |
|
buf[len++] = ipkt->id & 0xFF; |
|
memcpy(buf + len, ipkt->data, ipkt->payload_len); |
|
len += ipkt->payload_len; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: payload added, total len=%u", len); |
|
|
|
// Alloc dgram |
|
struct ETCP_DGRAM* dgram = memory_pool_alloc(etcp->instance->pkt_pool); |
|
if (!dgram) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: failed to allocate dgram from memory pool"); |
|
free_inflight_packet(ipkt); |
|
return NULL; |
|
} |
|
dgram->link = link; |
|
dgram->data_len = len; |
|
dgram->noencrypt_len = 0; |
|
dgram->timestamp = get_current_timestamp(); |
|
memcpy(dgram->data, buf, len); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: allocated dgram %p (link=%p, data_len=%u, timestamp=%u)", |
|
dgram, link, len, dgram->timestamp); |
|
|
|
// Update inflight |
|
ipkt->last_link = link; |
|
ipkt->last_timestamp = dgram->timestamp; |
|
ipkt->send_count++; |
|
etcp->unacked_bytes += ipkt->payload_len; |
|
etcp->bytes_sent_total += len; // Approx |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: updated inflight - send_count=%u, unacked_bytes=%u, bytes_sent_total=%u", |
|
ipkt->send_count, etcp->unacked_bytes, etcp->bytes_sent_total); |
|
|
|
// Move to wait_ack |
|
ipkt->state = INFLIGHT_STATE_WAIT_ACK; |
|
ipkt->next = etcp->wait_ack_list; |
|
etcp->wait_ack_list = ipkt; |
|
|
|
// Add to wait_ack list |
|
ipkt->next = etcp->wait_ack_list; |
|
etcp->wait_ack_list = ipkt; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: added packet %u to wait_ack_list", ipkt->id); |
|
|
|
// Start retrans timer if empty before |
|
if (etcp->wait_ack_list->next == NULL && !etcp->retrans_timer) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: starting retrans_timer (wait_ack_list was empty)"); |
|
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); |
|
} |
|
|
|
// Update load balancer after send (call from connections after actual send) |
|
// etcp_loadbalancer_update_after_send(link, len + headers); // Assume called externally |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_request_pkt: successfully created dgram %p for packet id=%u", |
|
dgram, ipkt->id); |
|
return dgram; |
|
} |
|
|
|
// Input queue callback (placeholder to resume load balancer) |
|
static void input_queue_cb(struct ll_queue* q, struct ll_entry* entry, void* arg) { |
|
(void)q; |
|
(void)entry; |
|
struct ETCP_CONN* etcp = arg; |
|
// Resume polling etcp_request_pkt |
|
queue_resume_callback(q); |
|
} |
|
|
|
// Append optional (expanded with debug) |
|
static uint16_t append_optional_sections(struct ETCP_CONN* etcp, uint8_t* buf, struct ETCP_LINK* link) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: etcp=%p, link=%p", etcp, link); |
|
|
|
uint16_t len = 0; |
|
int sections_added = 0; |
|
|
|
// Timestamp (assume 2 bytes TS) |
|
uint16_t ts = get_current_timestamp(); |
|
buf[len++] = ETCP_SECTION_TIMESTAMP; |
|
buf[len++] = 0; // len high |
|
buf[len++] = 2; // len low |
|
buf[len++] = (ts >> 8) & 0xFF; |
|
buf[len++] = ts & 0xFF; |
|
sections_added++; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: added TIMESTAMP section (ts=%u, len=%u)", ts, len); |
|
|
|
// ACK (assume count(1) + [id(2)+ts(2)]*count) |
|
if (etcp->pending_ack_count > 0) { |
|
uint16_t ack_len = 1 + etcp->pending_ack_count * 4; |
|
buf[len++] = ETCP_SECTION_ACK; |
|
buf[len++] = (ack_len >> 8) & 0xFF; |
|
buf[len++] = ack_len & 0xFF; |
|
buf[len++] = etcp->pending_ack_count; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding ACK section with %u ACKs", etcp->pending_ack_count); |
|
|
|
for (uint8_t i = 0; i < etcp->pending_ack_count; i++) { |
|
uint16_t id = etcp->pending_ack_ids[i]; |
|
uint16_t ats = etcp->pending_ack_ts[i]; |
|
buf[len++] = (id >> 8) & 0xFF; |
|
buf[len++] = id & 0xFF; |
|
buf[len++] = (ats >> 8) & 0xFF; |
|
buf[len++] = ats & 0xFF; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: ACK[%u] - id=%u, ts=%u", i, id, ats); |
|
} |
|
|
|
etcp->pending_ack_count = 0; |
|
etcp->ack_packets_count++; |
|
sections_added++; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added ACK section (total_len=%u)", len); |
|
} |
|
|
|
// RETRANS (similar: count(1) + id(2)*count) |
|
if (etcp->pending_retrans_count > 0) { |
|
uint16_t ret_len = 1 + etcp->pending_retrans_count * 2; |
|
buf[len++] = ETCP_SECTION_RETRANS; |
|
buf[len++] = (ret_len >> 8) & 0xFF; |
|
buf[len++] = ret_len & 0xFF; |
|
buf[len++] = etcp->pending_retrans_count; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: adding RETRANS section with %u requests", etcp->pending_retrans_count); |
|
|
|
for (uint8_t i = 0; i < etcp->pending_retrans_count; i++) { |
|
uint16_t id = etcp->pending_retrans_ids[i]; |
|
buf[len++] = (id >> 8) & 0xFF; |
|
buf[len++] = id & 0xFF; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "append_optional_sections: RETRANS[%u] - id=%u", i, id); |
|
} |
|
|
|
etcp->pending_retrans_count = 0; |
|
sections_added++; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: added RETRANS section (total_len=%u)", len); |
|
} |
|
|
|
// MEAS_TS if burst (assume [start_id(2)][n(1)]) |
|
if (etcp->burst_in_progress) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: burst measurement in progress, would add MEAS_TS"); |
|
// Logic for adding to burst packets |
|
// Placeholder |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "append_optional_sections: completed - added %u sections, total_len=%u", |
|
sections_added, len); |
|
|
|
return len; |
|
} |
|
|
|
// Process ACK (expanded) |
|
static void process_section_ack(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: etcp=%p, data=%p, len=%u", etcp, data, len); |
|
|
|
if (len < 1) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid length %u", len); |
|
return; |
|
} |
|
uint8_t count = data[0]; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: count=%u", count); |
|
|
|
if (1 + count * 4 > len) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_ack: invalid count %u for len %u", count, len); |
|
return; |
|
} |
|
|
|
uint16_t lp_ts = 0; |
|
int acked_count = 0; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: processing %u ACK entries", count); |
|
|
|
for (uint8_t i = 0; i < count; i++) { |
|
uint16_t id = (data[1 + i*4] << 8) | data[2 + i*4]; |
|
uint16_t ts = (data[3 + i*4] << 8) | data[4 + i*4]; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: entry %u - id=%u, ts=%u", i, id, ts); |
|
|
|
inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); |
|
if (pkt) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: found inflight packet %u, removing (payload_len=%u)", |
|
id, pkt->payload_len); |
|
etcp->unacked_bytes -= pkt->payload_len; |
|
remove_inflight(etcp, pkt); |
|
free_inflight_packet(pkt); |
|
lp_ts = ts; // Update last positive ts |
|
acked_count++; |
|
} else { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_ack: no inflight packet found for id=%u", id); |
|
} |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_ack: successfully ACKed %u/%u packets", acked_count, count); |
|
// Use lp_ts in retrans_check if needed |
|
etcp->last_rx_ack_id = (data[1 + (count-1)*4] << 8) | data[2 + (count-1)*4]; // Last ACK ID |
|
} |
|
|
|
// Process RETRANS (expanded) |
|
static void process_section_retrans(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: etcp=%p, data=%p, len=%u", etcp, data, len); |
|
|
|
if (len < 1) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid length %u", len); |
|
return; |
|
} |
|
uint8_t count = data[0]; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: count=%u", count); |
|
|
|
if (1 + count * 2 > len) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_retrans: invalid count %u for len %u", count, len); |
|
return; |
|
} |
|
|
|
int retrans_count = 0; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: processing %u retrans requests", count); |
|
|
|
for (uint8_t i = 0; i < count; i++) { |
|
uint16_t id = (data[1 + i*2] << 8) | data[2 + i*2]; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: requesting retrans for id=%u", id); |
|
|
|
inflight_packet_t* pkt = find_inflight(etcp, id, INFLIGHT_STATE_WAIT_ACK); |
|
if (pkt) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: found packet %u, marking for retransmission", id); |
|
pkt->retrans_req_count++; |
|
pkt->need_retrans = 1; |
|
move_to_wait_send(etcp, pkt); |
|
retrans_count++; |
|
} else { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_retrans: no inflight packet found for id=%u", id); |
|
} |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_retrans: marked %u/%u packets for retransmission", retrans_count, count); |
|
} |
|
|
|
// Process TIMESTAMP (expanded) |
|
static void process_section_timestamp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: etcp=%p, data=%p, len=%u", etcp, data, len); |
|
|
|
if (len != 2) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_timestamp: invalid length %u (expected 2)", len); |
|
return; |
|
} |
|
uint16_t recv_ts = (data[0] << 8) | data[1]; |
|
uint16_t current_ts = get_current_timestamp(); |
|
uint16_t rtt = timestamp_diff(current_ts, recv_ts); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_timestamp: recv_ts=%u, current_ts=%u, calculated_rtt=%u", |
|
recv_ts, current_ts, rtt); |
|
update_rtt(etcp, rtt); |
|
} |
|
|
|
// Process PAYLOAD (expanded) |
|
static void process_section_payload(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: etcp=%p, data=%p, len=%u", etcp, data, len); |
|
|
|
if (len < 2) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: invalid length %u (minimum 2)", len); |
|
return; |
|
} |
|
uint16_t id = (data[0] << 8) | data[1]; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u, last_rx_id=%u", id, etcp->last_rx_id); |
|
|
|
if (id <= etcp->last_rx_id) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u (last_rx_id=%u), ignoring", |
|
id, etcp->last_rx_id); |
|
return; // Dup |
|
} |
|
|
|
rx_packet_t* new_rx = calloc(1, sizeof(rx_packet_t)); |
|
if (!new_rx) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx_packet_t"); |
|
return; |
|
} |
|
|
|
new_rx->id = id; |
|
new_rx->timestamp = get_current_timestamp(); |
|
new_rx->data_len = len - 2; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: allocated rx_packet %p (id=%u, data_len=%u, timestamp=%u)", |
|
new_rx, new_rx->id, new_rx->data_len, new_rx->timestamp); |
|
|
|
new_rx->data = malloc(new_rx->data_len); |
|
if (!new_rx->data) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "process_section_payload: failed to allocate rx data buffer (size=%u)", new_rx->data_len); |
|
free(new_rx); |
|
return; |
|
} |
|
memcpy(new_rx->data, data + 2, new_rx->data_len); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: copied %u bytes of payload data", new_rx->data_len); |
|
|
|
// Insert sorted (assume IDs sequential, no wrap for simplicity) |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: inserting packet into rx_list (id=%u)", id); |
|
rx_packet_t** ptr = &etcp->rx_list; |
|
while (*ptr && (*ptr)->id < id) ptr = &(*ptr)->next; |
|
if (*ptr && (*ptr)->id == id) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: duplicate packet id=%u found in rx_list, discarding", id); |
|
free(new_rx->data); |
|
free(new_rx); |
|
return; |
|
} |
|
new_rx->next = *ptr; |
|
*ptr = new_rx; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: successfully inserted packet id=%u into rx_list", id); |
|
|
|
if (id > etcp->last_rx_id) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: updating last_rx_id from %u to %u", |
|
etcp->last_rx_id, id); |
|
etcp->last_rx_id = id; |
|
} |
|
|
|
// Pending ACK |
|
if (etcp->pending_ack_count < MAX_PENDING) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "process_section_payload: adding packet id=%u to pending ACKs (count=%u)", |
|
id, etcp->pending_ack_count + 1); |
|
etcp->pending_ack_ids[etcp->pending_ack_count] = id; |
|
etcp->pending_ack_ts[etcp->pending_ack_count++] = new_rx->timestamp; |
|
} else { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_payload: pending ACK buffer full (%u), cannot add id=%u", |
|
MAX_PENDING, id); |
|
} |
|
|
|
// Schedule ACK |
|
if (!etcp->ack_timer) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: scheduling ACK timer (delay=%u tb)", ACK_DELAY_TB); |
|
etcp->ack_timer = uasync_set_timeout(etcp->instance->ua, ACK_DELAY_TB, etcp, ack_timer_cb); |
|
} |
|
|
|
etcp->bytes_received_total += len; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_payload: packet id=%u processed successfully, bytes_received_total=%u", |
|
id, etcp->bytes_received_total); |
|
} |
|
|
|
// Process MEAS_TS (expanded: send resp) |
|
static void process_section_meas_ts(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: etcp=%p, data=%p, len=%u", etcp, data, len); |
|
|
|
if (len < 3) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: invalid length %u (minimum 3)", len); |
|
return; |
|
} |
|
uint16_t start_id = (data[0] << 8) | data[1]; |
|
uint8_t n = data[2]; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_ts: start_id=%u, n=%u", start_id, n); |
|
|
|
// Send MEAS_RESP (add to pending or direct) |
|
// Placeholder: assume added to next packet |
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "MEAS_TS: start_id=%u n=%u", start_id, n); |
|
// Build resp [start_id(2)][n(1)][recv_ts(2)] |
|
// But since appended in optional, set state |
|
} |
|
|
|
// Process MEAS_RESP (expanded: calc bw) |
|
static void process_section_meas_resp(struct ETCP_CONN* etcp, uint8_t* data, uint16_t len) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: etcp=%p, data=%p, len=%u", etcp, data, len); |
|
|
|
// Assume format [start_id(2)][n(1)][recv_ts(2)] |
|
if (len < 5) { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: invalid length %u (minimum 5)", len); |
|
return; |
|
} |
|
uint16_t start_id = (data[0] << 8) | data[1]; |
|
uint8_t n = data[2]; |
|
uint16_t recv_ts = (data[3] << 8) | data[4]; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "process_section_meas_resp: start_id=%u, n=%u, recv_ts=%u", start_id, n, recv_ts); |
|
|
|
if (etcp->burst_start_id != start_id || !etcp->burst_in_progress) return; |
|
|
|
// Calc bw based on burst (placeholder logic) |
|
uint16_t delta = timestamp_diff(get_current_timestamp(), recv_ts); |
|
uint32_t bw = (BURST_SIZE * etcp->mtu * 8) / (delta / 1000.0); // bits/sec approx |
|
update_link_bandwidth(etcp->links, bw); // Assume primary link |
|
etcp->burst_in_progress = 0; |
|
} |
|
|
|
// Detect gaps (expanded) |
|
static void detect_gaps_and_request_retrans(struct ETCP_CONN* etcp) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: etcp=%p, last_delivered_id=%u", |
|
etcp, etcp->last_delivered_id); |
|
|
|
rx_packet_t* rx = etcp->rx_list; |
|
uint16_t expected = etcp->last_delivered_id + 1; |
|
int gaps_found = 0; |
|
int retrans_requested = 0; |
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: starting gap detection from expected_id=%u", expected); |
|
|
|
while (rx) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: checking rx packet id=%u (expected=%u)", |
|
rx->id, expected); |
|
|
|
if (rx->id > expected) { |
|
// Gap: add retrans for expected to rx->id -1 |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: gap detected - expected=%u, found=%u", |
|
expected, rx->id); |
|
gaps_found++; |
|
|
|
for (uint16_t gid = expected; gid < rx->id && etcp->pending_retrans_count < MAX_PENDING; gid++) { |
|
etcp->pending_retrans_ids[etcp->pending_retrans_count++] = gid; |
|
retrans_requested++; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: queued retrans request for id=%u", gid); |
|
} |
|
expected = rx->id + 1; |
|
} else if (rx->id == expected) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: found expected packet id=%u", expected); |
|
expected++; |
|
} else { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: packet id=%u < expected=%u, already processed", |
|
rx->id, expected); |
|
} |
|
rx = rx->next; |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "detect_gaps_and_request_retrans: completed - gaps_found=%d, retrans_requested=%u", |
|
gaps_found, retrans_requested); |
|
} |
|
|
|
// Deliver contiguous (expanded) |
|
static void deliver_contiguous_packets(struct ETCP_CONN* etcp) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: etcp=%p, last_delivered_id=%u", |
|
etcp, etcp->last_delivered_id); |
|
|
|
rx_packet_t** ptr = &etcp->rx_list; |
|
int delivered_count = 0; |
|
uint32_t delivered_bytes = 0; |
|
|
|
while (*ptr && (*ptr)->id == etcp->last_delivered_id + 1) { |
|
rx_packet_t* rx = *ptr; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivering packet id=%u (data_len=%u)", |
|
rx->id, rx->data_len); |
|
|
|
struct ll_entry* entry = queue_entry_new(rx->data_len); |
|
if (entry) { |
|
memcpy(ll_entry_data(entry), rx->data, rx->data_len); |
|
queue_entry_put(etcp->output_queue, entry); |
|
delivered_bytes += rx->data_len; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: queued packet id=%u to output_queue", rx->id); |
|
} else { |
|
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: failed to allocate queue entry for packet id=%u", rx->id); |
|
} |
|
|
|
etcp->last_delivered_id = rx->id; |
|
*ptr = rx->next; |
|
free(rx->data); |
|
free(rx); |
|
delivered_count++; |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "deliver_contiguous_packets: delivered %u contiguous packets (%u bytes), last_delivered_id=%u", |
|
delivered_count, delivered_bytes, etcp->last_delivered_id); |
|
} |
|
|
|
// Retrans check (expanded with lp_ts) |
|
static void retrans_check(struct ETCP_CONN* etcp) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: etcp=%p, wait_ack_list=%p, rtt_avg_10=%u, jitter=%u", |
|
etcp, etcp->wait_ack_list, etcp->rtt_avg_10, etcp->jitter); |
|
|
|
uint64_t now = get_current_time_units(); |
|
inflight_packet_t** ptr = &etcp->wait_ack_list; |
|
uint16_t lp_ts = 0; |
|
int checked_packets = 0; |
|
int retrans_packets = 0; |
|
uint16_t timeout_calc = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: timeout calculation: rtt_avg_10=%u * %.1f + jitter=%u * %.1f = %u", |
|
etcp->rtt_avg_10, RETRANS_K1, etcp->jitter, RETRANS_K2, timeout_calc); |
|
|
|
while (*ptr) { |
|
inflight_packet_t* pkt = *ptr; |
|
checked_packets++; |
|
uint16_t timeout = (uint16_t)(etcp->rtt_avg_10 * RETRANS_K1 + etcp->jitter * RETRANS_K2); |
|
uint16_t time_since_send = timestamp_diff(now, pkt->last_timestamp); |
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "retrans_check: checking packet id=%u (time_since_send=%u, timeout=%u, need_retrans=%u)", |
|
pkt->id, time_since_send, timeout, pkt->need_retrans); |
|
|
|
if (time_since_send > timeout) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: packet id=%u timed out, marking for retransmission", pkt->id); |
|
pkt->need_retrans = 1; |
|
move_to_wait_send(etcp, pkt); |
|
etcp->retransmissions_count++; |
|
retrans_packets++; |
|
ptr = &etcp->wait_ack_list; // Restart scan after move |
|
continue; |
|
} |
|
// lp_ts logic (per spec: if acked, remember ts; if later unacked with ts < lp_ts, ?) |
|
// Assume if pkt acked (but shouldn't be in list), or for forward progress |
|
ptr = &pkt->next; |
|
} |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: completed - checked %d packets, retransmitted %d packets, total_retransmissions=%u", |
|
checked_packets, retrans_packets, etcp->retransmissions_count); |
|
|
|
// Reschedule if list not empty |
|
if (etcp->wait_ack_list) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: rescheduling retrans_timer (wait_ack_list not empty)"); |
|
etcp->retrans_timer = uasync_set_timeout(etcp->instance->ua, 20, etcp, retrans_timer_cb); |
|
} else { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_check: clearing retrans_timer (wait_ack_list empty)"); |
|
etcp->retrans_timer = NULL; |
|
} |
|
} |
|
|
|
// Find inflight (expanded) |
|
static inflight_packet_t* find_inflight(struct ETCP_CONN* etcp, uint16_t id, uint8_t state) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: etcp=%p, id=%u, state=%u", etcp, id, state); |
|
|
|
inflight_packet_t* list = (state == INFLIGHT_STATE_WAIT_ACK) ? etcp->wait_ack_list : etcp->wait_send_list; |
|
inflight_packet_t* pkt = list; |
|
int position = 0; |
|
|
|
while (pkt && pkt->id != id) { |
|
pkt = pkt->next; |
|
position++; |
|
} |
|
|
|
if (pkt) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: found packet %u at position %d in %s list", |
|
id, position, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); |
|
} else { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "find_inflight: packet %u not found in %s list", |
|
id, (state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); |
|
} |
|
|
|
return pkt; |
|
return pkt; |
|
} |
|
|
|
// Move to wait_send (expanded) |
|
static void move_to_wait_send(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: etcp=%p, pkt=%p (id=%u, state=%u)", |
|
etcp, pkt, pkt->id, pkt->state); |
|
|
|
remove_inflight(etcp, pkt); |
|
pkt->state = INFLIGHT_STATE_WAIT_SEND; |
|
pkt->next = etcp->wait_send_list; |
|
etcp->wait_send_list = pkt; |
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "move_to_wait_send: moved packet %u to wait_send_list", pkt->id); |
|
} |
|
|
|
// Remove inflight (expanded) |
|
static void remove_inflight(struct ETCP_CONN* etcp, inflight_packet_t* pkt) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: etcp=%p, pkt=%p (id=%u, state=%u)", |
|
etcp, pkt, pkt->id, pkt->state); |
|
|
|
inflight_packet_t** list = (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? &etcp->wait_ack_list : &etcp->wait_send_list; |
|
inflight_packet_t** ptr = list; |
|
int position = 0; |
|
|
|
while (*ptr && *ptr != pkt) { |
|
ptr = &(*ptr)->next; |
|
position++; |
|
} |
|
|
|
if (*ptr) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "remove_inflight: found packet %u at position %d in %s list", |
|
pkt->id, position, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); |
|
*ptr = (*ptr)->next; |
|
} else { |
|
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "remove_inflight: packet %u not found in %s list", |
|
pkt->id, (pkt->state == INFLIGHT_STATE_WAIT_ACK) ? "wait_ack" : "wait_send"); |
|
} |
|
} |
|
|
|
// Start burst (placeholder expanded) |
|
static void start_burst_measurement(struct ETCP_CONN* etcp, struct ETCP_LINK* link) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "start_burst_measurement: etcp=%p, link=%p", etcp, link); |
|
|
|
// Periodically call this |
|
etcp->burst_in_progress = 1; |
|
uint16_t old_start_id = etcp->burst_start_id; |
|
etcp->burst_start_id = etcp->next_tx_id; |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "start_burst_measurement: started burst measurement - burst_start_id=%u (was %u)", |
|
etcp->burst_start_id, old_start_id); |
|
|
|
// Delay first, then burst 4 |
|
// Assume handled in request_pkt by adding meas_ts to next BURST_SIZE pkts |
|
} |
|
|
|
// Update link bw (expanded) |
|
static void update_link_bandwidth(struct ETCP_LINK* link, uint32_t new_bw) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: link=%p, new_bw=%u bits/sec (current=%u)", |
|
link, new_bw, link->bandwidth); |
|
|
|
uint32_t old_bw = link->bandwidth; |
|
link->bandwidth = (uint32_t)((link->bandwidth * 0.9) + (new_bw * 0.1)); // Smooth |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "update_link_bandwidth: updated link bandwidth from %u to %u bits/sec (new_bw=%u)", |
|
old_bw, link->bandwidth, new_bw); |
|
} |
|
|
|
// Update RTT (expanded with history) |
|
static void update_rtt(struct ETCP_CONN* etcp, uint16_t new_rtt) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: etcp=%p, new_rtt=%u (last_rtt=%u, avg_10=%u, avg_100=%u)", |
|
etcp, new_rtt, etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); |
|
|
|
etcp->rtt_last = new_rtt; |
|
etcp->rtt_avg_10 = (etcp->rtt_avg_10 * 9 + new_rtt) / 10; |
|
etcp->rtt_avg_100 = (etcp->rtt_avg_100 * 99 + new_rtt) / 100; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "update_rtt: updated RTT metrics - last=%u, avg_10=%u, avg_100=%u", |
|
etcp->rtt_last, etcp->rtt_avg_10, etcp->rtt_avg_100); |
|
|
|
// History for jitter |
|
etcp->rtt_history[etcp->rtt_history_idx++] = new_rtt; |
|
if (etcp->rtt_history_idx >= RTT_HISTORY_SIZE) etcp->rtt_history_idx = 0; |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "update_rtt: stored RTT in history at index %u", |
|
(etcp->rtt_history_idx + RTT_HISTORY_SIZE - 1) % RTT_HISTORY_SIZE); |
|
|
|
calculate_jitter(etcp); |
|
} |
|
|
|
static void calculate_jitter(struct ETCP_CONN* etcp) { |
|
uint16_t min_rtt = UINT16_MAX; |
|
uint16_t max_rtt = 0; |
|
int valid_samples = 0; |
|
|
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: analyzing RTT history (history_idx=%u)", etcp->rtt_history_idx); |
|
|
|
for (int i = 0; i < RTT_HISTORY_SIZE; i++) { |
|
if (etcp->rtt_history[i] == 0) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: history[%d] = 0 (invalid), skipping", i); |
|
continue; |
|
} |
|
valid_samples++; |
|
if (etcp->rtt_history[i] < min_rtt) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new min_rtt=%u (was %u) at history[%d]", |
|
etcp->rtt_history[i], min_rtt, i); |
|
min_rtt = etcp->rtt_history[i]; |
|
} |
|
if (etcp->rtt_history[i] > max_rtt) { |
|
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "calculate_jitter: new max_rtt=%u (was %u) at history[%d]", |
|
etcp->rtt_history[i], max_rtt, i); |
|
max_rtt = etcp->rtt_history[i]; |
|
} |
|
} |
|
|
|
uint16_t old_jitter = etcp->jitter; |
|
etcp->jitter = max_rtt - min_rtt; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "calculate_jitter: completed - valid_samples=%d, min_rtt=%u, max_rtt=%u, jitter=%u (was %u)", |
|
valid_samples, min_rtt, max_rtt, etcp->jitter, old_jitter); |
|
} |
|
|
|
// Timers (expanded with debug) |
|
static void retrans_timer_cb(void* arg) { |
|
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "retrans_timer_cb: etcp=%p, triggered retransmission check", etcp); |
|
retrans_check(etcp); |
|
} |
|
|
|
static void ack_timer_cb(void* arg) { |
|
struct ETCP_CONN* etcp = (struct ETCP_CONN*)arg; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: etcp=%p, processing pending ACKs", etcp); |
|
etcp->ack_timer = NULL; |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: processing %u pending ACKs", etcp->pending_ack_count); |
|
|
|
// Force send ACK if pending (e.g., empty payload pkt) |
|
// For now, assume appended next |
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "ack_timer_cb: ACK processing completed"); |
|
} |
|
|
|
// Get stats (expanded) |
|
void etcp_get_stats(struct ETCP_CONN* etcp, size_t* packets_sent, size_t* packets_recv, |
|
size_t* pool_allocs, size_t* pool_reuse) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: etcp=%p", etcp); |
|
|
|
if (packets_sent) { |
|
*packets_sent = etcp->total_packets_sent; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: total_packets_sent=%zu", *packets_sent); |
|
} |
|
if (packets_recv) { |
|
*packets_recv = etcp->bytes_received_total / 1400; // Approx MTU |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: bytes_received_total=%u, estimated_packets=%zu", |
|
etcp->bytes_received_total, *packets_recv); |
|
} |
|
if (pool_allocs || pool_reuse) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: getting memory pool stats"); |
|
memory_pool_get_stats(etcp->instance->pkt_pool, pool_allocs, pool_reuse); |
|
if (pool_allocs) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_allocs=%zu", *pool_allocs); |
|
if (pool_reuse) DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_get_stats: pool_reuse=%zu", *pool_reuse); |
|
} |
|
} |