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