Browse Source
- Add CRC32 checksum support using IEEE 802.3 polynomial - Update sc_encrypt/sc_decrypt API: combine tag and CRC32 into single buffer - Add SC_CRC32_SIZE, SC_MAX_OVERHEAD constants and SC_ERR_CRC_FAILED error code - Implement CRC32 calculation before encryption and verification after decryption - Update connection.c to use new API with error statistics tracking - Modify test files for compatibility with new API - All tests pass successfullyv2_dev
11 changed files with 514 additions and 584 deletions
@ -1,273 +0,0 @@ |
|||||||
# План реализации 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 на основе метрик |
|
||||||
Loading…
Reference in new issue