제4장 논리 프레임 구조

4.1 전체 구조

LogicalFrame은 다음 두 부분으로 구성되어야 한다:

+----------------+
|     Header     |   (평문)
+----------------+
|     Payload    |   (암호화)
+----------------+

LogicalFrame의 형식적 정의:

interface LogicalFrame {
  header: FrameHeader;
  payload: Uint8Array;  // 암호화
}

4.2 프레임 헤더(FrameHeader)

프레임 헤더는 다음 필드를 포함해야 하며, 아래 표의 순서가 규범적 순서이다:

필드타입필수암호화설명
protocolVersionProtocolVersion필수아니오프로토콜 버전 번호
frameTypeFrameType필수아니오프레임 유형
fragmentIdFragmentID필수아니오Fragment 고유 식별자
agreementIdAgreementID | null필수아니오약정 ID(null일 수 있음)
originTimestampOriginTimestamp필수아니오원본 타임스탬프(UTC ms)
dagDependenciesDAGEdge[]필수아니오DAG 의존 목록(빈 배열일 수 있음)
encryptionMetadataEncryptionMetadata필수아니오암호화 메타데이터
sequenceNumberSequenceNumber필수아니오전송 시퀀스 번호

프레임 헤더는 암호화해서는 안 된다. 모든 필드는 평문으로 전송해야 한다.

4.3 프레임 유형(FrameType)

FrameType은 다음 4개의 열거 값 중 하나여야 한다:

용도Payload 내용
"data"실제 Fragment 데이터 운반Fragment의 data 필드
"request"데이터 요청 발의 또는 전송 약정 조정RequestFrame의 직렬화 내용
"response"데이터 요청에 회신ResponseFrame의 직렬화 내용
"control"오류 알림, 약정 종료 등 제어 정보 전달ControlFrame의 직렬화 내용

구현은 나열되지 않은 프레임 유형을 도입해서는 안 된다.

4.4 프로토콜 버전(ProtocolVersion)

ProtocolVersion은 두 개의 음이 아닌 정수로 구성되어야 한다:

interface ProtocolVersion {
  major: number;
  minor: number;
}

버전 번호 의미와 변경 판정 규칙은 제1장 §1.7.2에서 정의한 규칙을 따라야 한다(권위 있는 출처).

4.5 Agreement_ID 압축

agreementId 필드는 null일 수 있으며, 압축 전송에 사용된다. 압축 규칙은 다음 요구사항을 따라야 한다:

  1. 동일 약정에 속하는 연속된 Fragment 묶음에서, 첫 번째 Fragment의 프레임 헤더만 완전한 Agreement_ID를 포함해야 한다.
  2. 후속 Fragment의 agreementId 필드는 null로 설정할 수 있으며, 이는 직전의 null이 아닌 Agreement_ID를 그대로 사용함을 의미한다.
  3. 수신측은 "현재 컨텍스트 Agreement_ID"를 유지해야 하며, 다음 규칙에 따라 해석해야 한다:
    • agreementId가 null이 아닌 Fragment를 수신하면 그 값을 현재 컨텍스트 Agreement_ID로 갱신해야 한다.
    • agreementId가 null인 Fragment를 수신하면 이를 현재 컨텍스트 Agreement_ID에 연결해야 한다.
  4. 수신측이 agreementId가 null이지만 현재 컨텍스트 Agreement_ID도 null인 Fragment를 수신하면, 해당 Fragment를 폐기하고 AGREEMENT_NOT_FOUND 오류(3001)를 반환해야 한다.
  5. 수신측이 알 수 없는 Agreement_ID를 참조하는 Fragment를 수신하면, 해당 Fragment를 폐기하고 AGREEMENT_NOT_FOUND 오류(3001)를 반환해야 한다.

수신측의 현재 컨텍스트 Agreement_ID는 각 전송 방향마다 독립적으로 유지되어야 한다.

4.6 원본 타임스탬프(Origin_Timestamp)

originTimestamp는 다음을 충족해야 한다:

  1. UTC 시간대를 사용해야 한다.
  2. 밀리초 정밀도를 가져야 한다.
  3. 음이 아닌 정수(Unix 타임스탬프의 밀리초 값)여야 한다.
  4. 데이터가 소스에서 실제로 생성된 시점을 기록해야 하며, 전송 시점이어서는 안 된다.
  5. 완전한 전송 경로(직렬화 → 암호화 → 전송 → 복호화 → 역직렬화)를 거친 후에도 전송 전과 완전히 일치해야 한다.
  6. 수신측은 수신한 Origin_Timestamp를 수정해서는 안 된다.

4.7 DAG 의존(DAGEdge)

DAGEdge는 다음 필드를 포함해야 한다:

interface DAGEdge {
  targetFragmentId: FragmentID;
  relationType: DAGRelationType;
}

DAGRelationType은 다음 3개의 열거 값 중 하나여야 한다:

의미
"derived_from"해당 Fragment는 대상 Fragment에서 파생됨
"annotates"해당 Fragment는 대상 Fragment에 주석을 달거나 설명함
"supersedes"해당 Fragment는 대상 Fragment를 대체함

dagDependencies 배열은 비어 있을 수 있다(즉, Fragment가 의존 관계를 갖지 않는 경우).

4.8 암호화 메타데이터(EncryptionMetadata)

