diff --git a/doc/stream.txt b/doc/stream.txt index 921f0689..72d5e681 100644 --- a/doc/stream.txt +++ b/doc/stream.txt @@ -9,11 +9,12 @@ Принятые решения: - Транспорт — существующий ETCP с быстрыми переповторами, мультилинком, шифрованием и управлением нагрузкой. - Параметры FEC задаёт источник; 6+2 — значение по умолчанию, а не ограничение протокола. -- Источник подписывает каждый блок после разбиения на субпотоки, включая проверочные блоки. +- Источник подписывает всю пачку один раз до RS; подпись восстанавливается вместе с содержимым и проверяется после decode. - В источник добавляется циклический timestamp uint16_t в миллисекундах; он сохраняется при всей ретрансляции. Задержка оценивается относительно предыдущих данных по дельтам timestamp и времени прихода, включая полную остановку. - Передаём произвольные данные как есть, без обработки форматов видео, кадров и ключевых точек. -- Размер одной кодограммы после разбиения — около 1300 байт; источник сбрасывает неполную пачку через 100 мс. +- Размер части автоматически уменьшается по длине пачки, максимум по умолчанию 1300 байт. + Побайтовый источник сбрасывает остаток через 100 мс; publish_block публикует сразу. - На каждый локальный экземпляр трансляции — кольцевой буфер 32 МБ для всех её субпотоков вместе. Вся выдача идёт из этого буфера. Вытесненные данные недоступны: не успел получить — опоздал. - Отдельных дозапросов диапазонов нет. Единственный способ получения — запросить субпоток с индекса или с конца. @@ -74,27 +75,36 @@ INFO/SUBSCRIBE/REVERSE_REQ к неподписанному узлу получа - Номера возрастают без повторов; UINT64_MAX зарезервирован для запроса с конца. Переполнение требует новой source_epoch. -Дескриптор содержит ключ трансляции, n, r и размер RS-кодограммы block_size. +Дескриптор содержит ключ трансляции, n, r и максимальный размер RS-кодограммы block_size. Параметры задаёт только источник и не меняет в пределах source_epoch; изменение создаёт новый запуск. -Дескриптор подтверждается подписанной опцией источника; поля FEC также входят в подписанный блок. +Дескриптор подтверждается подписанной опцией источника; поля FEC также входят в подписанную пачку. Ограничения кодека: 1 <= n <= 255, 0 <= r < n, 10 <= block_size <= 4096. -Кроме допустимости RS проверяется расход памяти и корректность полного размера блока. - -Целевой размер неизменяемого блока субпотока — около 1300 байт, включая метаданные источника и его 64-байтную подпись. -block_size для RS рассчитывается из этого бюджета; он включает 9-байтный заголовок RS seq/subblock. +Кроме допустимости RS проверяются расход памяти и место для подписанных метаданных, подписи и хотя бы одного байта содержимого. + +Максимальный размер части субпотока по умолчанию — 1300 байт: внешние метаданные 47 и RS до 1253 байт. +Внутри RS находятся подписанные метаданные 55 байт, содержимое и одна 64-байтная подпись. +block_size включает 9-байтный заголовок RS seq/subblock. Максимальная длина содержимого при 6+2 — 7345 байт. +При длине содержимого L фактический RS-size = 9 + ceil((55 + L + 64)/k), полный размер части = 47 + RS-size. +Например, L=1000 даёт восемь частей по 243 байта; L=7345 даёт восемь частей по 1300 байт. +Один контекст RS принимает пачки разной длины без пересоздания матрицы; все части одной пачки равны по размеру. Handle получателя, поколение stream-сессии, изменяемый путь и служебные заголовки ETCP идут сверх этого бюджета. Не обещаем соответствия одной кодограммы одному UDP-пакету: необходимую фрагментацию выполняет ETCP normalizer. На первой порции данных неполной пачки источник запускает таймер 100 мс. При заполнении пачка немедленно кодируется, таймер отменяется; для следующей неполной пачки начинается новый срок. -По таймауту кодируем накопленные данные с дополнением нулями, сохраняя их действительную длину. +По таймауту кодируем накопленные данные в минимальные части с дополнением максимум k-1 нулями. +Действительная длина содержимого входит в подпись; подпись также защищена RS от пропусков. Без накопленных данных пустые пачки не создаём. При штатном окончании источник сразу сбрасывает остаток. Этот таймер есть только у источника; ретрансляторы не накапливают данные для повторного формирования пачек. В этапе 1 ретранслятор пересылает полученные блоки; отсутствующие блоки для downstream заново не генерирует. -Локальный decode восстанавливает содержимое для потребителя, но не создаёт подписи источника на отсутствующие блоки. -Восстанавливаем при наличии k проверенных блоков, не ожидая остальных. -Полученные байты выдаются как есть с удалением padding, по порядку batch_seq. +Локальный decode восстанавливает содержимое и подпись для потребителя; отсутствующие части в кольцо не добавляются. +Восстанавливаем при наличии k структурно допустимых частей, не ожидая остальных. +После успешной проверки одной подписи выдаём только содержимое, без метаданных/подписи/padding, по порядку batch_seq. +Подпись не зависит от получения определённого субблока. Неверная подпись/подписанные поля дают GAP одной пачки; +приём следующей пачки продолжается, owner не закрывается. Повторно отвергнутая пачка не декодируется. +Подмена отдельной части обнаруживается только после RS; определить виновную часть по общей подписи нельзя. +Перебора альтернативных наборов и исправления неизвестных ошибок нет: RS исправляет только пропуски. Невосстановимый пропуск обозначается событием GAP; нельзя молча склеивать данные по обе стороны разрыва или выдавать нули за исходные байты. Окончательно пропускаем пачку, когда её данных уже нет в окне либо все нужные выдачи прошли её индекс/сообщили пропуск, а k блоков не собрано. Получение более поздней пачки от одного поставщика само по себе не закрывает ожидание остальных. @@ -102,20 +112,19 @@ Handle получателя, поколение stream-сессии, измен 4. Подпись источника и timestamp -Каждый полученный после RS блок, в том числе проверочный, подписывается Ed25519-ключом самого источника. -Подписывается каноническое сообщение с маркером назначения подписи stream_block, включающее: -group_id, source_node_id, source_epoch, параметры FEC, batch_seq, subblock, source_ts_ms, -действительную длину содержимого пачки, флаги источника и все байты RS-кодограммы. -Byte order и точная раскладка фиксируются в stream_proto.h. Дублирующиеся поля seq/subblock проверяются на совпадение. -Это связывает блок с группой, запуском, индексом, временем и параметрами кодирования. - -Подпись создаётся один раз в источнике после FEC. Она хранится с блоком и пересылается без изменений. +Источник подписывает Ed25519 каноническое сообщение stream_batch: group_id, source_node_id, source_epoch, +n, r, фактический RS-size, source_ts_ms, действительную длину, flags, batch_seq и все байты содержимого. +Единственная подпись добавляется к этому сообщению до RS. Внешняя часть stream_part1 содержит копию полей +без криптографической проверки; seq/subblock идут в RS-заголовке. Формат big-endian закреплён в stream_proto.h. +После decode подпись проверяется по ключу источника, а подписанные поля сверяются с дескриптором и внешними подсказками. +Неизменяемые части хранятся и пересылаются без изменения и без отдельной подписи каждого субблока. Handle, stream-сессия и hop-list находятся вне подписанного тела, поскольку меняются при выдаче и ретрансляции. -Перед помещением нового блока в кольцо, FEC или ретрансляцией проверяется подпись по ключу источника -из проверенной записи мембера данной группы. Ключ непосредственного поставщика не заменяет ключ источника. -Неверная подпись, неизвестный ключ или конфликт метаданных — отказ с диагностикой, без изменения индекса наличия. -Для одинакового уже проверенного блока допустимо повторно не выполнять crypto, только если совпадает всё неизменяемое тело. -Одинаковый индекс с другим содержимым нельзя считать обычным дубликатом без проверки. +Перед помещением части в кольцо проверяются формат, границы размеров, канонический размер и согласованность полей пачки. +Части могут пересылаться до сборки всей пачки, сохраняя малую задержку; они ещё не подтверждены ключом источника. +Содержимое для приложения допускается только после проверки подписи по ключу из принятой записи мембера данной группы. +Ключ непосредственного поставщика не заменяет ключ источника. Неверная подпись, неизвестный ключ или конфликт логируются. +При отвергнутой пачке логируются выбранные субблоки и их поставщики; приложение получает GAP и продолжает работу. +Дубликатом считается только побайтовое совпадение всей части. Тот же индекс с другим содержимым — конфликт. source_ts_ms = (uint16_t)monotonic_time_ms источника в момент формирования пачки. У всех её субпотоков одинаковые timestamp, длина и флаги. Ретранслятор не переписывает их своим временем. @@ -130,11 +139,13 @@ Timestamp циклический с периодом 65536 мс; обычный 5. Кольцевой буфер и единственный способ выдачи -На экземпляр — 32 * 1024 * 1024 байт под неизменяемые блоки всех субпотоков вместе, включая их подписи. +На экземпляр — 32 * 1024 * 1024 байт под неизменяемые части всех субпотоков вместе. +Слоты резервируются по максимальному размеру; меньшие части уменьшают трафик/работу RS, но не размер массива слотов. +stored_bytes и логическая очередь выдач учитывают фактические длины. Вытеснение ограничено числом зарезервированных слотов. Метаданные индекса, путей и сборки учитываются отдельно и ограничиваются. Буфер общий для локального потребителя и всех downstream; копии кольца на каждого получателя не создаются. Источник сначала помещает сформированные блоки в своё кольцо, затем делает их доступными выдачам. -Ретранслятор сначала проверяет и сохраняет блок, затем планирует выдачу подписчикам. +Ретранслятор сначала проверяет формат и сохраняет часть, затем планирует выдачу подписчикам. Индекс записи — (batch_seq, subblock) внутри конкретной эпохи. Новые пачки вытесняют старейшие; при вытеснении пачки удаляются все её блоки и метаданные. @@ -194,7 +205,7 @@ ETCP обеспечивает переповторы, порядок, мульт - SUBSCRIBE/ACCEPT/REJECT — непрерывная выдача с индекса либо последнего доступного блока. - RENEW — продление списка существующих выдач, период 2 секунды. - UNSUBSCRIBE/CLOSED — закрытие выдачи с указанием handle и причины. -- DATA/GAP/EXPIRED — подписанный блок либо уведомление о пропуске. +- DATA/GAP/EXPIRED — часть подписанной RS-пачки либо уведомление о пропуске. - OVERLOAD — запрос сокращения исходящей нагрузки через закрытие нескольких выдач. - REVERSE_REQ — встречное NCD-подключение через маршрутизатор группы. - END — последняя пачка и штатное завершение содержимого источником. @@ -229,7 +240,7 @@ ACCEPT содержит handle, фактический начальный инд которая ждёт первый блок; первый проверенный DATA по-прежнему устанавливает общий N. Пустой ретранслятор пути не объявляет. Завершение без данных подтверждается подписанным END с last_seq=UINT64_MAX. Отказы различают отсутствие подписки/трансляции, эпоху, FEC, позицию, слот, перегрузку, цикл и право доступа. -DATA содержит поколение сессии, handle, неизменяемый подписанный блок источника и изменяемый путь. +DATA содержит поколение сессии, handle, неизменяемую часть RS-пачки и изменяемый путь. Источник подписывает и END с ключом трансляции и индексом последней пачки, чтобы ретранслятор не мог подделать окончание. Нет продления 6 секунд — отправитель закрывает выдачу и сообщает CLOSED, при необходимости через маршрутизатор группы. @@ -476,12 +487,12 @@ Stop/destroy выполняются по правилам ядра, вне callb Проверки: - Несколько подписок/источников/групп на одном ETCP; stop одной сохраняет остальные и чужое владение. - Неподписанный узел не создаёт экземпляр, не выполняет crypto/FEC для DATA и не ретранслирует его. -- Источник задаёт различные FEC; decode из k проверенных блоков и невозможность при k-1. -- Подпись каждого исходного/проверочного блока; подмена payload/timestamp/индекса/FEC/эпохи/группы отвергается. -- Кодограммы около 1300 байт, полный/неполный буфер, источник сбрасывает за 100 мс, ретранслятор не имеет такого таймера. +- Источник задаёт различные FEC; decode и проверка одной подписи из k частей, невозможность при k-1. +- Подпись внутри RS; подмена payload/подписи/timestamp/индекса/FEC/эпохи/группы не выдаётся приложению; следующий индекс принимается. +- Части до 1300 байт, чередование малых/полных пачек, максимум k-1 padding; байтовый источник сбрасывает за 100 мс. - Оборот timestamp, нулевая дельта, постепенное отставание, догон, полная остановка и длинный разрыв без ложного числа оборотов. - Переполнение 32-МБ кольца, дырки, EXPIRED до отправки, курсор медленного получателя и ограниченность памяти. -- Первый подписанный блок с конца задаёт общий N; остальные выдачи начинают с него, а не с независимых концов. +- Первая допустимая часть с конца задаёт общий N; остальные выдачи начинают с него. READY требует восстановленной проверенной пачки. - Идемпотентные запросы/продления/закрытия, водяной знак handles, поздние сообщения, REINIT и новая эпоха источника. - Reverse/неуспех/отмена; потеря одного линка сохраняет выдачи, DOWN всего NCD запускает восстановление. - Перекрытие поставщиков без дублирования потребителю, rollback, одновременное восстановление нескольких субпотоков. @@ -506,9 +517,9 @@ Stop/destroy выполняются по правилам ядра, вне callb Реализация начата 10.10.2026. Работа разбита на проверяемые этапы: [+] Основа данных в src/stream: - stream_proto.h — канонический big-endian формат подписанного блока; дефолтный полный блок 1300 байт. - stream_block — RS и Ed25519-подпись каждого блока; проверка ключа трансляции, параметров и содержимого. - stream_buffer — общий бюджет 32 МБ по фактически сохранённым блокам, индекс пачек и вытеснение старейшей пачки. + stream_proto.h — канонический формат частей до 1300 байт и подписанной пачки внутри RS. + stream_block — переменный размер RS и одна Ed25519-подпись пачки до кодирования; проверка после восстановления. + stream_buffer — 32 МБ слотов, фактические длины данных/очереди, индекс пачек и вытеснение старейшей пачки. stream_metrics — относительное отставание, rate, timestamp wrap/reset и минутная история проблем/остановок. stream_source — приём непрозрачных байтов, немедленная выдача полной пачки, таймер остатка 100 мс и отмена при destroy. Добавлены категория stream, Autotools-сборка и test_stream_core. @@ -651,7 +662,7 @@ Stop/destroy выполняются по правилам ядра, вне callb Все stream-числа на wire — big-endian, без packed-структур и зависимости от host byte order. Версия stream wire — 4; обратная совместимость не поддерживается. -Заголовок raw (52 байта): cmd:1, version:1, command:1, subblock:1, +Заголовок raw (52 байта, version=5): cmd:1, version:1, command:1, subblock:1, group_id:8, source_node_id:8, source_epoch:8, sender_epoch:8, receiver_epoch:8, handle:8. INFO_REQ/RESP согласуют sender/receiver epochs; receiver_epoch=0 используется INFO_REQ для обнаружения поколения и REVERSE_REQ. Подписанный routed payload содержит тот же заголовок; его group_id сверяется с реальной группой router, @@ -670,7 +681,7 @@ bit3 — CPU известен, bit4 — кольцо непусто для эт SUBSCRIBE: requested_seq:8; REJECT: reason:1, для SUBSCRIBE дополнительно first_available:8,last_available:8, для INFO дополнительно probe_nonce:8. Старый nonce не подтверждает новую пробу. RENEW: один header handle без payload (каждая кодограмма продлевает один элемент списка). -UNSUBSCRIBE/CLOSED: reason:1. DATA: path_count:1,node_ids:8*path_count,неизменяемый подписанный блок. +UNSUBSCRIBE/CLOSED: reason:1. DATA: path_count:1,node_ids:8*path_count,неизменяемая часть RS-пачки переменной длины. GAP: old_cursor:8,next_cursor:8, уведомление включает диапазон [old_cursor,next_cursor). END: "stream_end":10,group_id:8,source_node_id:8,source_epoch:8,last_seq:8,Ed25519:64 — всего 106 байт. UINT64_MAX в END означает пустой источник; принимается только выдачей с next=0 и без ранее принятых DATA. @@ -728,7 +739,7 @@ Restart в той же source_epoch сохраняет consumer cursor: bootstra смешивание отвергается до изменения данных. stream_publish_finish не освобождает буфер источника. Медиаблок uAV1 находится целиком внутри входа RS, поэтому header восстанавливается вместе с данными. -При штатных 6+2 полный медиаблок <=7080 байт. Раскладка big-endian, без packed-структур: +При штатных 6+2 полный медиаблок <=7345 байт. Раскладка big-endian, без packed-структур: fixed header (32 байта): magic="uAV1":4, video_codec:1, audio_codec:1, video_count:1, audio_count:1, diff --git a/tests/test_stream_service.c b/tests/test_stream_service.c index 3de6db01..2fc2cc89 100644 --- a/tests/test_stream_service.c +++ b/tests/test_stream_service.c @@ -43,7 +43,7 @@ static etcp_recv_fn saved_raw[4], saved_route; static int drop_renew, gap_batch; static unsigned pressure_data, overload_seen, selects_seen; static int ignore_select; -static uint8_t pressure_payload[7080], stale_select[9 + STREAM_OVERLOAD_MAX * 8 + STREAM_WIRE_HEADER]; +static uint8_t pressure_payload[STREAM_DEFAULT_INPUT_CAPACITY], stale_select[9 + STREAM_OVERLOAD_MAX * 8 + STREAM_WIRE_HEADER]; static size_t stale_select_length; static etcp_recv_fn pressure_handlers[4]; static int simulated_cpu;