제6장 데이터 전송

6.1 양방향 데이터 흐름

DTP 구현은 다음 두 가지 독립적인 데이터 흐름 방향을 지원해야 한다:

방향이름송신측수신측
Terminal → Fay데이터 수집(Collection)SlaveMaster
Fay → Terminal데이터 주입(Injection)MasterSlave

두 방향은 다음을 충족해야 한다:

  1. 동일한 LogicalFrame 형식과 처리 흐름을 사용한다.
  2. 독립적인 시퀀스 번호 공간을 유지한다.
  3. 독립적인 재개 상태(미확인 Fragment 캐시, 최고 확인 시퀀스 번호 등)를 유지한다.
  4. 상호 간섭하지 않는다: 한 방향의 상태 변화가 다른 방향에 영향을 미쳐서는 안 된다.

6.2 데이터 수집 흐름(Terminal → Fay)

데이터 수집은 다음 흐름을 따라야 한다:

6.2.1 송신측(DTP_Slave) 흐름

  1. 단말 애플리케이션이 제출한 데이터를 수신한다.
  2. Fragment 구성: a. 새로운 Fragment_ID(UUID v4)를 생성한다. b. agreementId를 현재 active 약정의 Agreement_ID로 설정한다. c. originTimestamp를 데이터가 실제로 생성된 시점의 UTC 밀리초 타임스탬프로 설정한다. 현재 시점을 사용해서는 안 된다. d. 구조화된 ContextMetadata를 첨부한다(제4.11절 참조). e. dagDependencies를 첨부한다(빈 배열일 수 있음).
  3. DAG 의존을 검증한다: a. DAG Manager를 호출하여 순환이 형성되지 않음을 검증한다(제6.7절 참조). b. 순환이 검출되면 해당 Fragment를 거부하고 DAG_CYCLE_DETECTED 오류(4001)를 반환해야 한다.
  4. LogicalFrame 구성: a. frameType = "data"로 설정한다. b. Agreement_ID 압축 규칙을 적용한다(제4.5절 참조). c. 단조 증가하는 sequenceNumber(데이터 수집 방향)를 할당한다. d. 암호화 메타데이터를 설정한다.
  5. Payload 암호화: CAP에서 사전 협상한 키로 Fragment의 data 필드를 암호화한다.
  6. LogicalFrame을 직렬화한다.
  7. Transport_Adapter를 호출하여 바이너리 데이터를 전송한다.
  8. Fragment를 미확인 캐시에 추가한다(제8.2절 참조).

6.2.2 수신측(DTP_Master) 흐름

  1. Transport_Adapter가 전달한 바이너리 데이터를 수신한다.
  2. LogicalFrame으로 역직렬화한다. 실패 시 프레임을 폐기하고 FRAME_DESERIALIZATION_FAILED 오류(1001)를 반환해야 한다.
  3. 프로토콜 버전을 검증한다(제10장 참조).
  4. Agreement_ID를 해석한다(제4.5절의 압축 규칙 적용). 알 수 없는 약정에 연결될 경우, 프레임을 폐기하고 AGREEMENT_NOT_FOUND 오류(3001)를 반환해야 한다.
  5. Payload를 복호화한다. 실패 시 프레임을 폐기하고 DECRYPTION_FAILED 오류(2001)를 반환해야 한다.
  6. Fragment로 역직렬화한다.
  7. DAG 의존을 검증한다: a. 모든 의존 대상 Fragment가 이미 존재하면 수락하고 accepted로 표시해야 한다. b. 일부 의존 대상 Fragment가 아직 도착하지 않았으면 pending으로 표시하고 캐시해야 한다(제6.7절 참조). c. 순환이 검출되면 거부하고 DAG_CYCLE_DETECTED 오류(4001)를 반환해야 한다.
  8. 수신 상태 갱신: 해당 Fragment의 sequenceNumber가 증가했다면 해당 방향의 최고 수신 시퀀스 번호로 설정한다.
  9. 확인응답을 전송한다(제8장 참조).
  10. Fragment를 Personal Data Heap에 영속화한다.

6.3 데이터 주입 흐름(Fay → Terminal)

데이터 주입은 다음 흐름을 따라야 한다:

6.3.1 송신측(DTP_Master) 흐름

  1. Personal Data Heap에서 약정 dataRange로 필터링하여 데이터를 조회하고 최소화 데이터셋을 생성한다.
  2. Fragment를 구성한다(6.2.1의 단계 2-3과 동일).
  3. LogicalFrame 구성, Payload 암호화, 직렬화, 전송(6.2.1의 단계 4-8과 동일)하지만 데이터 주입 방향의 시퀀스 번호 공간을 사용한다.

6.3.2 수신측(DTP_Slave) 흐름

  1. 역직렬화, 버전 검증, Agreement_ID 해석, 복호화, Fragment 역직렬화, DAG 검증(6.2.2의 단계 1-7과 동일).
  2. 수신 상태 갱신, 확인응답 전송(6.2.2의 단계 8-9와 동일).
  3. 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 압축 제약