EncryptionMetadata는 다음 필드를 포함해야 한다:

interface EncryptionMetadata {
  algorithm: string;
  keyVersion: number;
}
필드규범적 요구사항
algorithm암호화 알고리즘의 식별 문자열이어야 한다(예: "AES-256-GCM"). IANA 등록 알고리즘 이름을 사용해야 한다(권장)
keyVersion음이 아닌 정수여야 한다. 키 로테이션 지원에 사용된다

암호화 메타데이터 자체는 암호화해서는 안 되며, 평문 형태로 프레임 헤더에 포함되어야 한다.

4.9 시퀀스 번호(Sequence_Number)

sequenceNumber는 다음을 충족해야 한다:

  1. 음이 아닌 정수여야 한다.
  2. 단일 세션 내에서 단조 증가해야 한다.
  3. 각 전송 방향(데이터 수집, 데이터 주입)마다 독립적으로 유지되어야 한다.
  4. 시퀀스 번호 공간은 두 방향 사이에 공유되어서는 안 된다.
  5. 0 또는 1부터 시작해야 한다(권장).
  6. 시퀀스 번호가 오버플로(구현 정의 최댓값)되면, 구현은 새로운 세션 생성을 통해 처리해야 하며, 순환(wrap around)해서는 안 된다.

4.10 Fragment 구조

Fragment는 다음 필드를 포함해야 한다:

interface Fragment {
  fragmentId: FragmentID;
  agreementId: AgreementID;
  originTimestamp: OriginTimestamp;
  contextMetadata: ContextMetadata;
  dagDependencies: DAGEdge[];
  data: Uint8Array;
}

참고: Fragment의 agreementId 필드는 LogicalFrame으로 직렬화될 때 압축 규칙에 의해 LogicalFrame 헤더에서 null로 표현될 수 있으나(제4.5절 참조), Fragment 자체의 논리적 agreementId는 항상 null이 아니어야 한다.

4.11 컨텍스트 메타데이터(ContextMetadata)

ContextMetadata는 다음 필드를 포함해야 한다:

interface ContextMetadata {
  dataType: string;
  source: DataSource;
  customFields: Record<string, unknown>;
}

DataSource는 다음 두 구조 중 하나여야 한다(discriminated union, kind 필드로 구분):

4.11.1 HardwareSource

데이터가 하드웨어 센서에서 발생한 경우, source는 다음과 같아야 한다:

interface HardwareSource {
  kind: "hardware";
  sensorType: string;
  precision: string;
  samplingRate: number;
}
필드규범적 요구사항
kind리터럴 문자열 "hardware"여야 한다
sensorType비어 있지 않은 문자열이어야 한다. 표준화된 명명을 사용해야 한다(권장)(예: "accelerometer", "heart_rate_monitor")
precision비어 있지 않은 문자열이어야 하며, 센서 정밀도를 설명한다(예: "±0.1°C")
samplingRate양수여야 하며, 단위는 Hz이다

4.11.2 SoftwareSource

데이터가 소프트웨어 공유에서 발생한 경우, source는 다음과 같아야 한다:

interface SoftwareSource {
  kind: "software";
  appIdentifier: string;
  sharingMethod: string;
}
필드규범적 요구사항
kind리터럴 문자열 "software"여야 한다
appIdentifier비어 있지 않은 문자열이어야 한다. 역방향 도메인 이름 형식을 사용해야 한다(권장)(예: "com.example.app")
sharingMethod비어 있지 않은 문자열이어야 하며, 공유 방식을 설명한다(예: "api_push", "clipboard_capture")

4.11.3 사용자 정의 필드

customFields는 문자열에서 임의 값으로의 매핑이어야 하며, 빈 객체 {}일 수 있다. 구현은 customFields에서 dataType이나 source에 이미 있는 정보를 중복해서는 안 된다.

4.12 직렬화 요구사항

LogicalFrame의 직렬화는 다음 규범적 요구사항을 충족해야 한다:

  1. 결정성: 동일한 LogicalFrame 객체는 동일한 바이너리 출력을 생성해야 한다.
  2. 왕복 일관성: 임의의 유효한 LogicalFrame에 대해, 직렬화 후 다시 역직렬화하면 원본 객체와 동등한 LogicalFrame이 생성되어야 한다.
  3. 완전성: 직렬화 출력은 프레임 헤더의 모든 필드를 포함해야 한다.
  4. 페이로드 암호화: 직렬화 전에 Payload는 EncryptionMetadata에 지정된 알고리즘으로 이미 암호화되어 있어야 한다.

직렬화의 구체적인 바이너리 레이아웃(바이트 순서, 필드 인코딩 방식)은 후속 초안에서 명시되며, CBOR(RFC 8949) 또는 Protocol Buffers와 같은 성숙한 바이너리 형식을 우선 선택해야 한다(권장).

4.13 물리적 분할

하위 전송이 분할을 요구할 때(예: BLE의 MTU 제한):

  1. 분할 작업은 Transport_Adapter가 담당해야 한다.
  2. LogicalFrame은 DTP_Engine 계층에서 완전성을 유지해야 한다.
  3. 수신측의 Transport_Adapter는 DTP_Engine에 전달하기 전에 완전한 LogicalFrame을 재조립해야 한다.