제4장 논리 프레임 구조
4.1 전체 구조
LogicalFrame은 다음 두 부분으로 구성되어야 한다:
+----------------+
| Header | (평문)
+----------------+
| Payload | (암호화)
+----------------+
LogicalFrame의 형식적 정의:
interface LogicalFrame {
header: FrameHeader;
payload: Uint8Array; // 암호화
}
4.2 프레임 헤더(FrameHeader)
프레임 헤더는 다음 필드를 포함해야 하며, 아래 표의 순서가 규범적 순서이다:
| 필드 | 타입 | 필수 | 암호화 | 설명 |
|---|---|---|---|---|
protocolVersion | ProtocolVersion | 필수 | 아니오 | 프로토콜 버전 번호 |
frameType | FrameType | 필수 | 아니오 | 프레임 유형 |
fragmentId | FragmentID | 필수 | 아니오 | Fragment 고유 식별자 |
agreementId | AgreementID | null | 필수 | 아니오 | 약정 ID(null일 수 있음) |
originTimestamp | OriginTimestamp | 필수 | 아니오 | 원본 타임스탬프(UTC ms) |
dagDependencies | DAGEdge[] | 필수 | 아니오 | DAG 의존 목록(빈 배열일 수 있음) |
encryptionMetadata | EncryptionMetadata | 필수 | 아니오 | 암호화 메타데이터 |
sequenceNumber | SequenceNumber | 필수 | 아니오 | 전송 시퀀스 번호 |
프레임 헤더는 암호화해서는 안 된다. 모든 필드는 평문으로 전송해야 한다.
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일 수 있으며, 압축 전송에 사용된다. 압축 규칙은 다음 요구사항을 따라야 한다:
- 동일 약정에 속하는 연속된 Fragment 묶음에서, 첫 번째 Fragment의 프레임 헤더만 완전한 Agreement_ID를 포함해야 한다.
- 후속 Fragment의
agreementId필드는null로 설정할 수 있으며, 이는 직전의 null이 아닌 Agreement_ID를 그대로 사용함을 의미한다. - 수신측은 "현재 컨텍스트 Agreement_ID"를 유지해야 하며, 다음 규칙에 따라 해석해야 한다:
agreementId가 null이 아닌 Fragment를 수신하면 그 값을 현재 컨텍스트 Agreement_ID로 갱신해야 한다.agreementId가 null인 Fragment를 수신하면 이를 현재 컨텍스트 Agreement_ID에 연결해야 한다.
- 수신측이
agreementId가 null이지만 현재 컨텍스트 Agreement_ID도 null인 Fragment를 수신하면, 해당 Fragment를 폐기하고AGREEMENT_NOT_FOUND오류(3001)를 반환해야 한다. - 수신측이 알 수 없는 Agreement_ID를 참조하는 Fragment를 수신하면, 해당 Fragment를 폐기하고
AGREEMENT_NOT_FOUND오류(3001)를 반환해야 한다.
수신측의 현재 컨텍스트 Agreement_ID는 각 전송 방향마다 독립적으로 유지되어야 한다.
4.6 원본 타임스탬프(Origin_Timestamp)
originTimestamp는 다음을 충족해야 한다:
- UTC 시간대를 사용해야 한다.
- 밀리초 정밀도를 가져야 한다.
- 음이 아닌 정수(Unix 타임스탬프의 밀리초 값)여야 한다.
- 데이터가 소스에서 실제로 생성된 시점을 기록해야 하며, 전송 시점이어서는 안 된다.
- 완전한 전송 경로(직렬화 → 암호화 → 전송 → 복호화 → 역직렬화)를 거친 후에도 전송 전과 완전히 일치해야 한다.
- 수신측은 수신한 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는 다음을 충족해야 한다:
- 음이 아닌 정수여야 한다.
- 단일 세션 내에서 단조 증가해야 한다.
- 각 전송 방향(데이터 수집, 데이터 주입)마다 독립적으로 유지되어야 한다.
- 시퀀스 번호 공간은 두 방향 사이에 공유되어서는 안 된다.
- 0 또는 1부터 시작해야 한다(권장).
- 시퀀스 번호가 오버플로(구현 정의 최댓값)되면, 구현은 새로운 세션 생성을 통해 처리해야 하며, 순환(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의 직렬화는 다음 규범적 요구사항을 충족해야 한다:
- 결정성: 동일한 LogicalFrame 객체는 동일한 바이너리 출력을 생성해야 한다.
- 왕복 일관성: 임의의 유효한 LogicalFrame에 대해, 직렬화 후 다시 역직렬화하면 원본 객체와 동등한 LogicalFrame이 생성되어야 한다.
- 완전성: 직렬화 출력은 프레임 헤더의 모든 필드를 포함해야 한다.
- 페이로드 암호화: 직렬화 전에 Payload는 EncryptionMetadata에 지정된 알고리즘으로 이미 암호화되어 있어야 한다.
직렬화의 구체적인 바이너리 레이아웃(바이트 순서, 필드 인코딩 방식)은 후속 초안에서 명시되며, CBOR(RFC 8949) 또는 Protocol Buffers와 같은 성숙한 바이너리 형식을 우선 선택해야 한다(권장).
4.13 물리적 분할
하위 전송이 분할을 요구할 때(예: BLE의 MTU 제한):
- 분할 작업은 Transport_Adapter가 담당해야 한다.
- LogicalFrame은 DTP_Engine 계층에서 완전성을 유지해야 한다.
- 수신측의 Transport_Adapter는 DTP_Engine에 전달하기 전에 완전한 LogicalFrame을 재조립해야 한다.
