# Архитектура ETCP Router Маршрутизатор связывает сервис с удалённым узлом через группу, не привязывая логическую сессию к одному физическому ETCP. Ключ канала — `(group_id, remote_node_id, svc_id)`. Wire-формат и сервисный API подробно описаны в [etcp_router_doc.md](../src/routing_layer/etcp_router_doc.md), объявления — в [etcp_router.h](../src/routing_layer/etcp_router.h). ## Отправка и приём ```mermaid flowchart TD Producer["Сервис: payload + mode"] --> Send["send_q: plaintext, seq, mode"] Send --> Session{"peer_reset_id подтверждён?"} Session -- нет --> Hello["HELLO / challenge / confirm"] Hello --> Session Session -- да --> Route["Текущий маршрут: conn_mgr → BGP, либо прямой глобальный ETCP"] Route --> Wait["Waiter физической send_input_q"] Wait --> Encode["route_crypto_encode: encrypt → sign"] Encode --> Inflight["inflight_q: неизменяемое тело без visited-list"] Inflight --> Path["Добавить source ID"] Path --> ETCP["etcp_send / normalizer"] Inflight -- RTO 300 ms --> Wait ETCP --> Network["Сеть / транзитные узлы"] Network --> Check["Проверить visited-list"] Check --> Destination{"dst=self?"} Destination -- нет --> Transit["Добавить self один раз, BGP next hop / transit FIFO"] Transit --> Network Destination -- да --> Decode["Crypto policy + verify/decrypt + проверка эпох"] Decode --> Incoming["incoming_q: plaintext"] Incoming --> Reorder["recv_q: reorder по seq"] Reorder --> Ready{"receive_ready разрешает?"} Ready -- нет --> Busy["Сохранить пакет, ACK RECEIVE_BUSY"] Busy --> Resume["resume_receive: отложенное продолжение"] Resume --> Reorder Ready -- да --> Callback["Callback сервиса получает entry"] Callback --> Ack["rx_seq++ → ACK текущей границы"] Ack --> Release["Отправитель снимает подтверждённый inflight"] ``` Каждая кодограмма имеет самостоятельный seq; это не байтовый поток. send_q ограничена 64 пакетами при обычной отправке, `force` обходит этот порог. Inflight по умолчанию — 256, при minRTT probe — 4. Получатель ограничивает reorder окном max_inflight и суммарным размером очередей max_inflight+1 записей. Все события выполняются в uasync-потоке. Штатный normalizer откладывает physical waiters, а callbacks производителей send_q могут выполняться синхронно при освобождении места. Поэтому код отправки повторно проверяет состояние/эпохи после callback. ## Эпохи логического канала Полный сетевой пакет: `[57-byte SVC_ROUTE_HDR][payload][signature?][visited_ids:8*N][count:1]` Числа в заголовке — native endian. flags — отдельный байт; биты seq не заняты флагами. Заголовок включает group/src/dst/service, seq/timestamp и поля reset_id/peer_reset_id/challenge. visited-list содержит 1..64 ID, находится вне подписи/шифрования и проверяется на каждом переходе. Первичное согласование двух сторон имеет вид: ```mermaid sequenceDiagram participant A as Узел A, local=a participant B as Узел B, local=b A->>B: HELLO(reset=a, peer=0) B->>A: CHALLENGE(reset=b, peer=a, cB) A->>B: CONFIRM(reset=a, peer=b, cB) A->>B: CHALLENGE(reset=a, peer=b, cA) B->>A: CONFIRM(reset=b, peer=a, cA) Note over A,B: Каждый узел принимает peer ID только после ответа на свой pending challenge ``` Получение HELLO/CHALLENGE с неизвестной эпохой само по себе не сбрасывает работающий канал. Переставленные/старые CONFIRM не совпадают с pending ID/challenge и игнорируются. Новые challenge ограничены интервалом 300 ms; первичный HELLO повторяется без отдельного timeout. | Событие | Что сохраняется | Что меняется | | --- | --- | --- | | Смена ETCP/next hop | Логические эпохи, seq, очереди | Физический waiter/маршрут | | Первое подтверждение peer | Накопленная send_q | Заполняется peer_reset_id, начинается отправка | | Подтверждённый рестарт peer | Собственный reset_id | Очереди, seq, RTT/метрики; уведомление сервиса | | Явный conn_restart | Ключ group/peer/service и адрес rconn | Новый reset_id, пустое состояние; затем callback и HELLO | | conn_close | Только данные, переданные во владение callback | Запись снимается с реестра, ресурсы освобождаются | Сервис возобновляет прикладную синхронизацию после сброса; старые гарантии доставки через reset не продолжаются. Готовность router означает наличие подтверждённого peer ID. Это отдельное состояние от физического UP и READY BGP-группы. ## Подтверждение и пауза потребителя DATA проходят crypto policy и verify/decrypt до изменения reorder/rx_seq. Сервис получает `[svc:1][src:8][dst:8][rx_flags:1][group:8][payload]`, всего 26 байт заголовка. Его callback владеет entry, ACK продвигается после успешной передачи callback. ACK подтверждает только диапазон до tx_sent, в модульном порядке uint32. При продвижении снимаются существующие inflight-записи и один раз учитываются исходные payload bytes. RECEIVE_BUSY не подтверждает отложенные DATA, но поддерживает живость ожидающего диска/буфера сервиса. Даже при прежнем seq допустимый BUSY обновляет отсчёт RTO и останавливает новую отправку/проход ретрансмитов. Обычный повторный ACK без продвижения этого не делает. Снятие BUSY возобновляет send_q. receive_ready — предикат без I/O и изменения/закрытия роутера. При 0 пакет сохраняется в recv_q; resume_receive планирует продолжение. Управляющий CLOSE не проходит через этот предикат. | Механизм | Интервал/условие | | --- | --- | | ACK | Не чаще 10 ms; текущая граница повторяется при дубликате DATA после потери ACK | | RECEIVE_BUSY | Около 100 ms, пока сервис остановлен | | Idle ACK | 500 ms, только если rx_seq ещё не был отправлен в ACK | | Handshake/RTO | 300 ms | | Нет маршрута после handshake | Повторный поиск через 20 ms | | Транзит при backpressure | Waiter + повтор выбора маршрута через 20 ms | | Watchdog send_q | 500 ms, пока очередь непуста | | Нет прогресса/BUSY | Закрытие после 17 циклов, для сервиса CALL — после 67 | Транзитные очереди принадлежат физическому ETCP, сгруппированы по group/src/dst и не имеют своих seq. При смене маршрута пакет переносится на актуальный next hop без ожидания освобождения старой очереди. Отсутствие пути приводит к потере транзитной копии; её восстанавливает end-to-end inflight источника. ## Владение и локальная доставка | Объект | Владение | | --- | --- | | Входной entry etcp_route_send | Всегда поглощается, включая ошибку | | Буфер etcp_router_conn_send | Заимствован на время копирования | | ETCP_ROUTER_CONN из conn_get | Заимствован до close/destroy | | Сервисный entry | Передаётся callback; освободить dgram и entry | | Физический conn в callback | Заимствован, может обозначать транзитного пира | | Синтетический conn | Только на время callback, instance/peer=self; без сетевого send/ref_take | | Send-ready handle/arg | У производителя до callback/отмены; перед освобождением отменить | Self-доставка через etcp_route_send синхронна и не создаёт канал. Она обходит сетевые crypto policy и receive_ready, выдаёт rx_flags=0; режим проверяется на допустимые биты. Payload должен быть непустым и помещаться вместе с 26-байтным заголовком в uint16 длину. Нет обработчика, переполнение размера или отказ памяти возвращают -1. Отдельный синтетический conn каждого вызова сохраняет контекст при вложенных доставках разных ядер. Закрываемый канал удаляется из реестра до callback. Callback может закрывать группу/другие каналы или открыть замену с тем же ключом; старая запись не возвращается повторным поиском. Память канала и send_q освобождается отложенно за пределами текущего callback. До массовой остановки сервисы отменяют producers/waiters; уничтожение ядра из callback останавливаемого сервиса запрещено. close_group удаляет логические каналы и pending transit указанной группы. destroy снимает wire callback, закрывает все логические каналы и очищает транзитные очереди, таймеры и waiters на всех живых ETCP. Уничтожать сам ETCP для очистки роутера не требуется. Метрики delivery считают первые подтверждения исходного payload, без заголовков/crypto/ретрансмитов. RTT — 0.1 ms, snapshot API выдаёт микросекунды; jitter — unsigned 16.16 EWMA, монитор публикует удвоенный EWMA. Очереди, метрики и параметры minRTT сбрасываются вместе с логической сессией.