Глава 6. Передача данных
6.1 Двунаправленный поток данных
Реализация DTP MUST поддерживать следующие два независимых направления потока данных:
| Направление | Название | Отправитель | Получатель |
|---|---|---|---|
| Терминал → Fay | Сбор данных | Slave | Master |
| Fay → Терминал | Инжекция данных | Master | Slave |
Оба направления MUST удовлетворять следующим требованиям:
- Использовать один и тот же формат LogicalFrame и процесс обработки.
- Поддерживать независимые пространства порядковых номеров.
- Поддерживать независимое состояние возобновления (кэш неподтверждённых Fragment, наибольший подтверждённый порядковый номер и т.д.).
- Не мешать друг другу: изменения состояния в одном направлении MUST NOT влиять на другое направление.
6.2 Процесс сбора данных (Терминал → Fay)
Сбор данных MUST следовать данному процессу:
6.2.1 Процесс отправителя (DTP_Slave)
- Получить данные, отправленные терминальным приложением.
- Построить Fragment:
a. Сгенерировать новый Fragment_ID (UUID v4).
b. Установить
agreementIdравным Agreement_ID текущего активного Agreement. c. УстановитьoriginTimestampравным UTC-метке времени в миллисекундах, когда данные фактически были произведены. MUST NOT использовать текущий момент. d. Прикрепить структурированные ContextMetadata (см. раздел 4.11). e. ПрикрепитьdagDependencies(MAY быть пустым массивом). - Проверить зависимости DAG:
a. Вызвать DAG Manager для проверки отсутствия цикла (см. раздел 6.7).
b. При обнаружении цикла MUST отклонить Fragment и вернуть ошибку
DAG_CYCLE_DETECTED(4001). - Построить LogicalFrame:
a. Установить
frameType = "data". b. Применить правила сжатия Agreement_ID (см. раздел 4.5). c. Назначить монотонно возрастающийsequenceNumber(направление сбора данных). d. Установить метаданные шифрования. - Зашифровать Payload: зашифровать поле
dataFragment с использованием ключа, заранее согласованного через CAP. - Сериализовать LogicalFrame.
- Вызвать Transport_Adapter для отправки бинарных данных.
- Добавить Fragment в кэш неподтверждённых (см. раздел 8.2).
6.2.2 Процесс получателя (DTP_Master)
- Получить бинарные данные, доставленные Transport_Adapter.
- Десериализовать в LogicalFrame. При сбое фрейм MUST быть отброшен, и MUST быть возвращена ошибка
FRAME_DESERIALIZATION_FAILED(1001). - Проверить версию протокола (см. главу 10).
- Распарсить Agreement_ID (применить правила сжатия из раздела 4.5). При связи с неизвестным Agreement фрейм MUST быть отброшен, и MUST быть возвращена ошибка
AGREEMENT_NOT_FOUND(3001). - Расшифровать Payload. При сбое фрейм MUST быть отброшен, и MUST быть возвращена ошибка
DECRYPTION_FAILED(2001). - Десериализовать в Fragment.
- Проверить зависимости DAG:
a. Если все целевые зависимости уже существуют, MUST принять и пометить как
accepted. b. Если некоторые целевые зависимости ещё не получены, MUST пометить какpendingи закэшировать (см. раздел 6.7). c. При обнаружении цикла MUST отклонить и вернуть ошибкуDAG_CYCLE_DETECTED(4001). - Обновить состояние приёма: установить sequenceNumber Fragment как наибольший полученный порядковый номер для данного направления (если он продвигается вперёд).
- Отправить подтверждение (см. главу 8).
- Сохранить Fragment в Personal Data Heap.
6.3 Процесс инжекции данных (Fay → Терминал)
Инжекция данных MUST следовать данному процессу:
6.3.1 Процесс отправителя (DTP_Master)
- Запросить Personal Data Heap и отфильтровать данные согласно
dataRangeAgreement, чтобы получить минимизированный набор данных. - Построить Fragment (так же, как в шагах 2-3 раздела 6.2.1).
- Построить LogicalFrame, зашифровать Payload, сериализовать и отправить (так же, как в шагах 4-8 раздела 6.2.1), но использовать пространство порядковых номеров направления инжекции данных.
6.3.2 Процесс получателя (DTP_Slave)
- Десериализовать, проверить версию, распарсить Agreement_ID, расшифровать, десериализовать Fragment, проверить DAG (так же, как в шагах 1-7 раздела 6.2.2).
- Обновить состояние приёма и отправить подтверждение (так же, как в шагах 8-9 раздела 6.2.2).
- Передать 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 удовлетворять следующим требованиям:
- MAY не использовать сжатие (т.е. все Fragment несут полный Agreement_ID).
- Если выбрано сжатие, MUST строго следовать разделу 4.5.
- Получатель MUST поддерживать оба режима — сжатый и несжатый.
- Контекстный Agreement_ID MUST сбрасываться в null после приостановки и возобновления Session (т.е. первый Fragment после возобновления MUST содержать полный ID).
6.5 Управление порядковыми номерами
6.5.1 Монотонное возрастание
sequenceNumber каждого Fragment MUST удовлетворять следующим требованиям:
- Монотонно возрастать в рамках одной Session.
- SHOULD быть строго непрерывным (т.е. инкрементироваться на 1 каждый раз).
- 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 удовлетворять следующим требованиям:
- Порядковый номер MUST сбрасываться при начале новой Session (SHOULD начинаться с 0 или 1).
- При восстановлении Session из
Suspendedпорядковый номер MUST сохранять своё значение, бывшее перед приостановкой, и MUST NOT сбрасываться. - Если порядковый номер приближается к определяемому реализацией максимальному значению, отправитель MUST активно установить новую Session во избежание переполнения.
6.6 Сохранение Origin Timestamp
Реализация MUST обеспечивать неизменность Origin_Timestamp при передаче:
- Отправитель MUST записывать момент фактического производства данных в качестве Origin_Timestamp.
- Сериализация, шифрование, передача, расшифровка и десериализация MUST NOT изменять Origin_Timestamp.
- Получатель MUST NOT изменять полученный Origin_Timestamp.
- При сохранении получатель MUST сохранять Origin_Timestamp.
Реализация SHOULD поддерживать независимое поле «временная метка передачи» вне заголовка фрейма (определяемое реализацией), но MUST NOT смешивать его с Origin_Timestamp.
6.7 Обработка зависимостей DAG
6.7.1 Добавление Fragment в DAG
Когда получатель получает Fragment, он MUST обработать зависимости через DAG Manager:
- Извлечь
dagDependenciesFragment. - Для каждой зависимости: a. Проверить, находится ли целевой Fragment_ID уже в DAG. b. Проверить, не приведёт ли добавление этого ребра к образованию цикла.
- Вернуть один из следующих трёх результатов на основе исхода проверки:
| Результат | Значение | Последующее действие |
|---|---|---|
accepted | Все зависимости разрешены; цикла нет | MUST добавить Fragment в DAG |
pending | Некоторые зависимости не разрешены (целевые Fragment не получены); цикла нет | MUST закэшировать Fragment и ожидать разрешения зависимостей |
rejected | Обнаружен цикл | MUST отклонить Fragment и вернуть ошибку DAG_CYCLE_DETECTED (4001) |
6.7.2 Отложенное разрешение
Когда Fragment находится в состоянии pending:
- Реализация MUST продолжать ожидать разрешения зависимостей в кэше для этого Fragment.
- При поступлении целевых Fragment зависимостей MUST повторно оценить все связанные Fragment в состоянии
pendingв кэше. - MAY установить максимальное время кэширования (определяемое реализацией). По истечении тайм-аута SHOULD вернуть ошибку
DAG_DEPENDENCY_UNRESOLVED(4002) и отбросить.
6.7.3 Алгоритм обнаружения циклов
Реализация MUST обнаруживать циклы перед добавлением нового Fragment. Алгоритм обнаружения SHOULD использовать DFS (поиск в глубину) или алгоритм Тарьяна.
Конкретно: от каждой целевой зависимости нового Fragment выполнить DFS. Если сам новый Fragment достижим из целевой зависимости, образуется цикл.
6.8 Чередующаяся передача нескольких Agreement
При сосуществовании нескольких активных Agreement отправитель MUST:
- Связывать каждый Fragment с правильным Agreement через Agreement_ID в заголовке фрейма.
- При переключении Agreement (т.е. когда следующий Fragment принадлежит другому Agreement) MUST включать полный Agreement_ID в этот Fragment (т.е. сжатие через null использовать нельзя).
- SHOULD планировать порядок отправки в соответствии с
priorityкаждого Agreement.