구현은 다음을 충족해야 한다:

  1. 압축을 하지 않을 수 있다(즉, 모든 Fragment가 완전한 Agreement_ID를 포함).
  2. 압축을 선택한 경우 4.5절 규칙을 엄격히 준수해야 한다.
  3. 수신측은 압축과 비압축 두 모드를 모두 지원해야 한다.
  4. 컨텍스트 Agreement_ID는 세션 일시 중지 후 회복되면 null로 재설정되어야 한다(즉, 회복 후 첫 번째 Fragment는 완전한 ID를 포함해야 한다).

6.5 시퀀스 번호 관리

6.5.1 단조 증가

각 Fragment의 sequenceNumber는 다음을 충족해야 한다:

  1. 단일 세션 내에서 단조 증가한다.
  2. 엄격하게 연속해야 한다(즉, 매번 1씩 증가)(권장).
  3. 중복되거나 역행해서는 안 된다.

6.5.2 양방향 독립성

데이터 수집 방향과 데이터 주입 방향은 독립적인 시퀀스 번호 공간을 유지해야 한다:

데이터 수집 방향 (collection):  seq 1, 2, 3, 4, 5, ...
데이터 주입 방향 (injection):   seq 1, 2, 3, 4, 5, ...

구현은 두 방향 사이에서 시퀀스 번호 공간을 공유해서는 안 된다.

6.5.3 재시작과 세션

시퀀스 번호는 다음을 충족해야 한다:

  1. 새 세션이 시작될 때 시퀀스 번호는 재설정되어야 한다(0 또는 1부터 시작해야 한다, 권장).
  2. 세션이 Suspended에서 회복될 때 시퀀스 번호는 일시 중지 전 값을 유지해야 하며, 재설정되어서는 안 된다.
  3. 시퀀스 번호가 구현 정의 최댓값에 근접하면 송신측은 새 세션을 능동적으로 생성하여 오버플로를 방지해야 한다.

6.6 원본 타임스탬프 보전

구현은 Origin_Timestamp가 전송 과정에서 변경되지 않도록 보장해야 한다:

  1. 송신측은 데이터가 실제로 생성된 시점을 Origin_Timestamp로 기록해야 한다.
  2. 직렬화, 암호화, 전송, 복호화, 역직렬화는 Origin_Timestamp를 수정해서는 안 된다.
  3. 수신측은 수신한 Origin_Timestamp를 수정해서는 안 된다.
  4. 수신측이 영속화할 때 Origin_Timestamp를 보존해야 한다.

구현은 프레임 헤더와 별개로 독립적인 "전송 타임스탬프" 필드(구현 정의)를 유지할 수 있으나(권장), Origin_Timestamp와 혼동해서는 안 된다.

6.7 DAG 의존 처리

6.7.1 Fragment를 DAG에 추가

수신측이 Fragment를 수신하면 DAG Manager를 통해 의존을 처리해야 한다:

  1. Fragment의 dagDependencies를 추출한다.
  2. 각 의존에 대해: a. 대상 Fragment_ID가 이미 DAG에 있는지 확인한다. b. 해당 엣지를 추가하면 순환이 형성되는지 확인한다.
  3. 검사 결과에 따라 다음 세 결과 중 하나를 반환한다:
결과의미후속 동작
accepted모든 의존이 해석됨, 순환 없음Fragment를 DAG에 추가해야 한다
pending일부 의존이 미해석(대상 Fragment 미도착), 순환 없음해당 Fragment를 캐시하고 의존 해석을 대기해야 한다
rejected순환 검출Fragment를 거부하고 DAG_CYCLE_DETECTED 오류(4001)를 반환해야 한다

6.7.2 지연 해석

Fragment가 pending 상태에 있을 때:

  1. 구현은 해당 Fragment 캐시에서 의존을 지속적으로 대기해야 한다.
  2. 의존 대상 Fragment가 도착하면 캐시 내 모든 관련 pending Fragment를 재평가해야 한다.
  3. 최대 캐시 시간을 설정할 수 있다(구현 정의). 타임아웃 후 DAG_DEPENDENCY_UNRESOLVED 오류(4002)를 반환하고 폐기해야 한다(권장).

6.7.3 순환 검출 알고리즘

구현은 새 Fragment를 추가하기 전에 순환을 검출해야 한다. 검출 알고리즘은 DFS(깊이 우선 탐색) 또는 Tarjan 알고리즘을 사용해야 한다(권장).

구체적 알고리즘: 새 Fragment의 각 의존 대상에서 출발하여 DFS를 수행한다. 의존 대상에서 새 Fragment 자신에게 도달할 수 있으면 순환이 형성된 것이다.

6.8 다중 약정 교차 전송

여러 active 약정이 공존할 때 송신측은 다음을 수행해야 한다:

  1. 각 Fragment의 프레임 헤더에서 Agreement_ID를 통해 올바른 약정에 연결한다.
  2. 약정을 전환할 때(즉, 다음 Fragment가 다른 약정에 속할 때), 해당 Fragment는 완전한 Agreement_ID를 포함해야 한다(즉, null 압축을 사용할 수 없음).
  3. 약정의 priority에 따라 전송 순서를 스케줄링해야 한다(권장).