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
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 |