제6장 데이터 전송
6.1 양방향 데이터 흐름
DTP 구현은 다음 두 가지 독립적인 데이터 흐름 방향을 지원해야 한다:
| 방향 | 이름 | 송신측 | 수신측 |
|---|---|---|---|
| Terminal → Fay | 데이터 수집(Collection) | Slave | Master |
| Fay → Terminal | 데이터 주입(Injection) | Master | Slave |
두 방향은 다음을 충족해야 한다:
- 동일한 LogicalFrame 형식과 처리 흐름을 사용한다.
- 독립적인 시퀀스 번호 공간을 유지한다.
- 독립적인 재개 상태(미확인 Fragment 캐시, 최고 확인 시퀀스 번호 등)를 유지한다.
- 상호 간섭하지 않는다: 한 방향의 상태 변화가 다른 방향에 영향을 미쳐서는 안 된다.
6.2 데이터 수집 흐름(Terminal → Fay)
데이터 수집은 다음 흐름을 따라야 한다:
6.2.1 송신측(DTP_Slave) 흐름
- 단말 애플리케이션이 제출한 데이터를 수신한다.
- Fragment 구성:
a. 새로운 Fragment_ID(UUID v4)를 생성한다.
b.
agreementId를 현재 active 약정의 Agreement_ID로 설정한다. c.originTimestamp를 데이터가 실제로 생성된 시점의 UTC 밀리초 타임스탬프로 설정한다. 현재 시점을 사용해서는 안 된다. d. 구조화된 ContextMetadata를 첨부한다(제4.11절 참조). e.dagDependencies를 첨부한다(빈 배열일 수 있음). - DAG 의존을 검증한다:
a. DAG Manager를 호출하여 순환이 형성되지 않음을 검증한다(제6.7절 참조).
b. 순환이 검출되면 해당 Fragment를 거부하고
DAG_CYCLE_DETECTED오류(4001)를 반환해야 한다. - LogicalFrame 구성:
a.
frameType = "data"로 설정한다. b. Agreement_ID 압축 규칙을 적용한다(제4.5절 참조). c. 단조 증가하는sequenceNumber(데이터 수집 방향)를 할당한다. d. 암호화 메타데이터를 설정한다. - Payload 암호화: CAP에서 사전 협상한 키로 Fragment의
data필드를 암호화한다. - LogicalFrame을 직렬화한다.
- Transport_Adapter를 호출하여 바이너리 데이터를 전송한다.
- Fragment를 미확인 캐시에 추가한다(제8.2절 참조).
6.2.2 수신측(DTP_Master) 흐름
- Transport_Adapter가 전달한 바이너리 데이터를 수신한다.
- LogicalFrame으로 역직렬화한다. 실패 시 프레임을 폐기하고
FRAME_DESERIALIZATION_FAILED오류(1001)를 반환해야 한다. - 프로토콜 버전을 검증한다(제10장 참조).
- Agreement_ID를 해석한다(제4.5절의 압축 규칙 적용). 알 수 없는 약정에 연결될 경우, 프레임을 폐기하고
AGREEMENT_NOT_FOUND오류(3001)를 반환해야 한다. - Payload를 복호화한다. 실패 시 프레임을 폐기하고
DECRYPTION_FAILED오류(2001)를 반환해야 한다. - Fragment로 역직렬화한다.
- DAG 의존을 검증한다:
a. 모든 의존 대상 Fragment가 이미 존재하면 수락하고
accepted로 표시해야 한다. b. 일부 의존 대상 Fragment가 아직 도착하지 않았으면pending으로 표시하고 캐시해야 한다(제6.7절 참조). c. 순환이 검출되면 거부하고DAG_CYCLE_DETECTED오류(4001)를 반환해야 한다. - 수신 상태 갱신: 해당 Fragment의 sequenceNumber가 증가했다면 해당 방향의 최고 수신 시퀀스 번호로 설정한다.
- 확인응답을 전송한다(제8장 참조).
- Fragment를 Personal Data Heap에 영속화한다.
6.3 데이터 주입 흐름(Fay → Terminal)
데이터 주입은 다음 흐름을 따라야 한다:
6.3.1 송신측(DTP_Master) 흐름
- Personal Data Heap에서 약정
dataRange로 필터링하여 데이터를 조회하고 최소화 데이터셋을 생성한다. - Fragment를 구성한다(6.2.1의 단계 2-3과 동일).
- LogicalFrame 구성, Payload 암호화, 직렬화, 전송(6.2.1의 단계 4-8과 동일)하지만 데이터 주입 방향의 시퀀스 번호 공간을 사용한다.
6.3.2 수신측(DTP_Slave) 흐름
- 역직렬화, 버전 검증, Agreement_ID 해석, 복호화, Fragment 역직렬화, DAG 검증(6.2.2의 단계 1-7과 동일).
- 수신 상태 갱신, 확인응답 전송(6.2.2의 단계 8-9와 동일).
- Fragment를 단말 애플리케이션에 전달한다.
6.4 Agreement_ID 압축 전송
구현은 제4.5절에서 정의한 Agreement_ID 압축 규칙을 엄격히 준수해야 한다.
6.4.1 압축 예시
다음은 사양에 부합하는 전송 시퀀스이다:
Fragment 1: agreementId = "abc-123" (새 약정, 완전한 ID)
Fragment 2: agreementId = null ("abc-123" 사용)
Fragment 3: agreementId = null ("abc-123" 사용)
Fragment 4: agreementId = "def-456" (새 약정으로 전환, 완전한 ID)
Fragment 5: agreementId = null ("def-456" 사용)
6.4.2 압축 제약
구현은 다음을 충족해야 한다:
- 압축을 하지 않을 수 있다(즉, 모든 Fragment가 완전한 Agreement_ID를 포함).
- 압축을 선택한 경우 4.5절 규칙을 엄격히 준수해야 한다.
- 수신측은 압축과 비압축 두 모드를 모두 지원해야 한다.
- 컨텍스트 Agreement_ID는 세션 일시 중지 후 회복되면 null로 재설정되어야 한다(즉, 회복 후 첫 번째 Fragment는 완전한 ID를 포함해야 한다).
6.5 시퀀스 번호 관리
6.5.1 단조 증가
각 Fragment의 sequenceNumber는 다음을 충족해야 한다:
- 단일 세션 내에서 단조 증가한다.
- 엄격하게 연속해야 한다(즉, 매번 1씩 증가)(권장).
- 중복되거나 역행해서는 안 된다.
6.5.2 양방향 독립성
데이터 수집 방향과 데이터 주입 방향은 독립적인 시퀀스 번호 공간을 유지해야 한다:
데이터 수집 방향 (collection): seq 1, 2, 3, 4, 5, ...
데이터 주입 방향 (injection): seq 1, 2, 3, 4, 5, ...
구현은 두 방향 사이에서 시퀀스 번호 공간을 공유해서는 안 된다.
6.5.3 재시작과 세션
시퀀스 번호는 다음을 충족해야 한다:
- 새 세션이 시작될 때 시퀀스 번호는 재설정되어야 한다(0 또는 1부터 시작해야 한다, 권장).
- 세션이
Suspended에서 회복될 때 시퀀스 번호는 일시 중지 전 값을 유지해야 하며, 재설정되어서는 안 된다. - 시퀀스 번호가 구현 정의 최댓값에 근접하면 송신측은 새 세션을 능동적으로 생성하여 오버플로를 방지해야 한다.
6.6 원본 타임스탬프 보전
구현은 Origin_Timestamp가 전송 과정에서 변경되지 않도록 보장해야 한다:
- 송신측은 데이터가 실제로 생성된 시점을 Origin_Timestamp로 기록해야 한다.
- 직렬화, 암호화, 전송, 복호화, 역직렬화는 Origin_Timestamp를 수정해서는 안 된다.
- 수신측은 수신한 Origin_Timestamp를 수정해서는 안 된다.
- 수신측이 영속화할 때 Origin_Timestamp를 보존해야 한다.
구현은 프레임 헤더와 별개로 독립적인 "전송 타임스탬프" 필드(구현 정의)를 유지할 수 있으나(권장), Origin_Timestamp와 혼동해서는 안 된다.
6.7 DAG 의존 처리
6.7.1 Fragment를 DAG에 추가
수신측이 Fragment를 수신하면 DAG Manager를 통해 의존을 처리해야 한다:
- Fragment의
dagDependencies를 추출한다. - 각 의존에 대해: a. 대상 Fragment_ID가 이미 DAG에 있는지 확인한다. b. 해당 엣지를 추가하면 순환이 형성되는지 확인한다.
- 검사 결과에 따라 다음 세 결과 중 하나를 반환한다:
| 결과 | 의미 | 후속 동작 |
|---|---|---|
accepted | 모든 의존이 해석됨, 순환 없음 | Fragment를 DAG에 추가해야 한다 |
pending | 일부 의존이 미해석(대상 Fragment 미도착), 순환 없음 | 해당 Fragment를 캐시하고 의존 해석을 대기해야 한다 |
rejected | 순환 검출 | Fragment를 거부하고 DAG_CYCLE_DETECTED 오류(4001)를 반환해야 한다 |
6.7.2 지연 해석
Fragment가 pending 상태에 있을 때:
- 구현은 해당 Fragment 캐시에서 의존을 지속적으로 대기해야 한다.
- 의존 대상 Fragment가 도착하면 캐시 내 모든 관련
pendingFragment를 재평가해야 한다. - 최대 캐시 시간을 설정할 수 있다(구현 정의). 타임아웃 후
DAG_DEPENDENCY_UNRESOLVED오류(4002)를 반환하고 폐기해야 한다(권장).
6.7.3 순환 검출 알고리즘
구현은 새 Fragment를 추가하기 전에 순환을 검출해야 한다. 검출 알고리즘은 DFS(깊이 우선 탐색) 또는 Tarjan 알고리즘을 사용해야 한다(권장).
구체적 알고리즘: 새 Fragment의 각 의존 대상에서 출발하여 DFS를 수행한다. 의존 대상에서 새 Fragment 자신에게 도달할 수 있으면 순환이 형성된 것이다.
6.8 다중 약정 교차 전송
여러 active 약정이 공존할 때 송신측은 다음을 수행해야 한다:
- 각 Fragment의 프레임 헤더에서 Agreement_ID를 통해 올바른 약정에 연결한다.
- 약정을 전환할 때(즉, 다음 Fragment가 다른 약정에 속할 때), 해당 Fragment는 완전한 Agreement_ID를 포함해야 한다(즉, null 압축을 사용할 수 없음).
- 약정의
priority에 따라 전송 순서를 스케줄링해야 한다(권장).
