diff --git a/src/transport_layer/etcp.h b/src/transport_layer/etcp.h index 6eab68b6..57c3141c 100644 --- a/src/transport_layer/etcp.h +++ b/src/transport_layer/etcp.h @@ -65,6 +65,7 @@ uint16_t get_current_timestamp(void); #define INFLIGHT_STATE_WAIT_ACK 0 #define INFLIGHT_STATE_WAIT_SEND 1 #define INFLIGHT_INITIAL_HASH_SIZE 1024 +#define ETCP_LINK_HISTORY_BYTES 32 // по одному биту для локального слота линка 0..255 #define MAX_INFLIGHT_SIZE 16384 // максимальное число элементов в inflight приёмной очереди (для предотвращения атак) #define ETCP_ACK_GAP_MAX_PACKETS 8192 // предел кумулятивного ACK за приём: 8192 × PACKET_DATA_SIZE(2048) = 16 MB (защита от DoS) @@ -84,8 +85,14 @@ struct INFLIGHT_PACKET {// выделяется из etcp->inflight_pool uint32_t active_attempts; // Попытки этого seq, ещё занимающие окна каналов uint8_t state; // WAIT_SEND — готов к отправке; WAIT_ACK — логически ожидает доставки uint8_t send_hist[8]; // local_link_id последних попыток; send_count%8 — следующая позиция до насыщения счётчика + uint8_t used_links[ETCP_LINK_HISTORY_BYTES]; // принятые отправки текущего обхода; слот очищается до удаления/повторной выдачи ID }; +/* Проверить текущий обход каналов пакета; бит описывает живой локальный слот, а не идентичность физической попытки. */ +static inline int etcp_packet_link_used(const struct INFLIGHT_PACKET* packet, uint8_t link_id) { + return !!(packet->used_links[link_id >> 3] & (1u << (link_id & 7))); +} + // Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем) struct ETCP_FRAGMENT {// выделяется из пула etcp->rx_pool struct ll_entry ll; diff --git a/src/transport_layer/etcp_connections.h b/src/transport_layer/etcp_connections.h index b0484630..47b8fa20 100644 --- a/src/transport_layer/etcp_connections.h +++ b/src/transport_layer/etcp_connections.h @@ -269,6 +269,9 @@ struct ETCP_LINK { uint64_t retrans_deadline_us; // Уже запланированная проверка: более ранний таймер можно сохранить uint32_t data_rtt_us; // Сглаженный RTT DATA->ACK этого пути; 0 до первого измерения uint32_t data_jitter_us; // Сглаженное абсолютное отклонение собственного DATA RTT + uint8_t data_rto_streak; // 0..3: RTO попыток без подтверждённого прогресса; после3 tx_stalled=1 + uint8_t tx_stalled; // исключить из повторов при наличии незастрявшего UP-пути; новые DATA/UP не меняются + uint64_t tx_stalled_since_us; // момент установки флага; снять его может ACK принятой после этого DATA-попытки uint32_t inflight_bytes; // DATA-байты физических попыток этого линка, включая синхронный резерв send uint32_t inflight_packets; // число таких пакетов diff --git a/src/transport_layer/etcp_tx.h b/src/transport_layer/etcp_tx.h index 4bf304a8..3c35825a 100644 --- a/src/transport_layer/etcp_tx.h +++ b/src/transport_layer/etcp_tx.h @@ -14,6 +14,8 @@ struct ETCP_LINK; struct ETCP_DGRAM; struct INFLIGHT_PACKET; +#define ETCP_TX_STALL_THRESHOLD 3 // три RTO физических попыток без ACK-прогресса, включая один пакетный вызов таймера + /* Внутренний ключ пары seq/номер без padding. Номер 1..65535; wire содержит seq:u32 и номер:u16 отдельно. */ static inline uint64_t etcp_tx_attempt_id(uint32_t seq, uint16_t number) { return ((uint64_t)seq << 16) | number; @@ -36,7 +38,7 @@ struct ETCP_TX_ATTEMPT { /* Инициализировать очереди attempts/ACK линка. 0 — успех, -1 — OOM (с логом); при ошибке очереди очищены. */ int etcp_tx_link_init(struct ETCP_LINK* link); -/* Снять попытки/таймер/ACK линка перед reset или free. retry=1 возвращает недоставленные DATA на отправку. +/* Снять попытки/таймер/ACK/флаг застревания линка перед reset или free. retry=1 возвращает недоставленные DATA на отправку. * reset не меняет физический UP; destroy дополнительно освобождает очереди. */ void etcp_tx_link_reset(struct ETCP_LINK* link, int retry); void etcp_tx_link_destroy(struct ETCP_LINK* link); @@ -51,10 +53,12 @@ uint32_t etcp_tx_rto_us(const struct ETCP_LINK* link); * После номера 65535 новый prepare сбрасывает поток со сменой эпохи, не оборачивает счётчик. */ struct ETCP_TX_ATTEMPT* etcp_tx_prepare(struct ETCP_LINK* link, struct INFLIGHT_PACKET* packet); /* Завершить синхронный send: success=1 принят транспортом, 0 — снять резерв без сетевой потери. + * Принятие DATA фиксирует историю каналов и RR; повторно использованный слот начинает новый обход. * Вызвать ровно один раз до освобождения dgram и до следующего poll; dgram->attempt обнуляется. */ void etcp_tx_complete(struct ETCP_DGRAM* dgram, int success); /* Принятый ACK: seq подтверждает логическую доставку; внутренний attempt_id=(seq<<16)|номер обновляет только свой линк. * Неизвестный старый ID не создаёт измерение и не вычитает окно. UINT32_MAX delay — RTT недостоверен. + * Действующий ACK сбрасывает серию RTO; флаг застревания снимается только для отправки после его установки. * peer_rx_bytes/time_us — согласованная точка приёма пути из ACK; 0/0 — неизвестна. * Достоверная пара ограничивает BW темпом приёма, сохраняя границы send/ACK; RTT и доставка от неё не зависят. * interval=max(send_us,ack_us,ceil(delivered*delta_rx_us/delta_rx_bytes)); дельты от снимка при отправке.