# План реализации ETCP (Extended Transmission Control Protocol) ## Обзор Протокол поверх UDP с восстановлением порядка, повторными передачами, метриками RTT/jitter и ограничением полосы. Использует две очереди: сортированный linked-list для принятых пакетов и ll_queue для выходных данных. ## Структуры данных ### struct epkt (etcp.h) ```c typedef struct epkt epkt_t; struct epkt { // Очереди ll_queue_t* tx_queue; // Очередь пакетов на передачу ll_queue_t* output_queue; // Выходная очередь (собранные данные) struct rx_packet* rx_list; // Сортированный linked-list принятых пакетов // Метрики uint16_t rtt_last; // Последний RTT (timebase 0.1ms) uint16_t rtt_avg_10; // Среднее RTT за последние 10 пакетов uint16_t rtt_avg_100; // Среднее RTT за последние 100 пакетов uint16_t jitter; // Jitter (усреднённый) uint16_t bandwidth; // Текущая полоса пропускания (байт/таймбазу) uint32_t bytes_sent_total; // Всего отправлено байт uint16_t last_sent_timestamp; // Timestamp последней отправки uint32_t bytes_allowed; // Расчётное число байт, которое можно отправить // Состояние uint16_t next_tx_id; // Следующий ID для передачи uint16_t last_rx_id; // Последний принятый ID (для ACK) uint16_t last_delivered_id; // Последний доставленный в output_queue ID // Таймеры void* next_tx_timer; // Таймер следующей передачи void* retransmit_timer; // Таймер повторных передач // Callback'и void (*tx_callback)(epkt_t*, uint8_t*, uint16_t); // Отправка в UDP void* tx_callback_arg; // Буферы для метрик uint16_t rtt_history[100]; // История RTT для усреднения uint8_t rtt_history_idx; uint8_t rtt_history_count; // Накопленные timestamp'ы для отчётов uint16_t pending_ack_ids[32]; // ID пакетов, для которых нужно отправить ACK uint16_t pending_ack_timestamps[32]; // Соответствующие timestamp'ы uint8_t pending_ack_count; }; ``` ### struct rx_packet (внутренняя) ```c struct rx_packet { struct rx_packet* next; uint16_t id; uint16_t timestamp; uint8_t* data; uint16_t data_len; // uint8_t has_payload; // 1 если содержит payload (hdr=0) - пакеты без payload сюда не попадаютю просто парсим сразу метрици и всё. }; ``` ## Формат пакета ``` [ ]* ``` ### Заголовки (hdr): - `0x00`: payload (данные начинаются со следующего байта) - `0x01`: отчёт о timestamp принятого пакета (4 байта: id:2 + timestamp:2) - `0x10`-`0x2F`: перезапрос пакетов + ACK: - hdr & 0x0F = количество записей (1-32) - Далее N*2 байт: ID пакетов для повторной передачи - Последние 2 байта: номер последнего доставленного пакета (ACK) ### Пакет только с метриками: Если очередь передачи пуста, отправляется пакет с id=0 и без hdr=0+payload. ## Алгоритмы ### 1. Инициализация - Создать очереди tx_queue и output_queue через queue_new() - Инициализировать метрики нулями - Установить bandwidth по умолчанию (например, 100000 байт/таймбазу) ### 2. Приём пакетов (etcp_rx_input) 1. Парсинг: - Читаем id, timestamp - Пока есть данные, читаем hdr: - hdr=0x00: запоминаем payload - hdr=0x01: обрабатываем отчёт о timestamp (обновляем метрики) - hdr=0x10-0x2F: обрабатываем перезапрос (добавляем ID в очередь повторной передачи) 2. Для пакетов с hdr!=0 вызываем upd_metric_for_transmitter: - rtt_last = текущее время - timestamp - Обновляем rtt_avg_10 и rtt_avg_100 (скользящее среднее) - jitter += (abs(rtt_avg_10 - rtt_last) - jitter) * 0.1 3. Добавляем пакет в сортированный rx_list: - Ищем позицию по id (сравнение через (int16_t)(id1-id2)) - Пропускаем дубликаты - Вставляем в нужное место 4. Перемещаем непрерывную последовательность в output_queue: - Начиная с last_delivered_id+1, проверяем наличие пакетов в rx_list - При нахождении непрерывной цепочки перемещаем payload в output_queue - Освобождаем rx_packet структуры 5. Накопление ACK: - Для каждого принятого пакета сохраняем id и timestamp в pending_ack_* - При следующей отправке включаем эти ACK в пакет ### 3. Передача пакетов 1. Очередь tx_queue содержит данные для отправки 2. Функция tx_process вызывается по таймеру или при добавлении в пустую очередь: - Проверяем ограничение полосы: - delta_time = текущее_время - last_sent_timestamp - bytes_allowed += delta_time * bandwidth - Если bytes_allowed < размер_пакета, планируем таймер и выходим - Формируем пакет: - Базовый заголовок: next_tx_id++, текущий timestamp - Добавляем pending_ack (hdr=0x01 для каждого) - Добавляем перезапросы если нужно (на основе метрик) - Добавляем payload из tx_queue (hdr=0x00) - Если payload нет и есть метрики - отправляем пакет с id=0 - Отправляем через tx_callback - Обновляем bytes_sent_total, last_sent_timestamp, bytes_allowed - Сохраняем пакет в список отправленных (для возможной ретрансмиссии) 3. Повторные передачи: - Для каждого отправленного пакета отслеживаем время отправки - Если (текущее_время - время_отправки) > rtt_avg_10*1.2 + jitter*2 - Добавляем ID в очередь повторной передачи ### 4. Ограничение полосы - bandwidth: константа (байт/таймбазу), timebase = 0.1ms - При инициализации: bytes_allowed = 0, last_sent_timestamp = текущее_время - При отправке: - current_time = uasync время - delta = (int16_t)(current_time - last_sent_timestamp) (циклическое) - bytes_allowed += delta * bandwidth - Если bytes_allowed >= размер_пакета: - bytes_allowed -= размер_пакета - last_sent_timestamp = current_time - Отправляем пакет - Иначе: - wait_time = (размер_пакета - bytes_allowed) / bandwidth - Устанавливаем таймер на wait_time ## API функции ### Основные: ```c epkt_t* etcp_init(void); void etcp_free(epkt_t* epkt); void etcp_set_callback(epkt_t* epkt, void (*cb)(epkt_t*, uint8_t*, uint16_t), void* arg); int etcp_rx_input(epkt_t* epkt, uint8_t* pkt, uint16_t len); int etcp_tx_queue_size(epkt_t* epkt); ``` ### Вспомогательные: ```c void etcp_set_bandwidth(epkt_t* epkt, uint16_t bandwidth); uint16_t etcp_get_rtt(epkt_t* epkt); uint16_t etcp_get_jitter(epkt_t* epkt); ll_queue_t* etcp_get_output_queue(epkt_t* epkt); // Для извлечения данных int etcp_tx_put(epkt_t* epkt, uint8_t* data, uint16_t len); // Добавить данные на передачу ``` ## Интеграция с проектом ### Зависимости: - `ll_queue.c/.h` - для очередей - `u_async.h` - для таймеров - `stdint.h`, `stdlib.h`, `string.h` - стандартные библиотеки ### Таймеры: - Использовать `uasync_set_timeout` для: - Планирования следующей передачи (при ограничении полосы) - Повторных передач - Очистки старых пакетов в rx_list - Все callback'и должны быть быстрыми, не блокирующими ### Обработка циклических значений: - ID: uint16_t, сравнение через `(int16_t)(a - b)` - Timestamp: uint16_t, timebase 0.1us, сравнение аналогично - При вычислении дельты времени учитывать переполнение ## План тестирования 1. **Unit-тесты для парсинга:** - Корректность разбора различных hdr - Обработка циклических ID 2. **Тесты очередей:** - Сортировка в rx_list - Перемещение в output_queue - Обработка дубликатов 3. **Тесты метрик:** - Расчет RTT (скользящее среднее) - Расчет jitter - Обновление bandwidth 4. **Интеграционный тест:** - Два экземпляра ETCP, обмен данными через эмуляцию UDP - Проверка восстановления порядка при потере пакетов - Проверка ограничения полосы 5. **Тест производительности:** - Минимальные задержки - Корректность работы при высокой нагрузке ## Последовательность реализации 1. Создать etcp.h с определениями структур и API 2. Реализовать etcp.c в следующем порядке: a) Базовая структура и init/free b) Внутренние функции для работы с rx_list c) Парсинг пакетов (etcp_rx_input) d) Функции метрик (upd_metric_for_transmitter) e) Механизм передачи (tx_process, ограничение полосы) f) Повторные передачи g) Интеграция с таймерами u_async 3. Создать тестовую программу test_etcp.c 4. Протестировать, исправить ошибки 5. Интегрировать в Makefile ## Риски и неопределённости 1. **Переполнение буферов:** Нужны лиматиры на размер rx_list и pending_ack 3. **Производительность:** Сортированный linked-list может быть медленным при большом количестве пакетов. Возможно, нужна оптимизация. - не должно быть много пакетов в списке перезапросов 4. **Интеграция с существующим кодом:** Проверить совместимость стиля кодирования и соглашений об именовании. ## Дополнительные вопросы 1. Нужны ли callback'и для событий (доставка данных, изменение метрик)? - нет, но нужна структура из которой эти метрики можно считывать. 2. Как обрабатывать очень старые пакеты в rx_list (таймаут)? - никак. надо сделать reset_connection - он обнуляет все очереди, метрики итд. 3. Как определять начальный bandwidth и адаптировать его? - пока константа. адаптировать позже. ## Статус реализации (13.01.2026) ### Реализовано: 1. Структуры epkt, rx_packet, sent_packet 2. API функции: etcp_init, etcp_free, etcp_set_callback, etcp_rx_input, etcp_tx_queue_size, etcp_tx_put, etcp_get_output_queue, etcp_set_bandwidth, etcp_get_rtt, etcp_get_jitter 3. Парсинг пакетов с поддержкой заголовков 0x00 (payload), 0x01 (timestamp report), 0x10-0x2F (retransmission request) 4. Сортированный rx_list с учётом циклических ID (сравнение через (int16_t)(id1-id2)) 5. Перемещение непрерывной последовательности пакетов в output_queue 6. Ограничение полосы пропускания (bandwidth, bytes_allowed) 7. Отправка пакетов с данными и метриками, включая пакеты только с метриками (id=0) 8. Накопление ACK для полученных пакетов и отправка их в следующий пакет 9. Повторные передачи на основе RTT и jitter 10. Обновление метрик RTT (скользящее среднее за 10 и 100 пакетов) и jitter 11. Интеграция с u_async для таймеров передачи и проверки повторных отправок 12. Unit-тесты, покрывающие базовую функциональность (инициализация, передача, приём, реordering) ### Особенности реализации: - Timebase: 0.1ms (совместимость с u_async) - Bandwidth по умолчанию: 10000 байт/таймбазу - Максимальное количество pending ACK: 32 - Максимальное количество pending retransmit: 32 - RTT история: 100 измерений ### Ограничения и упрощения: - Отсутствует таймаут для старых пакетов в rx_list - Отсутствует адаптация bandwidth - get_current_timestamp использует статический счётчик (для тестов) - Нет обработки reset_connection - Нет защиты от переполнения буферов при очень большом количестве пакетов ### Планируемые улучшения: 1. Реализация reset_connection для сброса состояния 2. Использование системного времени для get_current_timestamp 3. Добавление таймаута для rx_list 4. Оптимизация производительности при большом количестве пакетов 5. Адаптация bandwidth на основе метрик