Глава 6. Передача данных

6.1 Двунаправленный поток данных

Реализация DTP MUST поддерживать следующие два независимых направления потока данных:

НаправлениеНазваниеОтправительПолучатель
Терминал → FayСбор данныхSlaveMaster
Fay → ТерминалИнжекция данныхMasterSlave

Оба направления MUST удовлетворять следующим требованиям:

  1. Использовать один и тот же формат LogicalFrame и процесс обработки.
  2. Поддерживать независимые пространства порядковых номеров.
  3. Поддерживать независимое состояние возобновления (кэш неподтверждённых Fragment, наибольший подтверждённый порядковый номер и т.д.).
  4. Не мешать друг другу: изменения состояния в одном направлении MUST NOT влиять на другое направление.

6.2 Процесс сбора данных (Терминал → Fay)

Сбор данных MUST следовать данному процессу:

6.2.1 Процесс отправителя (DTP_Slave)

  1. Получить данные, отправленные терминальным приложением.
  2. Построить Fragment: a. Сгенерировать новый Fragment_ID (UUID v4). b. Установить agreementId равным Agreement_ID текущего активного Agreement. c. Установить originTimestamp равным UTC-метке времени в миллисекундах, когда данные фактически были произведены. MUST NOT использовать текущий момент. d. Прикрепить структурированные ContextMetadata (см. раздел 4.11). e. Прикрепить dagDependencies (MAY быть пустым массивом).
  3. Проверить зависимости DAG: a. Вызвать DAG Manager для проверки отсутствия цикла (см. раздел 6.7). b. При обнаружении цикла MUST отклонить Fragment и вернуть ошибку DAG_CYCLE_DETECTED (4001).
  4. Построить LogicalFrame: a. Установить frameType = "data". b. Применить правила сжатия Agreement_ID (см. раздел 4.5). c. Назначить монотонно возрастающий sequenceNumber (направление сбора данных). d. Установить метаданные шифрования.
  5. Зашифровать Payload: зашифровать поле data Fragment с использованием ключа, заранее согласованного через CAP.
  6. Сериализовать LogicalFrame.
  7. Вызвать Transport_Adapter для отправки бинарных данных.
  8. Добавить Fragment в кэш неподтверждённых (см. раздел 8.2).

6.2.2 Процесс получателя (DTP_Master)

  1. Получить бинарные данные, доставленные Transport_Adapter.
  2. Десериализовать в LogicalFrame. При сбое фрейм MUST быть отброшен, и MUST быть возвращена ошибка FRAME_DESERIALIZATION_FAILED (1001).
  3. Проверить версию протокола (см. главу 10).
  4. Распарсить Agreement_ID (применить правила сжатия из раздела 4.5). При связи с неизвестным Agreement фрейм MUST быть отброшен, и MUST быть возвращена ошибка AGREEMENT_NOT_FOUND (3001).
  5. Расшифровать Payload. При сбое фрейм MUST быть отброшен, и MUST быть возвращена ошибка DECRYPTION_FAILED (2001).
  6. Десериализовать в Fragment.
  7. Проверить зависимости DAG: a. Если все целевые зависимости уже существуют, MUST принять и пометить как accepted. b. Если некоторые целевые зависимости ещё не получены, MUST пометить как pending и закэшировать (см. раздел 6.7). c. При обнаружении цикла MUST отклонить и вернуть ошибку DAG_CYCLE_DETECTED (4001).
  8. Обновить состояние приёма: установить sequenceNumber Fragment как наибольший полученный порядковый номер для данного направления (если он продвигается вперёд).
  9. Отправить подтверждение (см. главу 8).
  10. Сохранить Fragment в Personal Data Heap.

6.3 Процесс инжекции данных (Fay → Терминал)

Инжекция данных MUST следовать данному процессу:

6.3.1 Процесс отправителя (DTP_Master)

  1. Запросить Personal Data Heap и отфильтровать данные согласно dataRange Agreement, чтобы получить минимизированный набор данных.
  2. Построить Fragment (так же, как в шагах 2-3 раздела 6.2.1).
  3. Построить LogicalFrame, зашифровать Payload, сериализовать и отправить (так же, как в шагах 4-8 раздела 6.2.1), но использовать пространство порядковых номеров направления инжекции данных.

6.3.2 Процесс получателя (DTP_Slave)

  1. Десериализовать, проверить версию, распарсить Agreement_ID, расшифровать, десериализовать Fragment, проверить DAG (так же, как в шагах 1-7 раздела 6.2.2).
  2. Обновить состояние приёма и отправить подтверждение (так же, как в шагах 8-9 раздела 6.2.2).
  3. Передать Fragment терминальному приложению.

6.4 Сжатие Agreement_ID при передаче

Реализация MUST строго соблюдать правила сжатия Agreement_ID, определённые в разделе 4.5.

6.4.1 Пример сжатия

Ниже приведена последовательность передачи, соответствующая спецификации:

