{
  "revision": "0.20",
  "profile": "group-audio/1",
  "openProfile": "group-audio-open/1",
  "title": "Открытый протокол систем оповещения. Групповая передача речи",
  "codec": "GSM",
  "sampleRate": 8000,
  "blockMs": 400,
  "shardMs": 100,
  "dataShards": 4,
  "parityShards": 1,
  "shardBytes": 165,
  "headerBytes": 40,
  "signatureBytes": 64,
  "tagBytes": 16,
  "maxDatagramBytes": 285,
  "maxAgeMs": 5000,
  "clockErrorMaxMs": 500,
  "maxBufferedBlocks": 4,
  "initialBufferMs": 800,
  "audioAbsenceMs": 5000,
  "verificationRatePerSecond": 25,
  "sections": [
    {
      "id": "group-scope",
      "title": "1. Назначение и совместимость",
      "blocks": [
        {
          "type": "p",
          "text": "Групповая передача дополняет базовую одноадресную связь с каждым УУ. Она предназначена для одной речи, одновременно адресованной многим получателям, когда сеть поддерживает маршрутизацию групп IPv4. Групповой пакет не выдает владение, не запускает ОУ и не заменяет индивидуальные результаты. Обязательная совместимость основного протокола не требует поддержки этого расширения."
        },
        {
          "type": "p",
          "text": "group-audio/1 использует UDP, шифрование Кузнечик-MGM и подпись источника ГОСТ Р 34.10-2012/GC256A со Стрибог-256. Общий ключ потока обеспечивает конфиденциальность, а отдельная подпись доказывает источник и при компрометации получателя. group-audio-open/1 передает те же заголовок и звуковые блоки без шифрования, метки и подписи; он ДОПУСКАЕТСЯ только при явно выбранном plain и optional. Эти режимы не равноценны по защищенности. Автоматическое переключение между ними запрещено."
        },
        {
          "type": "p",
          "text": "Маршрутизаторы и узлы распределения могут копировать исходные датаграммы без их изменения. Перекодирование или новое подписание создает другой источник и новый поток с отдельным разрешением. Для защищенного варианта подпись исходного разрешенного источника проверяет каждый УУ до декодирования. Доверие промежуточному узлу не заменяет эту проверку."
        }
      ]
    },
    {
      "id": "group-config",
      "title": "2. Конфигурация и подготовка",
      "blocks": [
        {
          "type": "p",
          "text": "Неизменный объект mediaGroups пакета конфигурации задает id, revision, deviceId, sourcePrincipalId, security=tls|plain, sourceAddress, sourcePort, groupAddress, destinationPort, ttl и resources. Только при security=tls обязательны sourceKeyId, publicX, publicY; при plain эти поля запрещены. id/sourceKeyId — token до 32 байт; revision — uint64 больше нуля; идентичности — 32 строчных hex; publicX/publicY — 64 строчных hex целочисленных координат GC256A в порядке big-endian, проверяемые на принадлежность кривой и подгруппе. resources содержит 1..4 различных resourceId/bindingGeneration данного deviceId; физическая привязка проверяется до commit. Открытые ключи не являются секретами. Изменение защищенного шаблона, источника и его ключа требует дополнительно права trustAdmin по TLS либо местной процедуры ввода при off. Открытый канал не устанавливает такое доверие; изменение иных полей конфигурации не дает обхода этой проверки."
        },
        {
          "type": "p",
          "text": "sourceAddress — разрешенный одноадресный IPv4; groupAddress — административная группа 239.0.0.0/8; sourcePort/destinationPort — 1..65535, ttl — 1..255. Сеть явно разрешает маршрут от этого источника до группы. До успешного ответа audio.group.prepare УУ выбирает локальный приемный интерфейс, открывает UDP-прием и выполняет присоединение IGMPv3 INCLUDE(sourceAddress) к groupAddress; стек ДОЛЖЕН принять подписку и поставить первоначальный отчет в очередь отправки. Если сеть использует IGMPv2, ДОПУСКАЕТСЯ присоединение к группе с обязательной программной фильтрацией источника. Неудача местного присоединения дает sourceUnavailable, без prepared. Это подтверждает местную подготовку, а не доставку по всем коммутаторам: фактический прием блока 0 проверяется в пределах prepareTimeoutMs. Источник начинает поток после получения prepared от выбранных получателей; неподготовленный получатель исключается явно. До session.start УУ не выдает звук. После discard, истечения подготовки, завершения, отзыва или reboot подписка снимается, когда не осталось другого действующего местного контекста этой группы. IGMP и фильтрация адреса не доказывают автора. Запоздалое присоединение к уже звучащему потоку в этом профиле НЕ ДОПУСКАЕТСЯ: ТРЕБУЕТСЯ новая подготовка и новый поток."
        },
        {
          "type": "p",
          "text": "audio.group.prepare является изменяющей командой владельца. Ее единственный элемент group содержит id, revision, streamId, source=recording|microphone и, только для recording, totalSamples. В режиме tls обязателен key — 64 строчных hex нового случайного 256-битного ключа данного потока; при plain key запрещен. Группа выбирает ресурсы из конфигурации, поэтому запрос не повторяет их длинное описание. Действуют bootId/controlEpoch/cmdSeq, права на все ресурсы и равенство sourcePrincipalId текущему участнику; для plain сравнивается только объявленный clientId без доказательства личности."
        },
        {
          "type": "p",
          "text": "Режим шаблона совпадает с режимом управляющего соединения; несовпадение дает forbidden. УУ выбирает group-audio/1 при tls и group-audio-open/1 при plain, проверяет поддержку соответствующего профиля, конфигурацию, часы и квоты, затем возвращает prepared mediaId/expiresAtMs/configRevision/capabilityRevision. mediaId индивидуален для УУ; streamId общий у получателей одного потока, содержит 32 строчных hex и создается случайно заново при каждой подготовке источника. Один streamId не используется повторно. Срок подготовки — 60000 мс. Повтор команды не продлевает срок. Во всех получателях один streamId обозначает одинаковые кодек, источник, ключ и шкалу блоков; иное значение отвергается как conflict."
        },
        {
          "type": "p",
          "text": "group-audio/1 РАЗРЕШАЕТСЯ только с защищенным управлением. Ключ передается каждому УУ по его собственному TLS-соединению с парным секретом, не включается в публичные пакеты доверия, конфигурацию, журналы и диагностические выгрузки. Хранятся лишь необходимый оперативный ключ и хеш нормализованного запроса для дедупликации; ключ уничтожается при discard, завершении, отзыве, истечении подготовки или reboot. Он не является общим секретом парка и не используется для управления, другой группы или нового потока. Приватный ключ подписи остается только у источника."
        },
        {
          "type": "p",
          "text": "audio.state дополнительно содержит groupStats с uint64 recoveredParts, receivedParityParts и missingDataParts. receivedPackets считает принятые проверенные звуковые части 0..3, receivedParityParts — части 4; FEC-восстановление увеличивает recoveredParts, а невосстановленный к сроку пропуск — missingDataParts и lostPackets. Поздние и повторные датаграммы учитываются отдельно и не увеличивают принятые части. clock имеет общие показатели частоты ЦАП; отсутствие оценки до начала звука не подменяется нулевой погрешностью. Индивидуальный session.get сохраняет критерии исполнения и полноту звука."
        },
        {
          "type": "p",
          "text": "session.start(mode=stream) с полученным mediaId и точными resources запускает обычный автомат исполнения; onControlLoss=stop. audio.discard, audio.state, audio.finish и session.stop/cancel сохраняют общие назначения. media.hello для группового профиля не используется. В audio.finish lastPacketIndex означает последний blockIndex, а sampleCount — число отсчетов источника. У каждого получателя session.start, session.stop, ошибки и результат самостоятельны; потеря связи с одним не создает успех остальным."
        }
      ]
    },
    {
      "id": "group-format",
      "title": "3. Формат датаграммы и подпись источника",
      "blocks": [
        {
          "type": "table",
          "headers": [
            "Смещение / длина",
            "Поле и представление"
          ],
          "rows": [
            [
              "0 / 4",
              "ASCII GA01 для защищенного профиля, GO01 для открытого."
            ],
            [
              "4 / 16",
              "streamId: 16 байт, полученных декодированием 32 hex."
            ],
            [
              "20 / 4",
              "blockIndex: uint32 BE, начинается с 0, увеличивается на 1; переполнение запрещено."
            ],
            [
              "24 / 8",
              "capturedUtcMs: uint64 BE, время захвата первого отсчета блока по UTC источника."
            ],
            [
              "32 / 2",
              "validSamples: uint16 BE, число действительных отсчетов всего блока: 3200, кроме конечного."
            ],
            [
              "34 / 2",
              "clockErrorMs: uint16 BE, верхняя погрешность UTC источника, 0..500."
            ],
            [
              "36 / 1",
              "shardIndex: 0..3 для звуковых частей, 4 для восстановительной части."
            ],
            [
              "37 / 1",
              "flags: бит 0 — END; остальные биты равны нулю."
            ],
            [
              "38 / 2",
              "Резерв: два нулевых байта."
            ],
            [
              "40 / 165",
              "Защищенный профиль: шифротекст; открытый профиль: звуковая или восстановительная часть."
            ],
            [
              "205 / 16",
              "Только защищенный профиль: полная метка MGM."
            ],
            [
              "221 / 64",
              "Только защищенный профиль: подпись r32 LE || s32 LE, как в общем профиле подписи."
            ]
          ]
        },
        {
          "type": "p",
          "text": "Длина UDP payload равна ровно 285 байтам при GA01 и 205 при GO01; иных длин, расширений или IP-фрагментации нет. MTU тракта ДОЛЖЕН вмещать датаграмму с IP/UDP и применяемой инкапсуляцией. Алгоритмы подписи, проверки точки, диапазонов r/s и хеша совпадают с основным документом. Подписывается ASCII OA-GROUP/1, затем нулевой байт, затем первые 221 байт датаграммы в исходном порядке. Подпись не включает сама себя. Перевод текста, пересериализация заголовка и разворот половин подписи запрещены."
        },
        {
          "type": "p",
          "text": "Для MGM применяются ключ потока, полный 16-байтовый тег, associated data — заголовок 40 байт, открытый текст — часть 165 байт. ICN равен конкатенации семи нулевых байтов, blockIndex:uint32 BE, shardIndex:uint8 и четырех нулевых байтов: 16 байт, старший бит нулевой. Срок одного потока не превышает 86400000 мс и 216000 блоков; дальнейшая речь требует новых streamId, ключа и подготовки. Уникальность ICN обеспечивается парой blockIndex/shardIndex при новом ключе каждого потока. Повторная передача допускает только побайтовую копию прежней датаграммы; повторное шифрование другого содержимого под тем же ICN запрещено."
        },
        {
          "type": "p",
          "text": "Порядок приема: проверка входного интерфейса и точного совпадения IPv4 sourceAddress, destination=groupAddress, UDP sourcePort и destinationPort с подготовленным шаблоном; затем длина и фиксированные поля, наличие действующего streamId, допустимое окно индексов и времени, затем проверка подписи разрешенного sourceKeyId, затем MGM, затем размещение части. До успешных криптографических проверок не обновляются окно повторов, таймер аудио и состояние воспроизведения. Знание группового ключа, IP-адреса, идентификатора потока или метки MGM не заменяет подпись. Открытый профиль сохраняет проверки формата и контекста, но не дает гарантии происхождения или целостности."
        }
      ]
    },
    {
      "id": "group-recovery",
      "title": "4. Блоки, восстановление потерь и очереди",
      "blocks": [
        {
          "type": "p",
          "text": "Кодек — GSM 06.10, RFC 3551, 8000 Гц, один канал. Блок охватывает 400 мс, то есть 3200 отсчетов и 20 кодековых кадров по 33 байта. Он разделяется на четыре последовательные части по 165 байт: пять кадров, 800 отсчетов и 100 мс. Часть 4 равна побайтовому XOR четырех открытых звуковых частей. Заголовки пяти частей имеют одинаковые streamId, blockIndex, capturedUtcMs, validSamples, clockErrorMs и flags, различается только shardIndex. Все пять частей отдельно защищаются."
        },
        {
          "type": "p",
          "text": "Без потерь воспроизводятся проверенные части 0..3. При потере ровно одной звуковой части и наличии трех других плюс части 4 восстанавливаются ее 165 байт XOR. Доказательство источника восстановленных байтов состоит из подписанных и прошедших MGM трех частей и восстановительной части того же блока; отдельная выдуманная подпись отсутствующей части не создается. Общие поля каждой новой проверенной части сверяются с уже принятой частью того же блока. Если все пять частей доступны до выдачи, до первого отсчета проверяется нулевой XOR. Несогласованные поля или ненулевой XOR означают ошибку источника: дальнейшая выдача прекращается, невоспроизведенные очереди очищаются, сеанс завершается failed/mediaIncomplete при подтвержденном off. Уже выданный звук не отменяется задним числом и сохраняется как состоявшееся исполнение с неполным завершением. Четыре целые проверенные звуковые части разрешено воспроизвести без ожидания восстановительной; ее отсутствие само по себе не является ошибкой. Если противоречие обнаружено после начала выдачи еще актуального блока, применяется то же прекращение остатка; опоздавшая часть уже закрытого блока отбрасывается без пересмотра законченной истории. Две потери этим кодом не восстанавливаются. Подпись общего ключа группы не используется: получатель не имеет ключа подписи источника."
        },
        {
          "type": "p",
          "text": "Источник накапливает один блок и отправляет пять частей равномерно с интервалом 80 мс, начиная не позднее 400 мс от первого захваченного отсчета. Захват следующего блока идет параллельно; в сети передается 12,5 датаграммы в секунду. Паузы микрофона кодируются как звук тишины, VAD/DTX запрещены. После захвата последнего частичного блока он отправляется по тому же порядку; END=1, validSamples=0..3200. Последний используемый GSM-кадр дополняется тишиной до 160 отсчетов, неиспользуемые кадры и части содержат нулевые байты и не декодируются. При нулевой речи передается конечный блок 0 с validSamples=0."
        },
        {
          "type": "p",
          "text": "Получатель хранит не более четырех блоков и 20 проверенных частей. Положение блока задается blockIndex*3200; заполнение начинается с блока 0. Начальный буфер 800 мс, максимум 1600 мс звука; отдельно считаются заголовки, защита, очереди и ЦАП. Окно принимает индексы от nextBlock до nextBlock+3. Поздний блок ниже nextBlock не воспроизводится; дальний индекс не сдвигает окно. Неверная подпись не резервирует индекс. Входная очередь до проверки — не более 5 датаграмм, приемник обеспечивает 25 проверок подписи в секунду с сохранением обслуживания session.stop. Это проверяемая квота, а не обещание неуязвимости к насыщению канала."
        },
        {
          "type": "p",
          "text": "Восстановление выполняется только до срока выдачи соответствующих отсчетов. Пропуск, который уже нельзя восстановить, маскируется тишиной и учитывается один раз в lostPackets и итоговой полноте; позднее прибытие не повторяет звучание. FEC-восстановленная часть учитывается как восстановленная и не является потерей звука. Повторных запросов NACK и подтверждений ACK на медиадатаграммы нет. Удаление невоспроизведенных данных из-за переполнения не скрывается: поток прекращается с mediaIncomplete, подтверждается off."
        }
      ]
    },
    {
      "id": "group-time",
      "title": "5. Время, окончание и отзыв",
      "blocks": [
        {
          "type": "p",
          "text": "Источник и УУ имеют UTC с подтвержденной верхней погрешностью не более 500 мс. Способ синхронизации фиксируется в паспорте и проверяется на фактическом канале; при tls источник времени ДОЛЖЕН быть аутентифицирован либо локально доверен. Просто полученная метка времени без учета задержки не доказывает эту погрешность. При отсутствии доказанной границы подготовка получает sourceUnavailable; утрата границы во время работы дает mediaClockFault. Возраст оценки часов вместе с дрейфом включается в публикуемую границу; неподтвержденное NTP-время не считается точным."
        },
        {
          "type": "p",
          "text": "Верхняя оценка возраста первого отсчета A=nowUtcMs+receiverErrorMs-capturedUtcMs+clockErrorMs. При A<-2 либо A>5000 блок отвергается; вычисления знаковые, без переполнения. При выдаче проверяется возраст именно выдаваемых отсчетов, с учетом их положения в блоке. Для последнего действительного отсчета части j=min(validSamples,800*(shardIndex+1))-1, где j — номер от начала блока. Его расчетное время захвата capturedUtcMs+j/8 мс; верхнее время фактической выдачи ЦАП обозначено emitUtcMs+receiverErrorMs. Проверяется -16 <= 8*(emitUtcMs+receiverErrorMs-capturedUtcMs+clockErrorMs)-j <= 40000; арифметика целочисленная знаковая без округления. Проверка распространяется на все отсчеты, включая первый; заранее рассчитанный срок учитывает аппаратную очередь, ресемплинг и дрейф. При нарушении отсчет не выдается, поток прекращается с mediaClockFault. Для части с нулем действительных отсчетов проверки выдачи нет. Для восстановленной части используется ее индекс0..3, а не индекс4 пакета XOR. При откате UTC либо скачке, нарушающем подтвержденную границу, сеанс останавливается с mediaClockFault; уже начатая остановка не отменяется исправлением часов. capturedUtcMs отражает захват звука, а не отправку очереди."
        },
        {
          "type": "p",
          "text": "END и audio.finish задают одну конечную границу: sampleCount=lastPacketIndex*3200+validSamples конечного блока. Для recording она совпадает с totalSamples подготовки. Противоречие дает conflict для команды либо mediaIncomplete для потока. Достаточно одного принятого объявления границы, однако успех полного воспроизведения требует всех отсчетов до нее, полученных либо восстановленных в срок. audio.finish повторяется индивидуально до подтверждения в общем порядке; потеря END не превращается в бесконечное ожидание. После первого принятого конца отводится не более 5000 мс на данные и выдачу хвоста, при сохранении maxDurationMs и предела возраста. Короткий полный поток начинает выдачу после получения всех его частей без ожидания 800 мс."
        },
        {
          "type": "p",
          "text": "Непрерывное отсутствие пригодного нового аудио 5000 мс вызывает sourceUnavailable для микрофона либо mediaIncomplete для записи. Подпись дубликата, FEC без новых пригодных отсчетов и служебный опрос не продлевают этот срок. Каждый УУ применяет свой lease и session.stop; пакет группы не возобновляет отозванный mediaId. session.stop очищает звуковые и аппаратные очереди в физических бюджетах основного документа, без дренирования."
        },
        {
          "type": "p",
          "text": "При исключении получателя или компрометации ключа источник прекращает прежний поток, создаются новые streamId и ключ, оставшиеся получатели проходят новую индивидуальную подготовку и session.start. Передача нового звука со старым ключом после отзыва запрещена. Отозванный публичный ключ источника прекращает прием немедленно. Утерянное подтверждение session.stop сохраняет неопределенный физический результат данного УУ; новое разрешение не отменяет блокировку его ресурса."
        }
      ]
    },
    {
      "id": "group-scale",
      "title": "6. Полоса и обратная связь",
      "blocks": [
        {
          "type": "p",
          "text": "Защищенная датаграмма требует 285 байт UDP payload, 8 UDP, 20 IPv4 и 38 Ethernet с преамбулой и межкадровым интервалом: 351 байт на тракте без VLAN и туннелей. Пять частей на 400 мс, включая восстановительную, дают 35,1 кбит/с. С резервом 30% — 45,63 кбит/с; с выделенными 12 кбит/с управления — 57,63 кбит/с. Открытый вариант: 271 байт, 27,1 кбит/с, 35,23 с резервом. Дополнительные заголовки и фактические потери включаются в расчет отдельно. Если они превышают остаток, данный поток на канале 64 кбит/с НЕ ДОПУСКАЕТСЯ."
        },
        {
          "type": "p",
          "text": "На общем участке маршрутизируемого группового дерева передается одна копия потока, на конечных ветвях — по одной. Число получателей не умножает исходящий медиатрафик источника, но влияет на состояние маршрутизаторов, команды подготовки и результаты. Протокол не обещает одновременно передать тысячи индивидуальных команд через один общий канал 64 кбит/с за срок одного кадра. Для таких групп рассчитываются общие участки, параллельная подготовка и емкость узлов распределения; сам по себе multicast не сокращает адресное управление."
        },
        {
          "type": "p",
          "text": "Начало потока не порождает массового группового запроса ответа. Готовность, ошибки, audio.state и итог читаются по индивидуальным управляющим соединениям с ограничением частоты у АРМ. На каждом узком участке резервируется полоса управления; опросы распределяются по времени с равномерным случайным смещением в интервале опроса, session.start/session.stop имеют приоритет. Итоговый отчет содержит также устройства, от которых результата нет. Ни размер группы, ни отсутствие сообщения об ошибке не считаются квитанцией успеха."
        }
      ]
    },
    {
      "id": "group-tests",
      "title": "7. Критерии приемки",
      "blocks": [
        {
          "type": "list",
          "ordered": true,
          "items": [
            "Проверяются точные байты заголовка, MGM, подписи и ICN, несовпадение streamId, измененная половина подписи, неверная точка ключа и повтор ICN с измененным содержимым. Получатель с групповым ключом и без приватного ключа источника не создает принимаемую поддельную речь.",
            "Для каждого из четырех положений теряется одна часть и восстанавливаются ровно исходные байты. Проверяются две потери, повреждение parity, повторы, перестановка, далекий индекс, позднее восстановление, конечный частичный и пустой блок.",
            "Проверяются пределы возраста и ошибки UTC, задержка очередей до ЦАП, потеря синхронизации, непрерывная тишина, потеря END и audio.finish, session.stop во время накопления и выдачи, отзыв одного получателя и источника.",
            "На фактическом тракте измеряются скорости обоих направлений, задержка, полоса управления, 25 проверок подписи в секунду и аппаратные сроки. Испытание группы проверяет отсутствие ответов на каждый медиапакет и индивидуальную полноту итогового отчета."
          ]
        }
      ]
    }
  ]
}
