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.
 
 
 
 
 
 

273 lines
16 KiB

# План реализации 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 сюда не попадаютю просто парсим сразу метрици и всё.
};
```
## Формат пакета
```
<id:2> <timestamp:2> [<hdr:1> <metrics_data>]* <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 на основе метрик