diff --git a/doc/etcp_protocol.txt b/doc/etcp_protocol.txt index bd25176e..907f16fd 100644 --- a/doc/etcp_protocol.txt +++ b/doc/etcp_protocol.txt @@ -324,3 +324,22 @@ only after STCP handshake. It completes the same session confirmation before INI REINIT notifies stream consumers without tearing down physical ownership. The router retains end-to-end inflight; BGP repeats JOIN_GROUP and TABLE_REQUEST because its previous control messages may have been discarded by the stream reset. + +Отказы памяти и прерывание потока: +- OOM до сохранения RX DATA в recv_q не создаёт ACK; отправитель повторяет DATA. +- ACK означает принятие в текущий транспортный поток, а успешный etcp_send — + передачу владения транспорту. Оба не гарантируют завершение операции сервиса + после прерывания сессии. Надёжность операции через REINIT обеспечивает её владелец. +- OOM после принятия данных в normalizer или при подготовке TX inflight вызывает + fatal REINIT: новая локальная эпоха, блокировка DATA, отбрасывание старых очередей, + частичных кодограмм и физических попыток. Старые payload не воспроизводятся. + Физические линки сохраняются; очистка очередей/reset не выделяет память. +- Активный обработчик освобождает собственный извлечённый фрагмент ровно один раз; + после reset/close из callback он прекращает разбор старой кодограммы/пакета. +- Согласование новой эпохи выполняется после очистки. OOM control-пакета оставляет + повтор активным, без продления исходного срока 10с. Отказ обязательного session- + таймера или генерации локальной эпохи закрывает соединение с диагностикой. + Если невозможно выделить даже обязательный deferred-close token внутри callback, + процесс завершается через abort: соединение не оставляется в зависшем состоянии. +- После подтверждения эпох владельцы получают REINIT и восстанавливают операции + своего протокола; при невозможности согласования соединение закрывается. diff --git a/src/transport_layer/etcp_session.h b/src/transport_layer/etcp_session.h index cf29f2c6..7d53b5f6 100644 --- a/src/transport_layer/etcp_session.h +++ b/src/transport_layer/etcp_session.h @@ -15,7 +15,8 @@ struct ETCP_CONN; #define ETCP_SESSION_HEADER_SIZE 17 #define ETCP_SESSION_CONTROL_SIZE 25 -/* Запустить обязательное подтверждение; при таймауте неподтверждённое соединение закрывается. */ +/* Запустить обязательное подтверждение: повтор через 500мс, общий срок 10с от start. + * OOM control-пакета оставляет повтор активным; OOM таймера или истечение срока закрывает соединение. */ void etcp_session_start(struct ETCP_CONN* conn); /* Предложить ненулевую эпоху пира для challenge; это ещё не разрешает приём DATA новой эпохи. */ void etcp_session_observe(struct ETCP_CONN* conn, uint64_t peer_epoch); diff --git a/src/transport_layer/pkt_normalizer_doc.md b/src/transport_layer/pkt_normalizer_doc.md index 0e7e0f73..0d9eca3c 100644 --- a/src/transport_layer/pkt_normalizer_doc.md +++ b/src/transport_layer/pkt_normalizer_doc.md @@ -1,12 +1,12 @@ # Packet Normalizer (фрагментация/дефрагментация) ## 1. Назначение -Модуль адаптирует произвольные IP-пакеты к передаче через ETCP-канал с ограниченным MTU. Состоит из двух половин: +Модуль адаптирует кодограммы сервисов к передаче через ETCP-канал с ограниченным MTU. Содержимое кодограммы непрозрачно для normalizer. Состоит из двух половин: - **Packer** (упаковщик) — разбивает входящие пакеты на фрагменты размером `frag_size` и помещает их в `etcp->input_queue`. Фрагменты собираются в общий буфер пачками для экономии накладных расходов, сброс по таймеру (call_soon) при пустой входной очереди. - **Unpacker** (распаковщик) — собирает фрагменты из `etcp->output_queue` обратно в исходные пакеты по заголовку длины (2 байта LE), помещает результат в `pn->output` (который читает ETCP router/клиент). -Каждое ETCP-соединение имеет свой экземпляр `PKTNORM` (создаётся в `etcp.c:247`). +Каждое ETCP-соединение владеет своим экземпляром `PKTNORM`. ## 2. Как пользоваться @@ -32,13 +32,18 @@ queue_data_put(pn->input, entry); ``` ### Формат фрагментов -Каждый фрагмент, уходящий в ETCP, содержит конкатенацию пакетов, каждый предварён 2-байтовым LE-заголовком длины: +Поток содержит конкатенацию кодограмм, каждая предварена 2-байтовым LE-заголовком длины: ``` [packet1_len:2 LE][packet1_data][packet2_len:2 LE][packet2_data]... ``` +Поток разделяется на фрагменты ETCP: данные одной кодограммы могут продолжаться в следующем фрагменте. Заголовок длины packer записывает целиком; максимально допустимая длина кодограммы — `PKTNORM_MAX_DGRAM_SIZE`. ### Сброс состояния -При разрыве/переподключении вызывать `pn_reset()`. При ошибке сборки (некорректный заголовок) вызывается `etcp_conn_reinit()` — полный перезапуск соединения. +Владелец вызывает `pn_reset()` при сбросе ETCP-потока. Он отменяет flush/waiter, очищает обе очереди normalizer и частичные буферы без выделения памяти. Сам `pn_reset()` не запускает handshake. + +Неверный заголовок/длина и OOM после принятия данных вызывают `etcp_conn_fatal_reinit()`: новая локальная эпоха, очистка старого потока и согласование с пиром при сохранённых физических линках. Старые кодограммы не воспроизводятся; после согласования владельцы получают REINIT и восстанавливают операции своего протокола. До сохранения RX DATA в `recv_q` OOM обрабатывает сам ETCP: отбрасывает фрагмент без ACK. + +Normalizеr не удерживает отдельную OOM-очередь или retry-таймер. После reset/close из пользовательского callback обработчик прекращает старый разбор; извлечённый им фрагмент остаётся его собственностью до однократного освобождения. Счётчик активных callbacks откладывает освобождение normalizer при закрытии. ## 3. API @@ -50,7 +55,7 @@ queue_data_put(pn->input, entry); |------|----------| | `input` | Входная очередь packer (`pn_input`) — сюда кладут пакеты для отправки | | `output` | Выходная очередь unpacker (`pn_output`) — отсюда читают собранные пакеты | -| `frag_size` | Максимальный размер одного фрагмента ETCP (`mtu - ACK_REZERV - UDP_HDR - UDP_SC_HDR`) | +| `frag_size` | Размер DATA-фрагмента: MTU минус IP/UDP overhead, `SC_ENCRYPT_OVERHEAD`, `ETCP_PAYLOAD_HEADER_SIZE` и `ACK_REZERV`; overhead учитывает IPv6-линки | | `data` / `data_ptr` / `data_size` | Буфер-накопитель packer (из `data_pool`) | | `recvpart` | Частично собранный пакет unpacker | | `flush_timer` | Таймер сброса накопленного буфера (call_soon) | @@ -66,7 +71,7 @@ queue_data_put(pn->input, entry); **`pn_deinit(pn)`** — освобождает все ресурсы: очереди, буферы, таймеры, waiter-ы. Вызывает `routing_del_conn()` (deprecated, no-op). Снимает callback с `etcp->output_queue` для предотвращения use-after-free. -**`pn_reset(pn)`** — сбрасывает состояние packer и unpacker (буферы, pending, recvpart, таймер). Используется при переподключении. +**`pn_reset(pn)`** — отбрасывает `input`/`output`, буферы packer/unpacker, pending и recvpart; отменяет flush и waiter. Не выделяет память, допускает NULL; используется владельцем при сбросе потока. Обычные callbacks очередей сохраняются. **`pn_unpacker_reset_state(pn)`** — сбрасывает только unpacker (recvpart). @@ -74,17 +79,17 @@ queue_data_put(pn->input, entry); ### Внутренние callback-и -**`packer_cb`** — callback на `pn->input`. Ждёт освобождения `etcp->input_queue` через `queue_waiter_wait`, затем вызывает `etcp_input_ready_cb`. +**`packer_cb`** — callback на `pn->input`. Пока сессия согласуется или закрывается, обработку не запускает. Ждёт освобождения `etcp->input_queue` через `queue_waiter_wait`, затем вызывает `etcp_input_ready_cb`. Отказ ожидания вызывает fatal REINIT. **`etcp_input_ready_cb`** — основной цикл packer. Извлекает пакет из `pn->input`, записывает 2-байтовый заголовок длины, копирует данные в буфер `pn->data`. При заполнении буфера (или если осталось <3 байт) вызывает `pn_send_to_etcp`. Если входная очередь пуста — ставит `flush_timer` через `uasync_call_soon` для сброса хвоста. **`pn_flush_cb`** — таймерный callback, вызывает `pn_send_to_etcp` при простое. -**`pn_send_to_etcp(pn)`** — выделяет `ETCP_FRAGMENT` из `io_pool`, передаёт в него накопленный буфер `pn->data`, отправляет в `etcp->input_queue`. Буфер после этого обнуляется (передан во владение фрагмента). +**`pn_send_to_etcp(pn)`** — выделяет `ETCP_FRAGMENT` из `io_pool`, передаёт в него накопленный буфер `pn->data`, отправляет в `etcp->input_queue`. Указатель normalizer обнуляется до синхронного callback очереди. Возвращает 0 при успехе/пустом буфере, -1 при ошибке или прерывании сессии; ошибку выделения/постановки обрабатывает fatal REINIT. -**`pn_unpacker_cb`** — callback на `etcp->output_queue`. В цикле извлекает фрагменты, парсит 2-байтовые заголовки длины, копирует данные в `pn->recvpart`. При полной сборке пакета помещает его в `pn->output`. При ошибке вызывает `etcp_conn_reinit`. +**`pn_unpacker_cb`** — callback на `etcp->output_queue`. Извлекает фрагменты, парсит длины, копирует данные в `pn->recvpart`. Перед выдачей целой кодограммы в `pn->output` снимает `recvpart` и обновляет статистику. OOM, неверный заголовок или отказ очереди вызывают fatal REINIT. После каждого пользовательского callback проверяет reset_count/close и прекращает старый разбор при изменении сессии. -**`pn_buf_renew(pn)`** — выделяет новый буфер из `data_pool` если `pn->data == NULL`. При нехватке места (<3 байт) вызывает `pn_send_to_etcp` для сброса текущего буфера. +**`pn_buf_renew(pn)`** — выделяет буфер из `data_pool`, если `pn->data == NULL`; при нехватке места (<3 байт) сначала вызывает `pn_send_to_etcp`. Возвращает 0 при успехе, -1 при ошибке/прерывании. OOM вызывает fatal REINIT. ### Зависимости - `etcp.h` / `etcp_api.h` — структуры `ETCP_CONN`, `ETCP_FRAGMENT`, `etcp_int_recv`