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