Fragment 1: agreementId = "abc-123"   (new Agreement, full ID)
Fragment 2: agreementId = null        (reuse "abc-123")
Fragment 3: agreementId = null        (reuse "abc-123")
Fragment 4: agreementId = "def-456"   (switch to a new Agreement, full ID)
Fragment 5: agreementId = null        (reuse "def-456")

6.4.2 Ограничения сжатия

Реализация MUST удовлетворять следующим требованиям:

  1. MAY не использовать сжатие (т.е. все Fragment несут полный Agreement_ID).
  2. Если выбрано сжатие, MUST строго следовать разделу 4.5.
  3. Получатель MUST поддерживать оба режима — сжатый и несжатый.
  4. Контекстный Agreement_ID MUST сбрасываться в null после приостановки и возобновления Session (т.е. первый Fragment после возобновления MUST содержать полный ID).

6.5 Управление порядковыми номерами

6.5.1 Монотонное возрастание

sequenceNumber каждого Fragment MUST удовлетворять следующим требованиям:

  1. Монотонно возрастать в рамках одной Session.
  2. SHOULD быть строго непрерывным (т.е. инкрементироваться на 1 каждый раз).
  3. MUST NOT повторяться или возвращаться назад.

6.5.2 Независимость по направлениям

Направление сбора данных и направление инжекции данных MUST поддерживать независимые пространства порядковых номеров:

Data collection direction (collection):  seq 1, 2, 3, 4, 5, ...
Data injection direction (injection):    seq 1, 2, 3, 4, 5, ...

Реализация MUST NOT разделять пространство порядковых номеров между двумя направлениями.

6.5.3 Перезапуск и сессии

Порядковые номера MUST удовлетворять следующим требованиям:

  1. Порядковый номер MUST сбрасываться при начале новой Session (SHOULD начинаться с 0 или 1).
  2. При восстановлении Session из Suspended порядковый номер MUST сохранять своё значение, бывшее перед приостановкой, и MUST NOT сбрасываться.
  3. Если порядковый номер приближается к определяемому реализацией максимальному значению, отправитель MUST активно установить новую Session во избежание переполнения.

6.6 Сохранение Origin Timestamp

Реализация MUST обеспечивать неизменность Origin_Timestamp при передаче:

  1. Отправитель MUST записывать момент фактического производства данных в качестве Origin_Timestamp.
  2. Сериализация, шифрование, передача, расшифровка и десериализация MUST NOT изменять Origin_Timestamp.
  3. Получатель MUST NOT изменять полученный Origin_Timestamp.
  4. При сохранении получатель MUST сохранять Origin_Timestamp.

Реализация SHOULD поддерживать независимое поле «временная метка передачи» вне заголовка фрейма (определяемое реализацией), но MUST NOT смешивать его с Origin_Timestamp.

6.7 Обработка зависимостей DAG

6.7.1 Добавление Fragment в DAG

Когда получатель получает Fragment, он MUST обработать зависимости через DAG Manager:

  1. Извлечь dagDependencies Fragment.
  2. Для каждой зависимости: a. Проверить, находится ли целевой Fragment_ID уже в DAG. b. Проверить, не приведёт ли добавление этого ребра к образованию цикла.
  3. Вернуть один из следующих трёх результатов на основе исхода проверки:
РезультатЗначениеПоследующее действие
acceptedВсе зависимости разрешены; цикла нетMUST добавить Fragment в DAG
pendingНекоторые зависимости не разрешены (целевые Fragment не получены); цикла нетMUST закэшировать Fragment и ожидать разрешения зависимостей
rejectedОбнаружен циклMUST отклонить Fragment и вернуть ошибку DAG_CYCLE_DETECTED (4001)

6.7.2 Отложенное разрешение

Когда Fragment находится в состоянии pending:

  1. Реализация MUST продолжать ожидать разрешения зависимостей в кэше для этого Fragment.
  2. При поступлении целевых Fragment зависимостей MUST повторно оценить все связанные Fragment в состоянии pending в кэше.
  3. MAY установить максимальное время кэширования (определяемое реализацией). По истечении тайм-аута SHOULD вернуть ошибку DAG_DEPENDENCY_UNRESOLVED (4002) и отбросить.

6.7.3 Алгоритм обнаружения циклов

Реализация MUST обнаруживать циклы перед добавлением нового Fragment. Алгоритм обнаружения SHOULD использовать DFS (поиск в глубину) или алгоритм Тарьяна.

Конкретно: от каждой целевой зависимости нового Fragment выполнить DFS. Если сам новый Fragment достижим из целевой зависимости, образуется цикл.

6.8 Чередующаяся передача нескольких Agreement

При сосуществовании нескольких активных Agreement отправитель MUST:

  1. Связывать каждый Fragment с правильным Agreement через Agreement_ID в заголовке фрейма.
  2. При переключении Agreement (т.е. когда следующий Fragment принадлежит другому Agreement) MUST включать полный Agreement_ID в этот Fragment (т.е. сжатие через null использовать нельзя).
  3. SHOULD планировать порядок отправки в соответствии с priority каждого Agreement.