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