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.
 
 
 
 
 
 

297 lines
16 KiB

ETCP - Extended Transmission Control Protocol
=============================================
Протокол для надежной передачи данных поверх UDP с восстановлением порядка,
повторной передачей потерянных пакетов и управлением потоком. Работает в связке
с pkt_normalizer для фрагментации/сборки и connection для криптографии.
Ключевые принципы:
- Минимальные накладные расходы (4 байта заголовка + опциональные метаданные)
- Циклические 16-битные счетчики (ID, timestamp) для компактности
- Адаптивное управление окном на основе RTT и bandwidth
- Механизм forward progress для гарантии доставки даже при потерях
- Reset handshake для синхронизации состояния соединения
Структура данных epkt:
-----------------------
struct epkt {
// Очереди
ll_queue_t* tx_queue; // Данные для отправки (от app → pkt_normalizer)
ll_queue_t* output_queue; // Собранные данные (к app через pkt_normalizer)
// Списки пакетов
struct rx_packet* rx_list; // Полученные пакеты (отсортированный по ID)
struct sent_packet* sent_list; // Отправленные пакеты (ждут ACK)
// Идентификаторы и тайминги
uint16_t next_tx_id; // Следующий ID для передачи (1..65535, циклический)
uint16_t last_sent_id; // Последний отправленный ID (для ретрансмиссии)
uint16_t last_rx_id; // Последний полученный ID
uint16_t last_delivered_id; // Последний ID, переданный в output_queue
uint16_t last_acked_id; // Последний подтвержденный ID
uint16_t last_rx_ack_id; // Последний полученный ACK ID от пира
// Метрики и управление потоком
uint16_t rtt_last; // Последнее RTT (единицы 0.1 мс)
uint16_t rtt_avg_10; // Среднее RTT за 10 пакетов
uint16_t rtt_avg_100; // Среднее RTT за 100 пакетов
uint16_t jitter; // Усредненный джиттер
uint16_t bandwidth; // Пропускная способность (байт/ед.времени)
uint32_t bytes_allowed; // Разрешенные к отправке байты (накопление)
uint32_t unacked_bytes; // Байты в пути (отправлены, но не подтверждены)
uint32_t window_size; // Текущий размер окна (RTT × bandwidth × 2)
uint8_t window_blocked; // Флаг блокировки передачи
// Буферы служебной информации
uint16_t pending_ack_ids[32]; // ID пакетов для подтверждения
uint16_t pending_ack_timestamps[32]; // Timestamps для RTT расчета
uint8_t pending_ack_count; // Количество ожидающих ACK
uint16_t pending_retransmit_ids[32]; // ID пакетов для ретрансмиссии
uint8_t pending_retransmit_count; // Количество запросов
// Отслеживание прогресса (forward progress)
uint16_t oldest_missing_id; // Самый старый отсутствующий ID
uint16_t missing_since_time; // Время первого обнаружения пропуска
// Reset состояние
uint8_t reset_pending; // Отправлен reset, ждем ACK
uint8_t reset_ack_received; // Получен reset ACK
uint8_t initialized; // 1 = соединение инициализировано после первого handshake
void* reset_timer; // Таймер повторной отправки reset
uint16_t reset_retry_count; // Счетчик попыток reset
etcp_reset_callback_t reset_callback; // Callback для уведомления о reset
void* reset_callback_arg; // Аргумент callback
// Таймеры (uasync-based)
void* next_tx_timer; // Таймер следующей передачи
void* retransmit_timer; // Таймер проверки ретрансмиссий
uasync_t* ua; // Инстанс uasync
// Callback отправки
etcp_tx_callback_t tx_callback;
void* tx_callback_arg;
// Статистика
uint32_t retransmissions_count; // Счетчик ретрансмиссий
uint32_t ack_packets_count; // Счетчик ACK пакетов
uint32_t control_packets_count; // Счетчик управляющих пакетов
uint32_t total_packets_sent; // Всего отправлено пакетов
uint32_t unique_packets_sent; // Уникальных отправленных пакетов
uint32_t bytes_sent_total; // Всего отправлено байт
uint32_t bytes_received_total; // Всего получено байт
};
Формат кодограммы:
------------------
Каждая кодограмма состоит из обязательного заголовка и опциональных секций:
1. Обязательный заголовок (4 байта):
[ID high][ID low][Timestamp high][Timestamp low]
- ID: 16-битный циклический номер (0 = служебный пакет без данных)
- Timestamp: время отправки в единицах 0.1 мс (циклическое)
2. Опциональные секции (одна или несколько):
а) Подтверждения (ACK) - заголовок 0x01:
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
- count: количество пар ID+timestamp (1-32)
- last_delivered_id: последний ID, доставленный получателю
- last_rx_id: последний полученный ID (для синхронизации прогресса)
б) Запросы ретрансмиссии - заголовок 0x10-0x2F:
[0x10+(count-1)][(id_hi,id_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
- count: (заголовок & 0x0F) + 1 (1-32)
- last_delivered_id, last_rx_id: для синхронизации состояния
в) Полезная нагрузка - заголовок 0x00:
[0x00][данные...]
- Данные произвольной длины
г) Reset запрос - заголовок 0x02:
[0x02]
- Инициирует reset handshake
д) Reset подтверждение - заголовок 0x03:
[0x03]
- Подтверждение получения reset
Пакет может содержать несколько секций (например, ACK + данные). Секция с данными
обычно идет последней.
Очереди и потоки данных:
------------------------
1. Отправка (app → сеть):
app_input_queue → pkt_normalizer(packer) → tx_queue → tx_process() → UDP
2. Прием (сеть → app):
UDP → etcp_rx_input() → rx_list → output_queue → pkt_normalizer(unpacker) → app_output_queue
3. Управление потоком:
- tx_process() учитывает bandwidth (bytes_allowed) и window_size
- Данные блокируются при reset_pending && !reset_ack_received
- Служебные пакеты (ACK, retransmit requests) обрабатываются всегда
Алгоритмы:
----------
1. Передача (tx_process):
- Проверка bandwidth: bytes_allowed накапливается со временем
- Проверка окна: unacked_bytes ≤ window_size
- Блокировка data packets при reset handshake
- Формирование пакета: заголовок + ACK + retransmit requests + данные
- Сохранение в sent_list для возможной ретрансмиссии
- Обновление unacked_bytes
2. Подтверждение и RTT:
- При получении пакета: ID+timestamp добавляются в pending_ack_ids
- При отправке: pending_ack_ids включаются в пакет
- Получатель вычисляет RTT = current_time - received_timestamp
- Усреднение: rtt_avg_10 (скользящее), rtt_avg_100 (история)
3. Ретрансмиссия (retransmit_check):
- Проверка sent_list каждые retrans_timer_period = max(RTT/2, 2ms)
- Порог ретрансмиссии: 1.5×RTT
- Новейший неподтвержденный пакет (last_sent_id) ретрансмитится через 2×RTT
- Пропускается при reset_pending && !reset_ack_received
4. Сборка на приеме:
- Вставка в отсортированный rx_list (по ID)
- Обнаружение пропусков → запросы ретрансмиссии
- Перемещение непрерывной последовательности в output_queue
- Обновление last_delivered_id
5. Forward progress:
- Отслеживание oldest_missing_id и missing_since_time
- Если пакет отсутствует >3×RTT (минимум 6 мс), last_delivered_id продвигается
- Предотвращает бесконечное ожидание потерянных пакетов
Reset механизм:
---------------
Цель: синхронизация состояния ETCP и pkt_normalizer при сбоях.
1. Инициализация соединения:
- При создании соединения: initialized=0
- Первый обмен reset (0x02) → reset_ack (0x03) → initialized=1
- Последующие resets вызывают локальный сброс состояния
2. Локальный reset (etcp_reset_connection):
- Установка reset_pending=1, reset_ack_received=0
- Отправка reset пакета (0x02)
- Таймер повторной отправки каждые 100 мс (до 10 попыток)
- Блокировка data packets, разрешение service packets
3. Удаленный reset (получение 0x02):
- Если initialized=0 (первый handshake): только reset_ack (0x03)
- Если initialized=1: reset_ack + вызов etcp_reset()
- etcp_reset() вызывает reset_callback (очистка pkt_normalizer)
4. Callback интеграция с connection:
- etcp_set_reset_callback(epkt, conn_etcp_reset_callback, conn)
- Callback очищает состояние packer/unpacker нормализатора
- Данные в очередях приложения сохраняются для повторной отправки
5. Завершение handshake:
- Получение reset_ack (0x03) → reset_ack_received=1, reset_pending=0
- initialized=1 (если еще не установлен)
- Отмена reset_timer
- Возобновление передачи data packets
Взаимодействие с pkt_normalizer:
---------------------------------
1. Фрагментация исходящих данных:
- pkt_normalizer(packer) разбивает данные на фрагменты ≤ 65535 байт
- Добавляет заголовки фрагментации (0xFC/0xFD/0xFE/0xFF)
- Передает в tx_queue через queue bridge
2. Сборка входящих данных:
- output_queue → pkt_normalizer(unpacker) собирает фрагменты
- Удаляет заголовки фрагментации
- Передает в app_output_queue
3. Reset очистка:
- pkt_normalizer_reset_state() сбрасывает буферы сборки
- Сохраняет сервисное состояние (service packets)
Статистика и отладка:
---------------------
Счетчики в структуре epkt позволяют отслеживать:
- Эффективность ретрансмиссий (retransmissions_count / unique_packets_sent)
- Накладные расходы (control_packets_count / total_packets_sent)
- Прогресс доставки (last_delivered_id vs last_rx_id)
Отладочные макросы (при -DETCP_DEBUG -DETCP_DEBUG_EXT):
- ETCP_LOG() - основные события
- ETCP_DEBUG_LOG() - детальная отладка
API функции:
------------
// Основные
epkt_t* etcp_init(uasync_t* ua);
void etcp_free(epkt_t* epkt);
void etcp_set_callback(epkt_t* epkt, etcp_tx_callback_t cb, void* arg);
void etcp_set_reset_callback(epkt_t* epkt, etcp_reset_callback_t cb, void* arg);
int etcp_rx_input(epkt_t* epkt, uint8_t* pkt, uint16_t len);
int etcp_tx_put(epkt_t* epkt, uint8_t* data, uint16_t len);
ll_queue_t* etcp_get_output_queue(epkt_t* epkt);
// Конфигурация
void etcp_set_bandwidth(epkt_t* epkt, uint16_t bandwidth);
void etcp_reset(epkt_t* epkt); // Локальный сброс состояния
void etcp_reset_connection(epkt_t* epkt); // Инициация reset handshake
// Мониторинг
uint16_t etcp_get_rtt(epkt_t* epkt);
uint16_t etcp_get_jitter(epkt_t* epkt);
int etcp_tx_queue_size(epkt_t* epkt);
void etcp_get_stats(epkt_t* epkt, ...); // Полная статистика
Особенности реализации:
-----------------------
1. Циклические счетчики:
- Функция id_compare(a,b) учитывает переполнение 65535→0
- Timestamp использует ту же логику для расчета RTT
2. Единицы времени:
- Базовый интервал: 0.1 мс (100 микросекунд)
- Все таймеры, RTT, jitter в этих единицах
- get_current_timestamp() возвращает монотонное время
3. Ограничения буферов:
- pending_ack_ids, pending_retransmit_ids: до 32 элементов
- rtt_history: 100 измерений для усреднения
- Максимальный размер данных в пакете: 65535 байт
4. Интеграция с uasync:
- Каждый epkt имеет ссылку на uasync_t* ua
- Таймеры создаются через uasync_set_timeout()
- Асинхронная обработка без блокировок
Пример потока данных:
---------------------
1. App отправляет данные → conn_send() → app_input_queue
2. Queue callback → pkt_normalizer(packer) → фрагментация
3. Packer output → tx_queue → tx_process()
4. tx_process() формирует пакет → tx_callback → UDP send
5. Пакет в сети → UDP recv → etcp_rx_input()
6. Обработка ACK/данных → output_queue → pkt_normalizer(unpacker)
7. Сборка фрагментов → app_output_queue → app callback
Reset сценарий:
---------------
1. Обнаружена проблема → etcp_reset_connection()
2. reset_pending=1, блокировка data packets
3. Отправка 0x02, таймер повторной отправки
4. Получатель: получен 0x02 → отправка 0x03
5. Если initialized=1 → etcp_reset() → reset_callback → очистка pkt_normalizer
6. Отправитель: получен 0x03 → reset_ack_received=1, reset_pending=0
7. Возобновление передачи, повторная отправка данных из app_input_queue