Browse Source

Document embedded batch signatures and resize pressure fixtures

master
evgeny 6 days ago
parent
commit
01d7fd5bef
  1. 89
      doc/stream.txt
  2. 2
      tests/test_stream_service.c

89
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,

2
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;

Loading…
Cancel
Save