From 77a737fd71d8f9d8356079538509590520a42f9e Mon Sep 17 00:00:00 2001 From: evgeny Date: Fri, 23 Oct 2026 19:25:20 +0200 Subject: [PATCH] Document compact attempt wire layout and epoch reuse protection --- doc/etcp_protocol.txt | 29 ++++++++++++++++++----------- src/transport_layer/etcp_bbr_doc.md | 4 +++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/doc/etcp_protocol.txt b/doc/etcp_protocol.txt index 7e9c4d4e..e2c70599 100644 --- a/doc/etcp_protocol.txt +++ b/doc/etcp_protocol.txt @@ -20,7 +20,7 @@ HELLO/CHALLENGE/CONFIRM и keepalive передаются своим незав Кумулятивный till можно отправить по любому рабочему пути. ACK-only не увеличивает нагрузку DATA-shaper; физическая очередь канала остаётся общей, её накопление ограничивают DATA-cwnd/pacing BBR. 4. Выделить ETCP_DGRAM до удаления DATA из очереди и изменения счётчиков. - Выделить отдельную физическую попытку с новым attempt_id и зарезервировать окно выбранного линка. + Выделить физическую попытку с новым attempt_no для этого seq и зарезервировать окно выбранного линка. Только успешное принятие транспортом фиксирует время, last_link и перенос DATA в input_wait_ack. Ошибка send снимает резерв; логический DATA остаётся готовым без сетевого loss sample. Пакет содержит ACK, опциональные timestamp/измерительные секции и последнюю секцию PAYLOAD. @@ -28,11 +28,11 @@ HELLO/CHALLENGE/CONFIRM и keepalive передаются своим незав помещаться в окно, иначе серия отправляет FILLER. ACK/FILLER окно DATA не занимают. При приёме пакета (от etcp_connections): - последовательно сканируем секции и отдаём их на обработку нужным обработчикам: - - ack (0x01): attempt_id подтверждает физическую попытку только принявшего ACK линка; + - ack (0x01): пара seq+attempt_no подтверждает физическую попытку только принявшего ACK линка; seq отдельно подтверждает логическую доставку. till освобождает логические записи, без BBR/снятия чужого flight. - timestamp (0x06): обновляем last RTT, пересчитываем RTT avg10 и jitter по каждому линку; обновляем recv_dt_avg для раздельного измерения времени передачи/приёма - [0x00] payload: сохраняем фрагмент в recv_q либо распознаём уже сохранённый/доставленный seq. - Только после этого ставим ACK с seq+attempt_id в ack_q принявшего линка; OOM до сохранения не подтверждается. + Только после этого ставим ACK с seq+attempt_no в ack_q принявшего линка; OOM до сохранения не подтверждается. после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть. Таймер попыток каждого линка (etcp_tx.c): @@ -57,7 +57,9 @@ inflight - две очереди (ll_queue): - timestamp последней отправки (last_timestamp) - число отправок (send_count) - send_hist[8] — local_link_id последних попыток, только диагностика - - last_attempt_id и active_attempts — последняя отправка и число ещё учитываемых физических копий + - last_attempt_id — внутренний составной ключ (seq<<16)|attempt_no последней принятой отправки + - attempt_count — выданные номера попыток данного seq, общие для всех каналов, включая неудачные send + - active_attempts — число ещё учитываемых физических копий - Payload принадлежит только логической записи. Попытка ETCP_TX_ATTEMPT хранит seq и неизменные снимки, без payload. inflight_bytes/inflight_packets линка равны сумме DATA-длин/числу записей в его attempts, включая резерв send. ACK/till может уже удалить логический объект, пока другая физическая копия продолжает занимать своё окно. @@ -126,17 +128,17 @@ pending/частичный буфер normalizer; ограничение окн Формат и подтверждение эпох описаны в разделе «Подтверждение транспортной сессии» ниже. 2. Опциональные секции (одна или несколько) добавляются в etcp.c внутри DATA/ACK после заголовка сессии: а) Подтверждения (ACK) - заголовок 0x01: - [0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][attempt_id(8 LE)][delay_us(4 LE)] × count] + [0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][attempt_no(2 LE)][delay_us(4 LE)] × count] - count: количество ACK записей (1 байт; до 255, дополнительно ограничено MTU и оставшимся местом) - last_delivered_id: uint32_t — последний ID, доставленный получателю (по этот ID всё собрано подряд) - rx_dup_count: uint16_t — счётчик принятых дубликатов (локальный, для вычисления tx_dup_count на удалённой стороне) - seq: uint32_t — номер подтверждаемого пакета - - attempt_id: uint64_t — ID конкретной физической попытки DATA; возвращается только тем же путём + - attempt_no: uint16_t — номер физической попытки данного seq, 1..65535; возвращается только тем же путём - delay_us: uint32_t — задержка между приёмом DATA и формированием ACK, мкс; UINT32_MAX исключает RTT sample При count>0 после всех записей обязательно добавляется [rx_data_bytes(8 LE)][rx_data_time_us(8 LE)]. Это точка приёма всего пути на момент формирования ACK, а не отдельной подтверждаемой попытки. Время — монотонные микросекунды получателя при последнем положительном приросте DATA-байтов; 0/0 — точки нет. - Каждая запись = 16 байт, база ACK = 8 байт; полный размер = 8+16*count+(count>0 ? 16 : 0). + Каждая запись = 10 байт, база ACK = 8 байт; полный размер = 8+10*count+(count>0 ? 16 : 0). count=0 допустим для передачи till, точки приёма не содержит. До обработки проверяется вся длина ACK. ACK_REQUEST: [0x0a], одна секция длиной 1 байт, запрос повторной отправки актуального till. в) channel timestamp (добавляется после выбора канала передачи, если есть свежие данные): @@ -148,10 +150,15 @@ pending/частичный буфер normalizer; ограничение окн Всего 5 байт. Добавляется только если last_recv_updated=1 (был принят пакет с момента последней отправки) и если прошло менее 100с (dt < 1000000 таймбаз). Секция не является источником DATA RTT для BBR/RTO. 3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя): - [0x00] [ID 4 байта LE] [attempt_id 8 байт LE] [данные...] - - ID: uint32_t, 32-битный циклический номер пакета - - attempt_id: uint64_t, монотонный ненулевой ID; не сбрасывается при reset или пересоздании линка. - Переполнение закрывает соединение. Заголовок PAYLOAD занимает 13 байт; при повторе attempt_id новый, seq прежний. + [0x00] [ID 4 байта LE] [attempt_no 2 байта LE] [данные...] + - ID: uint32_t, номер фрагмента 1..UINT32_MAX в пределах одной эпохи потока. + - attempt_no: uint16_t, номер 1..65535, общий для всех каналов одного ID. + Номер выдаётся при создании попытки и не возвращается при отказе send; исходный seq при повторе сохраняется. + После номера65535 следующая попытка аварийно меняет эпоху через REINIT; счётчик не оборачивается. + Перед выдачей seq=0 после UINT32_MAX поток также меняет эпоху: пара seq/attempt_no не повторяется внутри эпохи. + Смена эпохи отбрасывает прежний поток, владельцы восстанавливают свои операции по REINIT, как при фатальном OOM. + Старые DATA/ACK отклоняются заголовком сессии до разбора. Заголовок PAYLOAD занимает7 байт. + Внутренние хеш-очереди сохраняют64-битный ключ (seq<<16)|attempt_no; он не передаётся по сети целиком. Пакет может содержать несколько опциональных секций (например, ACK + channel_timestamp + данные), Порядок: ACK, возможный ACK_REQUEST, измерительные/временные секции и FILLER, затем payload последним. Разбор секций — последовательное сканирование с известными размерами (нет явного поля длины секции). diff --git a/src/transport_layer/etcp_bbr_doc.md b/src/transport_layer/etcp_bbr_doc.md index 971494d6..d0fd05e2 100644 --- a/src/transport_layer/etcp_bbr_doc.md +++ b/src/transport_layer/etcp_bbr_doc.md @@ -41,7 +41,9 @@ bbr_tx_start(&bbr_state); // вызывать при возобновлени ### Интеграция с ETCP_LINK BBR создаётся в `etcp_link_new()` как `link->bbr`. `etcp_tx_ack()` передаёт sample только известной попытки своего линка. Sample использует неизменные снимки delivered и времени доставки/отправки из `ETCP_TX_ATTEMPT`. -DATA несёт `seq+attempt_id`, ACK возвращает их и задержку формирования ACK тем же каналом. +DATA несёт `seq:u32+attempt_no:u16`, ACK возвращает их и задержку формирования ACK тем же каналом. +Номер 1..65535 общий для всех каналов одного seq; внутренняя очередь индексируется ключом `(seq<<16)|attempt_no`. +Исчерпание номера или seq меняет эпоху потока до повторного использования пары; старые DATA/ACK отсекаются сессией. RTT попытки равен времени до ACK минус эта задержка. Неизвестный/истёкший ID не обновляет BBR/RTT или физическое окно. `etcp_ack_recv()` освобождает только логическую запись; кумулятивный till не создаёт BBR samples. Интервал — максимум времени отправки и подтверждения; ACK одной пачки не создают искусственную сверхскорость.