Aqueous 아카이브 형식

편집자 초안,

편집자:
Kang Seonghoon
다른 언어 판 (비규범적):
English
비공식 한국어 번역

이 문서는 Kang Seonghoon의 Aqueous Archive Format 편집자 초안을 yeokja로 기계 번역한 비공식 한국어 번역이며, 원문 커밋 3d37dae5(2026년 10월 29일) 시점의 스냅샷입니다.

원문은 매주 크게 바뀌는 편집자 초안이므로 이 번역은 그 시점의 내용만 반영하며, 저자의 공식 번역이 아니고 저자가 검토하지 않았습니다. 최신 내용은 원문을 보십시오.

원문은 공개 도메인에 헌정되었으며 그것이 불가능한 곳에서는 MIT-0으로 제공됩니다. 이 번역도 같은 조건으로 제공합니다(LICENSE.txt).


요약

이 명세는 파일을 효율적으로 저장하고 검색하도록 설계된 압축 아카이브 형식인 Aqueous 아카이브 형식과, 디렉터리 트리를 기존 디렉터리 트리에 대한 변경으로 기술하는 Aqueous 오버레이 아카이브 형식을 정의합니다.

1. 소개

이 절은 비규범적입니다.

Aqueous 아카이브 형식은 디렉터리 트리 또는 단일 파일을 블록의 시퀀스로 저장합니다. 이 절은 형식의 각 부분이 어떻게 서로 맞물리는지 개관합니다. 각 단락은 그 단락이 설명하는 부분을 정의하는 장을 가리킵니다.

아카이브는 헤더, 블록의 시퀀스, 푸터로 이루어집니다(§ 3 컨테이너). 모든 블록은 자신의 유형과 크기로 시작하므로 압축 해제기는 어떤 블록이든 이해하지 않고도 건너뛸 수 있으며, 유형은 블록을 이해하지 못할 때 그 블록을 무시할 수 있는지를 알려 줍니다(§ 3.1.1 블록 유형). 푸터는 마지막 메타데이터 블록을 거슬러 가리키므로, 압축 해제기는 아카이브의 끝에서부터 읽기 시작할 수 있습니다.

블록은 세그먼트로 묶이며, 각 세그먼트는 메타데이터 블록으로 끝납니다(§ 4 세그먼트와 엔트리). 메타데이터 블록은 세그먼트의 엔트리, 즉 디렉터리, 파일, 심볼릭 링크 및 기타 노드의 이름을 지정하고, 각 파일이 그 내용을 담은 블록을 가리키게 합니다. 나중 세그먼트는 앞선 세그먼트의 엔트리를 추가, 교체, 제거하므로, 아카이브는 앞부분을 다시 쓰지 않고 세그먼트를 덧붙이는 것만으로 확장할 수 있습니다. 메타데이터 블록이 전혀 없는 아카이브는 gzip 파일과 마찬가지로 이름 없는 단일 파일로 간단히 압축 해제됩니다.

파일의 내용은 단 하나의 리프 블록으로 주어집니다. 가장 단순한 리프 블록은 내용 자체를 저장하는 데이터 블록이며, 선택적으로 Zstandard와 여러 작은 블록이 공유하는 사전으로 압축될 수 있습니다(§ 5 저장된 데이터). 공식 블록은 내용을 저장하는 대신, 뒤따르는 비리프 블록에 대한 작은 스택 기계 프로그램으로 내용을 기술하는 리프 블록입니다(§ 6 공식). 이 프로그램은 그 블록들의 조각을 취하고, 이어 붙이고, 패치하며, 압축기가 수행한 변환을 되돌리기 위해 그 블록들에 WebAssembly 모듈을 적용합니다(§ 7 모듈). 리프 블록과 그 뒤를 따르는 비리프 블록은 파셀을 이루며, 이는 보통 압축 해제기가 단일 파일을 추출하기 위해 읽는 전부이고, 공식 블록은 그 안에서의 임의 접근을 위한 색인 역할을 합니다.

오버레이 아카이브는 디렉터리 트리를 베이스라고 부르는 기존 디렉터리 트리에 대한 변경으로 기술합니다(§ 8 오버레이 아카이브와 임포트). 임포트 블록은 베이스에 있는 파일의 바이트 범위를 읽으며 공식은 이를 패치할 수 있으므로, 오버레이 아카이브는 베이스의 업데이트 또는 델타로 동작합니다.

아카이브 전체는 age [AGE]를 사용하여 패스프레이즈로 암호화될 수 있습니다(§ 9 암호화 아카이브). 체크섬은 선택적으로 제외되는 리프 데이터 블록과, 등록 사전과 모듈을 해시로 지정하는 약칭 블록을 제외한 모든 블록을 대상으로 합니다(§ 4.2.2 등록 사전과 등록 모듈). 미러 블록은 손상되면 많은 파일에 영향을 미칠 블록의 사본을 담습니다(§ 10 무결성과 복구). 타임스탬프, 주석, 권한처럼 파일을 읽는 데 필요하지 않은 메타데이터는 CBOR로 인코딩되며 압축 해제기가 무시할 수 있습니다(§ 11 선택적 메타데이터, § 12 추출).

2. 규약

2.1. 식

모든 식은 표준 산술에 따라 해석되며, 다음과 같은 확장이 적용됩니다.

모든 식의 결과는 -(264 - 1)과 264 - 1 사이의 정수이거나, 원소의 개수가 264 - 1을 넘지 않는 그러한 정수들의 배열입니다. 단, 하위 식은 여전히 이 범위를 넘을 수 있다는 점에 유의하십시오.

2.2. 타입

타입은 값들의 집합과 그에 연관된 인코딩 방식입니다. 타입이나 구조체의 바이트 길이를 문맥을 통해서만 알 수 있을 때, 그 타입이나 구조체는 암묵 크기입니다. 암묵 크기라고 명시되지 않는 한, 모든 타입은 문맥과 무관하게 정해진 바이트 길이를 가집니다.

u8은 1바이트를 차지하는 부호 없는 8비트 정수입니다. 그 범위는 [0, 28)입니다.

u16le는 2바이트를 차지하며 리틀 엔디언 바이트 순서(최하위 바이트가 먼저)로 저장되는 부호 없는 16비트 정수입니다. 그 범위는 [0, 216)입니다.

u32le는 4바이트를 차지하며 리틀 엔디언 바이트 순서로 저장되는 부호 없는 32비트 정수입니다. 그 범위는 [0, 232)입니다.

u64le는 8바이트를 차지하며 리틀 엔디언 바이트 순서로 저장되는 부호 없는 64비트 정수입니다. 그 범위는 [0, 264)입니다.

u(n)(n은 264보다 작은 정수)는 n > 0이면 n을 표현할 수 있는 가장 작은 정수 타입이고, n = 0이면 크기가 0인 타입입니다. 다시 말해 다음과 같습니다.

leb128은 LEB128 인코딩을 사용하여 가변 개수의 바이트로 인코딩된 부호 없는 64비트 정수입니다. 마지막 바이트를 제외한 각 바이트는 최상위 비트(MSB)가 1로 설정되어 있으며, 이는 뒤에 바이트가 더 이어짐을 나타냅니다. 각 바이트의 나머지 7비트는 정수의 값을 저장하는 데 사용됩니다. 첫 번째 바이트의 하위 7비트는 정수의 최하위 비트들이며, 이후 바이트도 같은 방식으로 이어집니다. 마지막 바이트는 MSB가 0으로 설정됩니다. 범위는 [0, 264)입니다. 인코딩은 최대 10바이트 길이여야 하며, 264 이상의 값을 인코딩해서는 안 됩니다.

이는 가장 짧은 인코딩이 요구되지 않는 한, 0 비트로 이루어진 바이트로 패딩하여 263보다 작은 모든 수에 대해 여러 인코딩을 허용합니다.

a[n]은 a의 순서 있는 배열을 나타내며, 여기서 n은 정수입니다. n의 범위는 [0, 264)입니다.

a[*]는 원소 개수가 지정되지 않은 a의 암묵 크기 순서 있는 배열을 나타냅니다. 원소의 개수는 문맥으로부터 암묵적으로 결정됩니다. 이 명세는 이러한 문맥을 두 가지 사용합니다.

2.3. 구조체

구조체는 서로 다른 타입일 수 있는 하나 이상의 순서 있는 필드를 이어 붙인 논리적 묶음입니다. 각 필드는 다음과 같은 속성을 가집니다.

필드 이름은 필드의 타입이 조건에 따라 달라지는 경우에만 겹칠 수 있으며, 이 경우 어느 시점에서든 그러한 조건 중 최대 하나만 적용될 수 있습니다. 필드의 이름이 이전 필드의 이름과 같은 경우 이름을 생략할 수 있습니다.

필드 타입은 offset일 수 있으며, 이는 지정되지 않은 파일 위치(원점)를 기준으로 한 다음 바이트까지의 오프셋으로 해석되는 특수한 크기 0의 타입입니다. 오프셋은 필드 집합 내의 바이트 수를 측정하는 데에만 사용됩니다. 이 명세의 구문과 의미는 원점의 선택에 의존하지 않습니다.

선택적인 제약 조건은 필드의 인코딩에 영향을 주지 않으며, 가능한 값의 집합을 좁히기만 합니다. 제약 조건은 다음 중 하나일 수 있습니다.

조건은 다음 중 하나일 수 있습니다:

구조체는 그 바이트 길이를 문맥을 통해 알 수 없고 그 안에 암묵 크기 필드가 하나 이상 존재하는 경우에만 암묵 크기입니다.

2.4. 상수

이 명세는 다음 상수를 정의하고 사용하며, 이들은 모두 대문자로 표기합니다.

이름 값
HEADER_TYPE 27816
OVERLAY_HEADER_TYPE 6916
FOOTER_TYPE 90 5BCB 957D16
OVERLAY_FOOTER_TYPE 7FC16
ENCRYPTED_HEADER_TYPE 155 04DC A57916
HEADER_MAGIC (F8 04 03 6A D6 BE)16
OVERLAY_HEADER_MAGIC (69 0D 15 AF F6 B2)16
FOOTER_MAGIC (FD AA AE DE 85 12)16
OVERLAY_FOOTER_MAGIC (FC 0F 6E 7B 4B AC)16
ENCRYPTED_HEADER_MAGIC (F9 CA F2 A6 D0 2A 01 00)16
ENCRYPTED_MIRRORED_HEADER_MAGIC (F9 CA F2 A6 D0 2A 02 00)16
MIRROR_MAGIC (02 AA F5 FE BA 2B AE 23)16
OVERLAY_MIRROR_MAGIC (01 05 34 FA BD 11 AF 53)16
ENCRYPTED_MIRROR_MAGIC (78 D7 3F 69 0A FF 44 83)16
HEADER_TYPE, OVERLAY_HEADER_TYPE, FOOTER_TYPE, OVERLAY_FOOTER_TYPE, ENCRYPTED_HEADER_TYPE를 블록 유형으로 인코딩한 것(§ 3.1 블록)은 각각 HEADER_MAGIC, OVERLAY_HEADER_MAGIC, FOOTER_MAGIC, OVERLAY_FOOTER_MAGIC, ENCRYPTED_HEADER_MAGIC의 접두사이며, ENCRYPTED_HEADER_TYPE을 인코딩한 것은 ENCRYPTED_MIRRORED_HEADER_MAGIC의 접두사이기도 합니다.

2.5. 의미론과 실패

이 명세는 아카이브의 의미를 바이트열에서 아카이브 값으로 가는 함수로 정의하며, 이 함수는 오버레이 아카이브의 경우 베이스도 함께 받습니다. 이 명세의 모든 알고리즘은 선언적입니다. 즉, 결과가 무엇인지를 정의할 뿐 그것을 어떻게 또는 어떤 순서로 계산해야 하는지를 정의하지 않으며, 적합한 구현은 같은 결과를 내는 다른 어떤 방식으로든 그것을 계산할 수 있습니다.

아카이브 해석의 결과는 아카이브 값 또는 실패 중 하나이며, 실패는 다음 중 하나입니다.

미지원 실패는 더 새로운 버전을 구현한 압축 해제기라면 입력을 해석할 수 있을지도 모른다는 것을 알려 주며, 이는 일반적으로 잘못된 아카이브 실패에는 해당하지 않습니다. 미래 버전이 다른 블록에 다른 의미를 부여할 수 있으므로, 미지원 실패는 어떤 블록에서 발생한 잘못된 아카이브 실패보다도 우선합니다(§ 4.4 아카이브 해석).

적합한 압축기는 아카이브 값이 주어지면 그 해석이 해당 아카이브 값인 바이트열을 생성해야 합니다. 압축기가 오버레이 아카이브를 생성할 때는 베이스도 함께 주어지며, 그 베이스를 사용한 해석은 해당 아카이브 값이어야 합니다. 이 명세는 압축기가 블록, 블록 크기, 사전 또는 공식을 선택하는 방식을 제한하지 않습니다.

3. 컨테이너

3.1. 블록

블록은 유형이 지정된 바이트열입니다. 암호화되지 않은 아카이브 전체와 암호화 아카이브의 시작 부분은 블록 또는 블록과 유사한 구조로 구성됩니다.

블록은 다음과 같은 일반 구조를 가집니다:

이름 타입 조건
block_start offset
type leb128 ∉ {FOOTER_TYPE, OVERLAY_FOOTER_TYPE}
size u8 type % 4 = 0인 경우
u16le type % 4 = 1인 경우
u32le type % 4 = 2인 경우
u64le type % 4 = 3인 경우
data u8[size] type에 따라 다름
block_end offset
padding u8 (block_end - block_start) % 2 = 1인 경우

FOOTER_TYPE과 OVERLAY_FOOTER_TYPE을 포함한 일부 type 값은 블록과 유사한 구조를 나타내는 데에도 사용됩니다. 예를 들어 아카이브 푸터는 블록 뒤에 놓이기 위해 별개의 블록 유형을 가져야 합니다. 이 명세는 명시적으로 언급하지 않는 한 이러한 블록 유사 구조에 어떠한 의미도 부여하지 않습니다.

type은 가장 짧은 leb128 인코딩을 사용해야 합니다.

padding이 존재하므로 모든 블록의 바이트 수는 짝수가 됩니다. 적합한 압축기는 padding을 0으로 설정해야 합니다. 그러나 그 값은 아카이브 해석 결과에 영향을 주지 않습니다.

padding은 아무런 정보도 담고 있지 않으므로, 그것이 손상되었다는 사실은 그 외에는 읽을 수 있는 아카이브를 거부할 이유가 되지 않습니다. 반면 type은 size의 너비와 다음 블록이 시작되는 위치를 결정하므로, 각 유형에 단 하나의 인코딩만 두면 스캔 등으로 블록 경계를 찾을 때 모호함이 남지 않습니다.

이후 절에서는 정의된 각 type에 따라 data 필드의 내용을 설명합니다. type은 size의 바이트 길이도 결정하므로, 일반적으로 각 논리적 블록 유형마다 여러 개의 type 값이 할당됩니다.

3.1.1. 블록 유형

다음 표는 블록 유형의 모든 범위를 나열합니다. 단일 논리적 블록 유형에 해당하는 블록 유형의 범위는 이진수로도 표기합니다. 예:0001 0mss 여기서 문자는 블록의 매개변수를 결정하는 비트를 나타냅니다. ss는 항상 최하위 두 비트를 나타내며, 이는 위에서와 같이 size의 너비를 결정합니다. 0은 u8, 1은 u16le, 2는 u32le, 3은 u64le입니다. 예약된 블록 유형이 필수인지 여부는 그 첫 자리 숫자에 의해 결정됩니다(§ 3.1.1.1 예약된 블록 유형).

16진수 2자리로 된 블록 유형은 다음과 같습니다.

블록 유형 필수 정의
0016 — 0316 예 § 3.3 스페이서 블록
0416 예 § 3.4 크기 힌트 블록
0516 — 0F16 예 예약됨
1016 — 1716 예 § 4.3 메타데이터 블록
1816 — 1F16 예 예약됨
2016 — 3716 예 § 5.1 데이터 블록
3816 — 3F16 예 예약됨
4016 — 4B16 예 § 5.1 데이터 블록
4C16 — 4F16 예 예약됨
5016 예 § 5.2 사전 블록, 약칭 블록으로서 (§ 4.2.2 등록 사전과 등록 모듈)
5116 — 5716 예 § 5.2 사전 블록
5816 — 5F16 예 § 8.1 임포트 블록
6016 예 § 7.1 모듈 블록, 약칭 블록으로서 (§ 4.2.2 등록 사전과 등록 모듈)
6116 — 6316 예 § 7.1 모듈 블록
6416 — 6716 예 § 7.2 리소스 블록
6816 예 예약됨
6916 예 OVERLAY_HEADER_TYPE용으로 예약됨 (§ 3.1.1.1 예약된 블록 유형)
6A16 — 6F16 예 예약됨
7016 — 7716 예 § 6.1 공식 블록
7816 — 9F16 예 예약됨
A016 — CF16 아니요 예약됨
D016 — DF16 아니요 개발 환경용으로 예약됨
E016 — EF16 아니요 실험용으로 예약됨
F016 — FF16 아니요 예약됨

16진수 3자리 이상의 블록 유형은 다음과 같습니다:

블록 유형 필수 정의
10016 — 9FF16, 100016 — 9FFF16, … 예 예약됨, 블록과 유사한 구조 포함(§ 3.1.1.1 예약된 블록 유형)
A0016 — BFF16, A00016 — BFFF16, … 아니요 예약됨
C0016 — C0F16 아니요 예약됨
C1016 — C1716 아니요 § 4.3 메타데이터 블록용 § 10.3 미러 블록
C1816 — C4F16 아니요 예약됨
C5016 — C5716 아니요 § 5.2 사전 블록용 § 10.3 미러 블록
C5816 — C5F16 아니요 § 8.1 임포트 블록용 § 10.3 미러 블록
C6016 — C6316 아니요 § 7.1 모듈 블록용 § 10.3 미러 블록
C6416 — C6716 아니요 § 7.2 리소스 블록에 대한 § 10.3 미러 블록
C6816 — C6F16 아니요 예약됨
C7016 — C7716 아니요 § 6.1 공식 블록에 대한 § 10.3 미러 블록
C7816 — CFF16, C00016 — CFFF16, … 아니요 예약됨
D0016 — DFF16, D00016 — DFFF16, … 아니요 개발 환경용으로 예약됨
E0016 — EFF16, E00016 — EFFF16, … 아니요 실험용으로 예약됨
F0016 — FFF16, F00016 — FFFF16, … 아니요 예약됨
3.1.1.1. 예약된 블록 유형

블록 유형의 선행 자릿수는 최소 2자리가 되도록 0으로 채운 16진수 표현의 첫 번째 자릿수입니다. 예를 들어 416, 4B16, C1016의 선행 자릿수는 각각 0, 4, C입니다.

블록 유형은 선행 자릿수가 0부터 9까지이면 필수이고, A부터 F까지이면 비필수입니다. 이 명세에서 정의하지 않은 타입의 블록은 다음과 같습니다:

이 명세에서 정의하는 모든 블록 유형은 미러 블록을 제외하고 필수이며, 미러 블록은 이를 사용하지 않는 압축 해제기가 건너뛸 수 있습니다. 이 명세의 향후 버전에서는 아카이브를 올바르게 해석하는 데 반드시 필요한 블록(예: 3816부터 3F16까지에 속하는 새로운 압축 방식의 데이터 블록)에는 필수 유형을 사용하고, 안전하게 건너뛸 수 있는 블록(예: 복구 데이터)에는 비필수 유형을 사용할 것입니다. 아래의 블록 유사 구조도 필수 타입을 가지므로 실수로 건너뛰어지는 일이 없습니다.

타입의 선두 자릿수가 D인 블록(즉, D016부터 DF16까지, D0016부터 DFF16까지, …)은 개발 환경용으로 예약되어 있습니다. 이 명세는 이러한 블록에 어떠한 의미도 부여하지 않습니다. 이러한 블록은 이를 활성화하는 특정 명령이 있을 때에만 쓰거나 읽어야 합니다.

타입의 선두 자릿수가 E인 블록(즉, E016부터 EF16까지, E0016부터 EFF16까지, …)은 실험용으로 예약되어 있습니다. 이 명세는 그러한 블록에 어떠한 의미도 부여하지 않습니다.

개발 환경용 및 실험용으로 예약된 블록은 비필수이므로, 이를 활성화하지 않은 압축 해제기는 이를 무시합니다. 실험은 완료되면 이 명세에서 정의하는 블록 유형으로 옮겨질 것으로 예상되므로, 실험을 활성화한 구현은 해당 블록을 필수로 취급할 수 있습니다.

다음 블록 유형은 블록과 유사한 구조로 사용되므로 유효하지 않은 것으로 정의됩니다. 유효한 아카이브는 이러한 블록 유형을 정식 블록으로 포함해서는 안 됩니다.

3.2. 공유 외부 구조

많은 블록 유형은 동일한 외부 구조를 공유하며, 내부 구조만 다릅니다. 이 구조는 압축될 수도 있고 압축되지 않을 수도 있으며, 상위 구조에서 지정한 compression_method와 checksum_method를 사용합니다. 각 압축 방식은 자신의 매개변수(compression_params)의 구조를 정의하며, 그 바이트 길이는 이를 파싱하여 결정됩니다. 각 checksum_method에는 또한 연관된 고정 checksum_size가 있습니다. 체크섬이 있는 Zstandard 데이터는 compression_method로 Zstandard를, checksum_method로 XXH64를 사용하는 압축된 외부 구조(compressed_data)입니다.

압축된 외부 구조(compressed_data)는 다음으로 구성됩니다:

이름 타입 조건
original_size_delta leb128
original_size_shrink leb128 original_size_delta = 0인 경우
compression_params compression_method의 매개변수 compression_method에 따라 다름
data_start offset
data u8[*]
data_end offset
checksum u8[checksum_size] checksum_method가 None이 아닌 경우, checksum_method에 따라 다름

압축된 크기(compressed_size)는 블록 크기로부터 추론합니다: compressed_size = data_end - data_start. 원본 크기(original_size)는 compressed_size에 대한 상대값으로 인코딩합니다:

따라서 각 원본 크기는 정확히 하나의 인코딩을 가지며, compressed_size와 같은 원본 크기는 두 개의 0 바이트로 인코딩됩니다.

압축된 데이터는 보통 원본보다 짧으며, 이 경우 필드 하나만 차지합니다. 그럼에도 원본 크기가 더 작을 수 있습니다. 예를 들어 Zstandard가 압축할 수 없는 데이터를 원시 블록에 저장하는 경우가 그러하며, 각 원시 블록에는 3바이트의 오버헤드가 있습니다. 이는 import_checksum이 압축되지 않는 임포트 블록처럼 항상 Zstandard를 사용하는 블록에서 중요하며, compression_method로 None을 사용할 수 있는 블록은 일반적으로 대신 None을 사용합니다.

압축되지 않은 외부 구조(uncompressed_data)는 다음으로 구성됩니다:

이름 타입 조건
data u8[*]
checksum u8[checksum_size] checksum_method가 None이 아닌 경우

두 구조 모두에서 체크섬은 저장된 바이트(§ 10.1 체크섬)가 아니라 디코딩된 데이터(§ 5.3 블록 내용), 즉 압축 해제 후의 데이터를 대상으로 계산됩니다.

3.2.1. Zstandard 압축 방식

이 명세는 단 하나의 압축 방식인 Zstandard [RFC8878]만을 정의합니다. Zstandard 블록은 Zstandard 프레임의 "Block" 구조([RFC8878], Section 3.1.1.2)입니다. 그 매개변수(compression_params)는 다음과 같습니다.

이름 타입
window_descriptor u8

window_descriptor 필드의 의미는 Zstandard의 Window_Descriptor 바이트와 동일하며, 이 바이트는 [RFC8878], Section 3.1.1.1.2에서와 같이 Window_Size를 결정합니다. 따라서 각 Zstandard 블록은 최대 Block_Maximum_Size = min(Window_Size, 128 KiB) 바이트의 데이터를 담습니다.

적합한 압축 해제기는 227 바이트(128 MiB)까지의 모든 Window_Size를 지원해야 하며, 더 큰 Window_Size에 대해서는 리소스 실패를 반환할 수 있습니다. 적합한 압축기는 명시적으로 요청받지 않는 한 227 바이트보다 큰 Window_Size를 사용하지 않는 것이 좋습니다.

사전은 마치 데이터 앞에 놓인 것처럼 압축 방식에 제공되는 바이트열로, 일반적으로 공유 압축 컨텍스트가 없는 점을 보완하기 위해 여러 작은 블록이 공유합니다. 사전이 사용되는 경우, 그 내용, 엔트로피 테이블 및 반복 오프셋은 § 5.2 사전 블록에서 설명하는 바와 같이 사전 블록에 의해 주어집니다.

이 방식으로 압축된 데이터는 Zstandard 프레임이 제공하는 추가 프레이밍 없이 Zstandard 블록들을 나열한 것입니다. data는 다른 바이트 없이 하나 이상의 그러한 블록으로 구성되어야 하며, 마지막 블록만이 그 블록 헤더에 의해 최종 블록으로 표시됩니다.

따라서 압축된 데이터의 끝은 data_end와 최종 블록 양쪽에 의해 주어지며, 둘은 일치해야 합니다.

3.2.2. XXH64 체크섬 방식

이 명세는 단 하나의 체크섬 방식인 XXH64 [XXHASH]만을 정의합니다. 그 시드는 0이어야 하며 결과는 u64le로 저장되어야 합니다. 그 결과 checksum_size는 8입니다.

3.3. 스페이서 블록

블록 016부터 316까지는 스페이서 블록입니다. 그 데이터는 다음으로 구성됩니다.

이름 타입
space u8[size]

스페이서 블록은 2 이상의 임의의 짝수 바이트를 차지하며, 패딩으로 인해 모든 블록이 짝수 바이트를 차지하므로 이것으로 충분합니다.

적합한 압축기는 데이터 유출을 방지하기 위해 공간을 0 바이트로 채워야 하지만, 그 내용은 아카이브 해석 결과에 영향을 주지 않습니다. 적합한 압축기는 성능을 위해 희소 파일(sparse file)을 활용하는 것이 좋습니다.

3.4. 크기 힌트 블록

블록 416은 크기 힌트 블록입니다. 그 데이터는 다음으로 구성됩니다.

이름 타입
size_hint leb128 ≠ 0

이 블록이 존재하는 경우, 이 블록은 헤더 바로 다음의 첫 번째 블록이어야 하며 그 size_hint 필드는 푸터의 size_hint 필드와 같아야 합니다.

3.5. 암호화되지 않은 아카이브

암호화되지 않은 아카이브는 헤더, 0개 이상의 블록, 그리고 푸터로 구성됩니다.

이름 타입 조건
header_magic u8[6] ∈ {HEADER_MAGIC, OVERLAY_HEADER_MAGIC}
archive_start offset
size_hint_block size_hint_block 선택
blocks 블록[*]
archive_end offset
footer_magic u8[6] ∈ {FOOTER_MAGIC, OVERLAY_FOOTER_MAGIC}
footer_size u16le = footer_end - footer_start
footer_start offset
size_hint leb128 = archive_end - archive_start
metadata_backward_offset leb128
footer_end offset
footer_size_copy u16le = footer_size

header_magic은 파일이 암호화되지 않은 Aqueous 아카이브임을 식별하기 위한 고정된 6바이트 바이트열입니다. 아카이브가 파일의 시작에서 시작하지 않는 경우, 이 헤더는 아카이브의 시작도 표시합니다.

footer_magic은 푸터의 시작을 표시하기 위한 고정된 6바이트 바이트열입니다. 아카이브가 파일의 끝에서 끝나지 않는 경우, 푸터는 아카이브의 끝도 표시합니다.

암호화되지 않은 아카이브는 header_magic이 OVERLAY_HEADER_MAGIC이면 오버레이 아카이브이고, 그렇지 않으면 독립 아카이브입니다. footer_magic은 오버레이 아카이브에서는 OVERLAY_FOOTER_MAGIC이어야 하고, 그 밖의 경우에는 FOOTER_MAGIC이어야 합니다. 오버레이 아카이브는 오버레이 아카이브와 별도로 주어지는 기존 디렉터리 트리 또는 파일인 베이스에 대해 해석됩니다. 즉 오버레이 아카이브는 빈 디렉터리 대신 베이스에서 시작하여 자신의 세그먼트를 베이스에 적용하며(§ 4.4 아카이브 해석), 베이스를 읽는 외부 임포트 블록은 오버레이 아카이브만 포함할 수 있습니다(§ 8.1 임포트 블록). 오버레이 아카이브의 형식은 Aqueous 오버레이 아카이브 형식이라고 하며, 고유한 미디어 타입을 가집니다(§ 15.2 application/aqo 미디어 타입).

독립 아카이브는 빈 디렉터리를 베이스로 하는 오버레이 아카이브와 같은 방식으로 해석됩니다. 반대 방향으로 혼동하는 것은 해롭습니다. 외부 임포트 없이 파일을 추가하고 제거하기만 하는 오버레이 아카이브는 베이스의 파일이 전혀 없는 상태로 아무런 경고 없이 해석될 것입니다. 이는 헤더가 손상된 오버레이 아카이브가 독립 아카이브로 복구될 때 발생할 수 있습니다. 따라서 헤더 없이 찾을 수 있는 모든 구조, 즉 아카이브의 끝에서 찾는 푸터와 스캔으로 찾는 미러 블록은 아카이브가 오버레이 아카이브인지 여부를 알려 줍니다. 메타데이터 블록은 이들을 통해서만 찾을 수 있으며, 암호화 아카이브는 복호화된 페이로드에서 이를 알려 줍니다.

header_magic과 footer_magic 사이의 오프셋은 size_hint로 기록되며, 임의 접근이 필요한 경우 size_hint_block으로도 복제할 수 있습니다.

푸터 크기, 즉 footer_size와 footer_size_copy 사이(둘 다 제외)에 있는 모든 필드의 크기는 아카이브의 끝에서 푸터를 읽을 수 있도록 footer_size와 footer_size_copy 양쪽에 기록됩니다(§ 3.6 아카이브 찾기).

footer_size와 footer_size_copy는 leb128 대신 u16le입니다. 따라서 푸터 크기는 65,535바이트로 제한되며, (footer_magic, footer_size, footer_size_copy를 포함하여) 65,545바이트를 한 번 읽는 것으로 푸터 전체를 읽는 것이 보장됩니다.

metadata_backward_offset은 previous_metadata_offset이 현재 메타데이터 블록의 시작에서 이전 메타데이터 블록까지 거슬러 올라가는 오프셋인 것과 마찬가지로, footer_start에서 마지막 메타데이터 블록의 시작까지 거슬러 올라가는 오프셋입니다. 압축 해제기가 blocks를 스캔하지 않고도 메타데이터 블록이 없는 아카이브를 구별할 수 있도록, 이 값은 메타데이터 블록이 없는 경우에만, 그리고 그 경우에는 반드시 0이어야 합니다.

blocks는 논리적으로 세그먼트로 나뉘며, 각 세그먼트는 콘텐츠 블록과 이를 기술하는 하나의 메타데이터 블록으로 구성되고, 그 사이에 선택적인 스페이서 블록, 미러 블록, 무시 블록이 올 수 있습니다. 각 세그먼트는 읽는 순서대로 독립적으로 처리 및 압축 해제될 수 있습니다. 다만 내부 임포트(§ 8.1 임포트 블록)는 선행 세그먼트의 결과를 읽습니다. 세그먼트는 § 4 세그먼트와 엔트리에서 설명합니다.

또는 블록에 메타데이터 블록이 없을 수도 있습니다. 그러한 아카이브는 이름 없는 단일 파일로 압축 해제되며, 그 내용은 모든 리프 블록의 내용을 나타나는 순서대로 이어 붙인 것입니다. 리프 블록이 없으면 빈 파일로 압축 해제됩니다.

이를 통해 크기를 알 수 없는 단일 파일을 스트리밍 방식으로 압축할 수 있으며, 임의 접근의 각 단위를 압축되는 즉시 별도의 리프 블록, 즉 별도의 파셀로 기록하면 됩니다. 이는 압축 해제된 파일의 이름을 담을 필요가 없는 gzip 파일과 유사합니다. 여러 파일을 스트리밍 방식으로 압축하는 것은 지원되지 않습니다. 여러 비리프 블록에 걸친 파일에는 공식 블록으로 시작하는 파셀이 필요하며, 이 공식 블록은 비리프 블록의 모든 슬라이스의 길이를 그 어느 것보다도 앞서 선언합니다.

3.6. 아카이브 찾기

파일은 앞쪽 바이트(예: 자동 압축 풀림 실행 파일)나 뒤쪽 바이트(예: 전송 과정에서 덧붙여진 바이트)와 함께 아카이브를 담고 있을 수 있습니다. 아카이브 해석은 아카이브만을 입력으로 받으므로, 그러한 파일을 받아들이는 압축 해제기는 먼저 아카이브를 찾아야 합니다.

헤더 매직과 푸터 매직이 HEADER_MAGIC과 FOOTER_MAGIC이거나, OVERLAY_HEADER_MAGIC과 OVERLAY_FOOTER_MAGIC이면 두 매직이 일치한다고 합니다.

바이트열 file에서 아카이브를 찾으려면 다음과 같이 합니다.

  1. file이 ENCRYPTED_HEADER_MAGIC 또는 ENCRYPTED_MIRRORED_HEADER_MAGIC으로 시작하면 file을 반환합니다.

  2. back을 null로 둡니다. file의 마지막 2바이트를 u16le로 읽은 값을 n이라 하고, file의 길이 − n − 10을 f라 합니다. f ≥ 0이고, f부터의 file 바이트가 FOOTER_MAGIC 또는 OVERLAY_FOOTER_MAGIC 뒤에 u16le로 된 n이 이어진 것이며, f + 8부터의 leb128이 h = f − s − 6 ≥ 0을 만족하는 s이고 h부터의 file 바이트가 그 푸터 매직과 일치하는 헤더 매직이면, back을 h로 설정합니다.

  3. file이 HEADER_MAGIC 또는 OVERLAY_HEADER_MAGIC으로 시작하면 다음과 같이 합니다.

    1. back이 0이면 file을 반환합니다.

    2. 현재 오프셋 e의 바이트가 FOOTER_MAGIC 또는 OVERLAY_FOOTER_MAGIC으로 시작할 때까지, 오프셋 6부터 일반 구조(§ 3.1 블록)에 따라 블록을 읽되, 모든 블록의 data는 파싱하지 않고 바이트열로 취급합니다. 그 전에 블록을 읽는 데 실패하거나, 푸터 매직이 헤더 매직과 일치하지 않으면 실패를 반환합니다.

    3. e + 6부터의 file의 2바이트를 u16le로 읽은 값을 m이라 합니다. file이 e + m + 10바이트보다 짧으면 실패를 반환합니다. 그렇지 않으면 file의 처음 e + m + 10바이트를 반환합니다.

  4. back이 null이 아니면 back부터의 file 바이트를 반환합니다.

  5. 실패를 반환합니다.

찾은 아카이브는 이어서 해석되며, 이 과정에서 여전히 실패할 수 있습니다. 압축 해제기는 file에 찾은 아카이브 바깥의 바이트가 있으면 사용자에게 경고하는 것이 좋습니다. 압축 해제기는 다른 방법으로 아카이브를 찾을 수 없을 때, 예를 들어 file에 앞쪽 바이트와 뒤쪽 바이트가 모두 있을 때, HEADER_MAGIC과 OVERLAY_HEADER_MAGIC을 검색할 수 있습니다.

암호화 아카이브는 페이로드를 복호화하지 않고는 그 끝을 알 수 없으므로, 파일의 시작에서만 찾아지며 파일의 끝까지 이어집니다(§ 9 암호화 아카이브).

FOOTER_MAGIC과 OVERLAY_FOOTER_MAGIC은 유효한 블록 유형이 아닌 FOOTER_TYPE과 OVERLAY_FOOTER_TYPE의 가장 짧은 leb128 인코딩으로 시작하므로(§ 2.4 상수), 블록은 이들로 시작할 수 없습니다. 따라서 3.2단계는 file에서 첫 번째 아카이브의 끝을 찾고, 4단계는 그 앞의 바이트를 읽지 않고 file 끝에 있는 아카이브를 찾습니다. 서로 이어 붙인 아카이브는 하나의 아카이브가 아니며, 그중 첫 번째 아카이브만 찾아집니다.

4. 세그먼트와 엔트리

4.1. 아카이브 모델

아카이브 값은 다음 항목을 가지는 구조체입니다.

파일 노드는 다음 항목을 가지는 구조체입니다.

디렉터리 노드는 다음 항목을 가지는 구조체입니다.

링크 노드는 심볼릭 링크를 나타내며, 다음 항목을 가지는 구조체입니다.

특수 노드는 FIFO 또는 장치 파일을 나타내며, 다음 항목을 가지는 구조체입니다.

장치 번호는 tar와 cpio가 하는 방식이나 makedev가 받는 방식처럼 플랫폼별 dev_t 인코딩으로 저장되지 않고, 쌍으로 저장됩니다. 그 의미는 이를 추출하는 플랫폼에 달려 있습니다. 같은 쌍이라도 플랫폼이 다르면 일반적으로 서로 무관한 장치를 가리키며(예를 들어 /dev/null은 Linux에서는 (1, 3)이지만 macOS에서는 (3, 2)입니다), 이 명세는 그 번호가 어느 플랫폼에서 왔는지 기록하지 않습니다.

미지 노드는 이 명세의 이번 버전에서 정의되지 않은 종류의 노드를 나타내며, 다음 항목을 가지는 구조체입니다.

root는 링크 노드, 특수 노드, 미지 노드일 수 없습니다.

툼스톤은 항목이 없는 구조체로, 노드의 제거를 표시합니다. 툼스톤은 세그먼트 해석 결과의 children에만 나타나며, 앞선 결과에 병합될 때 제거되므로(§ 4.4.2 세그먼트 병합) 아카이브 값에는 절대 나타나지 않습니다.

엔트리 이름은 다음 항목을 가지는 구조체입니다.

두 엔트리 이름은 두 항목이 모두 같은 경우에만 같습니다. 특히 "wtf-8" 이름과 "raw" 이름은 바이트가 같더라도 결코 같지 않습니다.

엔트리 이름의 바이트는 0016이나 2F16(/)를 포함할 수 없으며, (2E)16(.)이나 (2E 2E)16(..)와 같을 수도 없습니다.

이러한 제한으로 인해 아카이브 값의 모든 경로는 단순한 구성 요소의 나열이 되므로, 어떤 이름도 일반적인 파일 시스템에서 현재 디렉터리, 상위 디렉터리 또는 중첩 디렉터리를 가리킬 수 없습니다. 플랫폼별 제한은 추출 시점에 처리합니다(§ 12 추출).

엔트리 이름은 다음 방법 중 하나로 바이트열로 인코딩되며, 어느 방법도 0 바이트를 포함하지 않습니다:

바이트 C116을 선택한 이유는 이 바이트가 WTF-8이나 그 밖에 알려진 어떠한 UTF-8 확장에서도 나타날 수 없기 때문입니다.

적합한 압축기는 "wtf-8" 인코딩을 사용하는 것이 권장되며, "raw" 인코딩은 왕복 변환을 위한 수단으로만 사용하는 것이 좋습니다.

entry_name 필드 bytes에서 엔트리 이름을 디코딩하려면:

  1. bytes가 비어 있지 않고 bytes[0]이 C116이면, name을 인코딩이 "raw"이고 바이트가 첫 번째 바이트를 제외한 bytes인 엔트리 이름으로 둡니다.

  2. 그렇지 않고 bytes가 유효한 WTF-8 문자열이면, name을 인코딩이 "wtf-8"이고 바이트가 bytes인 엔트리 이름으로 둡니다.

  3. 그렇지 않으면 잘못된 아카이브 실패를 반환합니다.

  4. name이 위에 기술된 엔트리 이름의 제약 사항 중 하나라도 위반하면 잘못된 아카이브 실패를 반환합니다.

  5. name을 반환합니다.

링크 대상은 다음 항목을 가진 구조체입니다:

엔트리 이름과 달리 링크 대상은 구분자로 2F16(/)를 포함할 수 있고, .이나 ..와 같은 구성 요소를 포함할 수 있는 경로입니다. 링크 대상은 절대 경로일 수 있으며, 아카이브 값이나 다른 어느 곳에도 존재하지 않는 경로를 가리킬 수 있습니다. 구분자는 항상 /이며, Windows처럼 \를 구분자로 사용하는 플랫폼에서 온 링크 대상이라도 마찬가지로, 이 경우 모든 \는 /로 표현됩니다. 그 외에는 링크 대상을 정규화하지 않으며, 그 의미(예: 드라이브 문자의 의미)는 플랫폼에 맡깁니다. 구현에서 정의한 제한 외에는 길이 제한이 없습니다.

/는 Windows에서 파일 이름에 나타날 수 없고 그곳에서는 링크 대상의 구분자로 해석되지 않으므로, 이 치환은 손실이 없습니다. Windows의 압축 해제기는 링크를 생성할 때 /를 다시 \로 치환합니다(§ 12 추출).

Windows에서 절대 링크 또는 정션의 대상은 \??\로 시작하는 NT 경로로 저장되며, 이는 다른 플랫폼에서는 아무런 의미가 없습니다. 압축기는 이를 먼저 Win32 경로로 변환하는 것이 권장됩니다. 예를 들어 \??\C:\foo는 C:/foo로, \??\UNC\server\share\x는 //server/share/x로, \??\Volume{GUID}\x는 //?/Volume{GUID}/x로 변환합니다.

링크 대상의 최대 길이는 플랫폼마다 다릅니다. Linux에서는 4095바이트(일부 파일 시스템에서는 더 짧으며, 예를 들어 XFS에서는 1023바이트), macOS와 FreeBSD에서는 1023바이트, Windows에서는 약 8,000개의 UTF-16 코드 단위입니다. 생성할 수 없는 링크는 추출 시점에 처리됩니다(§ 12 추출).

link_target 필드 bytes에서 링크 대상을 디코딩하려면, bytes에서 엔트리 이름을 디코딩할 때와 같이 하십시오. 단, 결과는 링크 대상이며 그 대신 링크 대상의 제약이 적용됩니다.

링크 대상은 아카이브 모델 안에서 결코 해석되지 않습니다. 아카이브 값의 모든 경로는 여전히 단순한 구성 요소의 나열이며, 아카이브 값에서 경로를 조회할 때는 lstat가 그러하듯 링크 노드를 결코 따라가지 않습니다. 특히 어떤 노드도 링크 노드의 하위 노드가 아닙니다.

이로 인해 /etc를 가리키는 앞선 링크 a를 통해 a/passwd를 추출하는 고전적인 공격은 아카이브 모델에서 표현할 수 없습니다. 도구는 링크를 따라가는 조회를 제공할 수 있습니다. 다만 그 경우 따라가는 링크의 수를 제한해야 합니다.

링크 그룹은 동등성 비교에만 쓰이는 불투명한 값이며, 세그먼트와 세그먼트 로컬 정수의 쌍이거나 아카이브 전역 정수입니다(§ 11.3 link-group: 하드 링크). 두 아카이브 값은 둘 중 하나의 링크 그룹을 어떤 일대일 방식으로 이름을 바꾸어 동일해지는 경우에만, 그리고 그 경우에 한하여 같습니다.

4.2. 콘텐츠 블록

스페이서, 크기 힌트, 메타데이터, 미러 및 무시 블록을 제외한 모든 블록은 콘텐츠 블록이며, 이는 뒤따르는 메타데이터 블록에 의해 기술되어야 합니다. 메타데이터 블록과, 이전 메타데이터 블록(없으면 아카이브의 시작)과 그 메타데이터 블록 사이의 모든 콘텐츠 블록은 하나의 세그먼트를 이룹니다. 예외적으로, 메타데이터 블록이 없는 아카이브(§ 3.5 암호화되지 않은 아카이브)는 메타데이터 블록이 없는 단일 세그먼트를 이룹니다. 각 세그먼트는 자기 완결적이어야 합니다. 세그먼트 안의 블록에서 나오는 모든 참조는 인덱스에 의한 것이든 오프셋에 의한 것이든 같은 세그먼트 안의 블록을 가리켜야 합니다. 내부 임포트(§ 8.1 임포트 블록)는 블록이 아니라 경로를 통해 선행 세그먼트의 노드를 참조하며, 세그먼트가 다른 세그먼트의 내용을 사용할 수 있는 유일한 방법입니다.

콘텐츠 블록은 다음과 같이 분류됩니다.

사전, 모듈 및 리소스 블록은 각각 세그먼트 안에서 나타나는 순서대로 인덱스가 매겨집니다. 이 인덱스에는 두 가지 규칙이 사용됩니다.

4.2.1. 블록 순서

세그먼트 안의 블록은 공식 블록이 그보다 앞선 블록들에 의해 해석되고, 그 공식 블록을 평가하는 데 필요한 모든 데이터가 그 뒤에 오도록 정렬되어야 합니다.

이 규칙에 따라 공식 블록에서 비리프 블록으로의 모든 참조는 전방 참조가 됩니다. 따라서 비리프 블록을 스트리밍 방식으로 읽을 때 그것을 참조하는 모든 공식 블록은 이미 읽힌 상태이며, 압축 해제기는 해당 블록을 언제 폐기할 수 있는지 정확히 알 수 있습니다. 마찬가지로, 압축 해제기는 공식 블록이 참조하는 모든 비리프 블록을 읽은 뒤에는 그 공식 블록을 보관할 필요가 전혀 없습니다. 임포트가 많은 공식 블록에 의해 참조되는 임포트 블록은 그 공식 블록들보다 모두 뒤에 오므로, 스트리밍 압축 해제기는 해당 임포트 블록을 읽을 때까지 그 공식 블록들을 보관합니다. 압축기는 임포트를 여러 임포트 블록에 분산하여 이를 제한합니다.

리프 블록과, 그 뒤로 다음 리프 블록 또는 세그먼트의 끝까지 이어지는 모든 콘텐츠 블록은 하나의 파셀을 이룹니다. 비리프 블록 사이에 끼워 넣은 리프 블록이 없을 때, 파셀은 리프 블록, 그 리프 블록이 공식 블록인 경우 그것이 참조하는 비리프 블록, 그리고 경우에 따라 보조 블록으로 구성됩니다. 이 경우 압축 해제기는 파일의 파셀과 그 파일이 사용하는 보조 블록을 읽어 단일 파일을 읽을 수 있으며, 스트리밍 압축 해제기는 공식 블록을 그 파셀 너머까지 보관할 필요가 전혀 없습니다.

module formula non-leaf non-leaf leaf formula non-leaf metadata leaf metadata parcel parcel parcel parcel segment segment
두 개의 세그먼트와 네 개의 파셀로 이루어진 아카이브로, 각 세그먼트는 그 세그먼트를 기술하는 메타데이터 블록으로 끝나고 각 파셀은 리프 블록으로 시작합니다. 화살표는 공식 블록에서 비리프 블록으로의 참조(실선)와 모듈 블록으로의 참조(점선)를 나타냅니다. 모듈 블록은 해당 세그먼트의 첫 번째 리프 블록보다 앞에 오므로 어떤 파셀에도 속하지 않습니다. 사전 블록, 리소스 블록, 스페이서 블록은 생략되었으며, 비리프 블록 사이에 끼워 넣은 리프 블록은 없습니다.

4.2.2. 등록 사전과 등록 모듈

등록 사전은 Aqueous 사전 레지스트리(§ 15.5 Aqueous 사전 레지스트리)의 엔트리이고, 등록 모듈은 Aqueous 모듈 레지스트리(§ 15.6 Aqueous 모듈 레지스트리)의 엔트리입니다. 각 엔트리는 그 바이트의 SHA-512/256 해시 [FIPS180-4]인 등록 해시로 식별되며, 그 바이트는 등록 사전의 경우 사전 블록의 디코딩된 데이터(§ 5.2 사전 블록)이고, 등록 모듈의 경우 wasm_module(§ 7.1 모듈 블록)입니다.

블록 5016(사전 블록)과 블록 6016(모듈 블록)은 약칭 블록입니다. 약칭 블록의 data는 체크섬이 있는 Zstandard 데이터 대신 32바이트 길이의 등록 해시만으로 이루어지며, 약칭 블록은 디코딩된 데이터가 해당 엔트리의 바이트인 같은 종류의 블록을 나타냅니다:

두 레지스트리는 해시 함수를 공유하지만 엔트리는 공유하지 않으며, 약칭 블록은 자신과 같은 종류의 레지스트리에 대해서만 해석됩니다. 한 레지스트리의 엔트리를 안다고 해서 압축 해제기가 다른 레지스트리의 엔트리를 알게 되는 일은 결코 없습니다. 특히 블록 5016에 있는 등록 모듈의 해시는 모듈의 바이트로 이루어진 사전을 나타내지 않으며, 같은 바이트가 별도로 사전으로 등록된 경우에만 예외인데, 이때 둘은 서로 무관한 두 엔트리입니다.

약칭 블록에는 체크섬이 없으며, 등록 해시는 조회될 뿐 검증되지 않습니다. 따라서 해시의 손상은 한 번도 등록되지 않은 해시와 구별할 수 없으며, 두 경우 모두 잘못된 아카이브 실패가 아니라 미지원 실패가 됩니다.

레지스트리 엔트리는 결코 수정되거나 제거되지 않으므로 등록 해시는 계속 같은 바이트를 나타내며, 압축 해제기는 엔트리 중 일부만 알 수도 있습니다. 레지스트리를 분리해 두면 압축 해제기는 등록 모듈(§ 7.1 모듈 블록)의 바이트 대신 그 네이티브 구현을 갖출 수 있는데, 해시가 사전도 나타낼 수 있다면 이는 불가능할 것입니다. 손상된 해시는 압도적인 확률로 알 수 없는 해시로 판정되며, 약칭 블록의 미러 블록으로 이를 복구할 수 있습니다.

4.3. 메타데이터 블록

블록 1016부터 1716까지는 메타데이터 블록이며, 그 유형은 0001 0mss(이진수)입니다(§ 3.1.1 블록 유형). 이 블록의 데이터는 공유 외부 구조(§ 3.2 공유 외부 구조)를 사용하며, checksum_method로는 XXH64를 사용하고 compression_method는 m에 따라 정해집니다. m이 0이면 None이고, m이 1이면 Zstandard입니다.

압축 해제 후의 내부 구조는 다음과 같습니다:

이름 타입
previous_metadata_offset leb128
num_modules leb128
num_resources leb128
num_dictionaries leb128
num_directories leb128
num_files leb128
num_specials leb128
entries_size leb128 = entries_end - entries_start
optional_metadata_size leb128
module_offsets u(previous_metadata_offset)[num_modules]
resource_offsets u(previous_metadata_offset)[num_resources]
dictionary_offsets u(previous_metadata_offset)[num_dictionaries]
entry_parents u(num_directories)[num_directories + num_files + num_specials - 1]
file_params file_param[num_files]
entry_offsets u(entries_size)[num_directories + num_files + num_specials - 1]
entries_start offset
entries 엔트리[num_directories + num_files + num_specials]
entries_end offset
optional_metadata u8[optional_metadata_size]

모든 상대 오프셋은 현재 메타데이터 블록의 시작에서 거꾸로 측정됩니다. 상대 오프셋 o는 현재 메타데이터 블록보다 o 바이트 앞에서 시작하는 블록을 가리키며, 이는 오프셋 0이 유효하지 않음을 뜻합니다. 유효한 상대 오프셋은 모두 짝수이기도 합니다.

각 메타데이터 블록은 자신의 세그먼트에 있는 콘텐츠 블록, 즉 이전 메타데이터 블록이 있다면 그 뒤부터 현재 메타데이터 블록까지 나타나는 블록을 기술합니다. 이전 메타데이터 블록은 previous_metadata_offset을 통해 연결되며, 이 값은 해당 메타데이터 블록에 나타날 수 있는 최대 오프셋이기도 합니다. 이전 메타데이터 블록이 없으면 previous_metadata_offset은 암호화되지 않은 아카이브 구조(§ 3.5 암호화되지 않은 아카이브)에서 header_magic의 끝까지의 상대 오프셋에 1을 더한 값이며, 이로써 이 값은 결코 유효한 블록을 가리키지 않습니다.

num_modules, num_resources, num_dictionaries는 각각 세그먼트에 있는 모듈 블록, 리소스 블록, 사전 블록의 개수입니다. 이 블록들에 대한 상대 오프셋은 이후 나타나는 순서대로 각각 module_offsets, resource_offsets, dictionary_offsets로 기록됩니다.

메타데이터 블록은 0개 이상의 디렉터리, 0개 이상의 파일, 0개 이상의 특수 엔트리를 나타내며, 이들을 통틀어 엔트리라고 부르고 함께 번호를 매깁니다. 루트 디렉터리는 0번이고, 그 뒤의 디렉터리는 1번부터 num_directories번까지, 개별 파일은 (num_directories + 1)번부터 (num_directories + num_files)번까지, 특수 엔트리는 (num_directories + num_files + 1)번부터 (num_directories + num_files + num_specials)번까지 번호가 매겨집니다. 루트 디렉터리 외에 적어도 하나의 엔트리가 있어야 합니다. 즉 num_directories + num_files + num_specials ≥ 1이어야 합니다. entry_parents[k - 2]는 각 k ≥ 2에 대해 엔트리 k의 부모이며, 디렉터리가 순환을 이루지 않도록 k보다 작은 디렉터리, 즉 [0, min(k - 1, num_directories)] 범위에 속하는 디렉터리여야 합니다. 루트 디렉터리(엔트리 0)에는 부모가 없고 엔트리 1의 부모는 항상 루트 디렉터리이므로, 이들은 생략됩니다.

예를 들어 아카이브 전체의 선택적 메타데이터나 루트 디렉터리의 엔트리 추가 정보만 갱신하기 위해, 루트 디렉터리 외에 엔트리가 하나도 없는 세그먼트를 허용해야 하는지 여부입니다.

파일 매개변수(file_param)는 각 파일마다(디렉터리나 특수 엔트리에는 해당하지 않음) 나타나며, 다음으로 구성됩니다.

이름 타입 조건
file_dictionary u(num_dictionaries)
file_offset u(previous_metadata_offset)
file_output leb128 If file_offset이 홀수

file_dictionary는 file_offset이 가리키는 블록이 사용하는 사전을 1부터 시작하는 인덱스로 나타낸 것이며, 사전을 사용하지 않으면 0입니다. 따라서 file_dictionary = k는 dictionary_offsets[k - 1]에 있는 블록을 가리키며, num_dictionaries는 이 값이 가질 수 있는 최댓값입니다. 파일의 파셀을 시작하는 해당 리프 블록까지의 상대 오프셋이 file_offset으로 기록되며, 파일이 블록의 출력 0이 아닌 다른 출력이면 여기에 1을 더합니다. 상대 오프셋은 0일 수 없습니다. 모든 상대 오프셋은 짝수이므로 file_offset % 2로 어느 경우인지 알 수 있습니다: file_offset이 짝수이면 파일 출력 인덱스는 0이고, 홀수이면 file_output + 1입니다. 여러 파일이 같은 리프 블록과 출력을 가리킬 수 있으며, 이 경우 그 파일들은 같은 내용을 가집니다.

이것이 서로 하드 링크였던 파일들(§ 11.3 link-group: 하드 링크)을 포함하여, 아카이브가 같은 내용의 파일을 한 번만 저장하는 방식입니다. 이 방식은 파일당 file_param 하나와 엔트리 하나만 소요됩니다.

그다음 엔트리 구조(엔트리)가 각 디렉터리, 파일, 특수 엔트리마다 이 순서대로 나타나며, 다음으로 구성됩니다:

이름 타입 조건
entry_name u8[*] ≠ 0
entry_name_sentinel u8 = 0
original_size leb128 If 이 엔트리가 파일에 대한 것임
special_kind leb128 If 이 엔트리가 특수 엔트리에 대한 것임
link_target u8[*] ≠ 0 If 이 엔트리가 특수 엔트리에 대한 것이고 special_kind = 1임
link_target_sentinel u8 = 0 If 이 엔트리가 특수 엔트리에 대한 것이고 special_kind = 1임
device_major leb128 이 엔트리가 특수 엔트리에 대한 것이고 special_kind가 3 또는 4인 경우
device_minor leb128 이 엔트리가 특수 엔트리에 대한 것이고 special_kind가 3 또는 4인 경우
special_data u8[*] 이 엔트리가 특수 엔트리에 대한 것이고 special_kind가 4보다 큰 경우

각 엔트리의 이름은 entry_name에 따라 정해지며, entry_name은 § 4.1 아카이브 모델에 기술된 대로 엔트리 이름을 인코딩하고 그곳에서 디코딩됩니다. ZIP에서 온 엔트리 이름은 § 16.1 ZIP의 엔트리 이름에서 다룹니다.

특수 엔트리는 special_kind에 따라 결정되는, 파일이나 디렉터리가 아닌 노드를 나타냅니다:

special_kind 노드
0 선행 세그먼트 또는 베이스에서 같은 경로에 있는 노드를 제거하는 툼스톤 (§ 4.4.2 세그먼트 병합)
1 link_target을 대상으로 하는 링크 노드
2 종류가 "fifo"인 특수 노드
3 종류가 "char-device"이며 device_major와 device_minor를 장치 번호로 하는 특수 노드
4 종류가 "block-device"이며 device_major와 device_minor를 장치 번호로 하는 특수 노드
5 이상 이 명세의 향후 버전을 위해 예약된 미지 노드

link_target은 entry_name과 같은 인코딩을 사용합니다. special_data는 이 버전의 명세에서 해석되지 않으며, 그 길이는 entry_offsets에 있는 다음 엔트리의 시작 위치로 결정되고, 마지막 엔트리의 경우에는 entries_end로 결정됩니다.

특수 엔트리는 디렉터리와 마찬가지로 파일과 별도로 번호가 매겨지므로, file_params는 계속 엔트리 번호로 직접 인덱싱됩니다. 링크 대상은 노드 자체를 정의하며 무시될 수 없으므로, 선택 메타데이터가 아니라 엔트리에 저장됩니다. 메타데이터 블록은 전체가 한꺼번에 압축되므로, 비슷한 대상들은 잘 압축됩니다.

미지 종류는 잘못된 아카이브 실패가 아니므로, 압축 해제기는 향후 정의될 종류를 사용하는 아카이브에서도 나머지 부분을 여전히 추출할 수 있습니다. 모든 특수 엔트리는 entry_offsets 덕분에 그 종류를 알지 못해도 건너뛸 수 있습니다. 미지 노드는 디렉터리 노드가 아닌 다른 모든 노드와 마찬가지로 같은 경로에 있는 앞선 노드를 대체하므로(§ 4.4.2 세그먼트 병합), 향후 정의될 종류가 세그먼트 병합 방식을 바꿀 수 없습니다. 이것이 툼스톤을 이 버전에서 첫 번째 종류로 정의하는 이유입니다. 툼스톤을 미지 노드로 취급하는 압축 해제기는 툼스톤이 제거하려던 노드 자리에 툼스톤을 그대로 남겨 두게 됩니다.

엔트리 배열은 바이트 수가 지정되지 않은 유일한 부분이며, 따라서 그 크기를 entries_size로 미리 알 수 있습니다. 그 밖의 모든 필드는 처음의 leb128 필드들을 읽은 뒤 임의 접근할 수 있습니다.

위의 상대 오프셋에 대한 예외로, entry_offsets는 entries_start로부터 측정합니다. 엔트리 1은 항상 entries_start에서 시작하므로 그 오프셋은 생략되며, 각 k ≥ 2에 대해 entry_offsets[k - 2]는 엔트리 k의 시작 위치까지의 오프셋입니다. 이 값들은 엔트리의 실제 위치와 일치해야 합니다.

optional_metadata는 선택 메타데이터(§ 11 선택적 메타데이터)에서 설명합니다.

4.4. 아카이브 해석

바이트열 input, 선택적인 패스프레이즈 passphrase, 그리고 아카이브 값인 선택적인 베이스 base가 주어졌을 때 아카이브를 해석하려면 다음과 같이 합니다.

  1. input이 ENCRYPTED_HEADER_MAGIC 또는 ENCRYPTED_MIRRORED_HEADER_MAGIC으로 시작하면 다음과 같이 합니다.

    1. input 전체를 암호화 아카이브 구조(§ 9 암호화 아카이브)로 파싱합니다. 파싱에 실패하면 잘못된 아카이브 실패를 반환합니다.

    2. header_checksum이 있고 일치하지 않으면 잘못된 아카이브 실패를 반환합니다.

    3. lg_work_factor가 구현에서 정의한 한도를 초과하면 리소스 실패를 반환합니다.

    4. 패스프레이즈 passphrase가 주어지지 않았거나, [AGE]에 설명된 대로 passphrase로 encrypted_file_key에서 파일 키를 언래핑할 수 없으면, 복호화 실패를 반환하십시오.

    5. 풀어낸 파일 키를 사용하여, [AGE]에 설명된 대로 재구성한 age 헤더에 대해 age_header_mac을 검증하고 age 페이로드를 복호화합니다. 둘 중 하나라도 실패하면 잘못된 아카이브 실패를 반환합니다.

    6. input을 복호화된 페이로드로 설정합니다. 이 페이로드는 § 9 암호화 아카이브의 추가 제약 조건을 충족해야 하며, 그렇지 않으면 잘못된 아카이브 실패를 반환합니다.

  2. 모든 블록의 data를 파싱하지 않고 바이트열로 취급하여 input 전체가 암호화되지 않은 아카이브 구조(§ 3.5 암호화되지 않은 아카이브)로 파싱되고, 블록 중 어느 하나의 타입이 필수이면서 이 명세에서 정의하지 않은 것이거나, 블록 중 어느 하나가 data가 32바이트이고 압축 해제기가 그 등록 해시를 알지 못하는 약칭 블록이면(§ 4.2.2 등록 사전과 등록 모듈), 미지원 실패를 반환합니다.

  3. input 전체를 암호화되지 않은 아카이브 구조(§ 3.5 암호화되지 않은 아카이브)로 파싱합니다. 파싱에 실패하면 잘못된 아카이브 실패를 반환합니다.

  4. input이 오버레이 아카이브이고 base가 주어지지 않았으면 베이스 실패를 반환합니다.

  5. input이 독립 아카이브이면, 베이스 base를 root가 children으로 빈 맵을, extra로 null을 가지는 디렉터리 노드이고 extra가 빈 맵인 아카이브 값으로 설정하십시오.

  6. 블록에 메타데이터 블록이 없으면 다음과 같이 합니다.

    1. content를 블록에 있는 모든 리프 블록의 파일 내용을 나타나는 순서대로, 각각 0부터 차례로 모든 출력 인덱스에 대해(공식 블록은 모든 출력, 데이터 블록은 유일한 출력) 연결한 것으로 둡니다.

    2. 루트가 content를 내용으로, null을 링크 그룹으로, null을 extra로 갖는 파일 노드이고, extra가 빈 맵인 아카이브 값을 반환합니다.

  7. 베이스 base의 root가 디렉터리 노드이면, root를 그 복사본으로 두십시오. 그렇지 않으면 root를 빈 맵을 children으로, null을 extra로 갖는 디렉터리 노드로 둡니다.

  8. extra를 베이스 base의 extra의 사본으로 둡니다.

  9. 블록 안의 각 세그먼트 segment에 대해, 나타나는 순서대로 다음을 수행합니다:

    1. segment root를 세그먼트 segment를 해석한 결과로 둡니다.

    2. 세그먼트 루트 segment root를 root에 병합합니다.

    3. 세그먼트의 선택적 메타데이터의 최상위 맵에 있는 entry-extras 이외의 각 키 k와 값 v에 대해, extra[k]를 v로 설정합니다.

  10. root를 root로, extra를 extra로 하는 아카이브 값을 반환합니다.

아카이브 베이스는 5단계 이후의 base이며, 세그먼트의 선행 결과는 9단계에서 해당 세그먼트가 해석되기 직전의 root이거나, 메타데이터 블록이 없는 아카이브의 경우 아카이브 베이스의 root입니다. 이들은 각각 외부 임포트와 내부 임포트가 참조하는 대상입니다(§ 8.2 임포트 해석). 주어진 베이스 base는 독립 아카이브의 해석에 영향을 주지 않습니다.

오버레이 아카이브의 첫 번째 세그먼트의 선행 결과는 그 베이스이므로, 선행 세그먼트가 베이스의 파일을 대체하거나 제거하지 않은 한 내부 임포트도 그 파일을 읽을 수 있습니다. 외부 임포트는 선행 세그먼트가 파일을 대체한 이후에도 주어진 그대로의 베이스를 읽습니다. 메타데이터 블록이 없는 오버레이 아카이브는 그 베이스를 단일 파일로 대체하며, 예를 들어 베이스로 주어진 단일 파일을 패치하는 데 사용됩니다.

이 알고리즘의 입력은 정확히 header_magic의 첫 번째 바이트부터 footer_size_copy의 마지막 바이트까지의 아카이브입니다.

파일은 앞쪽 또는 뒤쪽 바이트가 있는 아카이브를 포함할 수 있으며, 압축 해제기는 아카이브 위치 찾기(§ 3.6 아카이브 찾기)를 통해 이를 제거합니다.

모든 콘텐츠 블록은 뒤따르는 메타데이터 블록에 의해 기술되어야 하므로(§ 4.2 콘텐츠 블록), 메타데이터 블록이 있는 아카이브는 마지막 메타데이터 블록 뒤에 어떤 콘텐츠 블록도 가질 수 없습니다. 압축 해제기가 블록을 앞쪽으로 스캔하거나 오프셋을 뒤쪽으로 따라가는 방식 중 어느 쪽으로든 각 세그먼트를 찾을 수 있도록, previous_metadata_offset와 metadata_backward_offset는 § 4.3 메타데이터 블록 및 § 3.5 암호화되지 않은 아카이브에 기술된 대로 blocks를 파싱하여 찾은 메타데이터 블록을 가리켜야 합니다.

4.4.1. 세그먼트 해석하기

메타데이터 블록이 metadata block인 segment를 세그먼트 해석하려면 다음을 수행합니다:

  1. meta를 메타데이터 블록 metadata block의 디코딩된 데이터를 그 내부 구조(§ 4.3 메타데이터 블록)로 파싱한 것으로 둡니다.

  2. d를 meta의 num_directories, f를 num_directories + num_files, n을 num_directories + num_files + num_specials라고 합니다.

  3. nodes를 빈 맵을 children으로, 엔트리 0에 대한 엔트리 extra를 extra로 하는 단일 디렉터리 노드를 포함하는 리스트라고 합니다.

  4. [1, n]의 각 k에 대해 다음을 수행합니다:

    1. entry를 엔트리 meta.entries[k - 1]로 둡니다.

    2. name을 엔트리 entry.entry_name을 디코딩한 결과로 둡니다.

    3. parent를 k = 1이면 0, 그렇지 않으면 meta.entry_parents[k - 2]라고 합니다.

    4. k ≤ d이면, node를 빈 맵을 children으로 하는 디렉터리 노드라고 합니다.

    5. 그렇지 않고 k ≤ f이면 다음을 수행합니다:

      1. param을 meta.file_params[k - d - 1]이라고 합니다.

      2. block을 메타데이터 블록 metadata block으로부터 상대 오프셋 param.file_offset - param.file_offset % 2에 있는 블록으로 둡니다. 이 블록은 세그먼트 segment 안의 리프 블록이어야 합니다.

      3. param.file_dictionary가 0이면 블록 block은 사전을 사용해서는 안 됩니다. 그렇지 않으면, block은 meta.dictionary_offsets[param.file_dictionary - 1]에 있는 사전 블록을 사용해야 합니다.

      4. param의 파일 출력 인덱스에 대한 블록 block의 파일 콘텐츠를 content라 합니다. 그 길이는 엔트리 entry.original_size여야 합니다.

      5. content를 내용으로, null을 링크 그룹으로 갖는 파일 노드를 node라고 합니다.

      6. 엔트리 k에 대한 엔트리 extra에 값 g를 가진 link-group이 있으면, g ≥ 0인 경우 node.링크 그룹을 segment와 g의 쌍으로, 그렇지 않은 경우 g로 설정하십시오.

    6. 그렇지 않고 entry.special_kind가 0이면, node를 툼스톤으로 하십시오. 엔트리 k에 대한 엔트리 추가 정보는 null이어야 합니다.

    7. 그렇지 않고 엔트리 entry.special_kind가 1이면:

      1. 엔트리 entry.link_target을 디코딩한 결과를 target이라 합니다.

      2. target을 대상으로 갖는 링크 노드를 node라고 합니다.

    8. 그렇지 않고 엔트리 entry.special_kind가 2이면, node를 종류가 "fifo"이고 장치가 null인 특수 노드라 합니다.

    9. 그렇지 않고 엔트리 entry.special_kind가 3 또는 4이면, node를 종류가 각각 "char-device" 또는 "block-device"이고 장치가 (entry.device_major, entry.device_minor)인 특수 노드라 합니다.

    10. 그렇지 않으면, node를 종류가 엔트리 entry.special_kind이고 데이터가 entry.special_data인 미지 노드라 합니다.

    11. node가 툼스톤이 아니면, node의 extra를 엔트리 k에 대한 엔트리 extra로 설정하십시오.

    12. children을 nodes[parent]의 자식으로 하십시오. children[name]은 존재해서는 안 됩니다.

    13. children[name]을 node로 설정하십시오.

    14. node를 nodes에 추가하십시오.

  5. nodes[0]을 반환하십시오.

§ 4.3 메타데이터 블록에 따라 모든 parent는 k보다 작고 d 이하이므로, nodes[parent]는 항상 nodes에 이미 있는 디렉터리 노드입니다.

엔트리 k에 대한 엔트리 추가 정보는 세그먼트의 선택적 메타데이터에 있는 entry-extras의 0부터 시작하는 인덱스 k에 있는 요소이며, 인덱스 0은 루트 디렉터리에 해당합니다. entry-extras 또는 해당 요소가 존재하지 않으면 null입니다. entry-extras는 루트 디렉터리를 포함한 엔트리 수, 즉 num_directories + num_files + num_specials + 1보다 많은 요소를 가져서는 안 됩니다.

4.4.2. 세그먼트 병합

later를 earlier에 디렉터리 노드 병합하려면 다음을 수행합니다.

  1. later.children의 각 name → 노드 node에 대해 다음을 수행합니다.

    1. earlier.자식[name]을 existing이라고 하며, 그것이 존재하지 않으면 null로 합니다.

    2. 노드 node가 툼스톤이면, earlier.children[name]이 존재하는 경우 이를 제거합니다.

    3. 그렇지 않고 node가 디렉터리 노드이면 다음을 수행합니다.

      1. existing이 디렉터리 노드가 아니면, existing을 빈 맵을 자식으로, null을 추가 정보로 갖는 디렉터리 노드로 설정하고, earlier.자식[name]을 existing으로 설정합니다.

      2. 노드 node를 existing에 병합합니다.

    4. 그렇지 않으면 earlier.children[name]을 노드 node로 설정합니다.

  2. later.추가 정보가 null이 아니면, earlier.추가 정보를 그것으로 설정합니다.

결과적으로 여러 세그먼트에 나타나는 디렉터리는 그 자식들의 합집합을 가지며, 그 밖의 노드나 다른 종류의 노드로 대체된 디렉터리는 그 자손과 함께 통째로 대체됩니다. 특히 링크 노드로 대체된 디렉터리는 모든 자손을 잃으므로, 결과에서 어떤 노드도 링크 노드의 자손이 아닙니다. 툼스톤은 자신의 경로에 있는 노드를 그 자손과 함께 제거하며, 그러한 노드가 없으면 아무것도 하지 않습니다. 툼스톤은 이전에 존재하지 않던 디렉터리 아래에 있더라도 결과에 결코 남지 않습니다. 이후 세그먼트에서 extra가 null인 디렉터리는 기존 extra를 유지하며, 대신 빈 맵 {}으로 대체할 수 있습니다.

디렉터리는 그 아래에 새 파일을 담기 위해 이후 세그먼트에 필연적으로 다시 나타나며, 이 때문에 디렉터리는 기존 디렉터리를 대체하지 않고 기존 디렉터리와 병합됩니다. 같은 이유로, 다시 나타나는 디렉터리는 자신의 엔트리 extra를 반복할 필요가 없습니다.
예를 들어 A에서 B로 자식이 완전히 바뀌는 디렉터리는 자신의 노드를 유지하고 A에는 있지만 B에는 없는 이름에 대해 툼스톤을 받으며, B의 이름은 평소와 같이 노드를 대체하거나 추가합니다. 디렉터리에 대한 툼스톤은 그 디렉터리 자체가 결과에 없음을 나타내며, 하나의 툼스톤이 하위 트리 전체를 제거합니다. 따라서 오버레이 아카이브의 베이스 전체를 대체하려면 베이스와 새 트리가 달라지는 최상위 수준에서 베이스에만 있는 각 이름에 대해 툼스톤이 필요합니다.

5. 저장된 데이터

5.1. 데이터 블록

블록 2016부터 3716까지와 4016부터 4B16까지는 데이터 블록입니다. 데이터 블록은 compression_method가 None이 아니면 압축된 블록이고, 그렇지 않으면 압축되지 않은 블록입니다.

데이터 블록의 type은 001kk css 리프 블록의 경우 이진수로 (§ 3.1.1 블록 유형) 이와 같고,0100 kkss 비리프 블록의 경우 다음과 같으며, 여기서:

그 데이터는 다음으로 구성됩니다:

이름 타입 조건
dictionary_index leb128 has_dictionary인 경우
compressed_data compressed_data compression_method가 None이 아닌 경우
uncompressed_data uncompressed_data compression_method가 None인 경우

사전을 사용하는 경우, 그 0부터 시작하는 인덱스는 dictionary_index로 지정됩니다. 리프 데이터 블록의 경우 메타데이터 블록도 사전을 file_dictionary로 제공하며, 이는 블록을 읽기 전에 사용할 수 있습니다. 비리프 데이터 블록의 경우 dictionary_index가 사전에 대한 유일한 참조입니다. compressed_data와 uncompressed_data는 위와 같이 결정된 compression_method 및 checksum_method와 함께 공유 외부 구조(§ 3.2 공유 외부 구조)를 사용합니다. uncompressed_data는 해석되지 않은 원시 바이트열을 나타냅니다.

압축된 블록은 하나의 압축 해제 단위입니다. 즉, 그 내용의 어느 부분을 읽든 처음부터 압축을 해제해야 합니다. 원하는 임의 접근 단위보다 큰 내용은 여러 블록으로 분할됩니다.

5.2. 사전 블록

블록 5016부터 5716까지는 사전 블록이며, 그 type은 이진수로 0101 0tss입니다(§ 3.1.1 블록 유형). 그 데이터는 체크섬이 있는 Zstandard 데이터(§ 3.2 공유 외부 구조)이지만, 블록 5016은 등록 사전에 대한 약칭 블록(§ 4.2.2 등록 사전과 등록 모듈)이라는 점이 예외입니다.

t는 사전이 엔트로피 테이블을 가지는지 여부(has_entropy_tables)를 결정합니다. 0이면 false이고, 1이면 true입니다.

압축 해제 후의 내부 구조는 다음과 같습니다.

이름 타입 조건
entropy_tables u8[*] has_entropy_tables인 경우
repeat_offsets u32le[3] has_entropy_tables인 경우
content u8[*]

사전 블록의 디코딩된 데이터는 그것을 사용하는 블록들을 위한 사전이며, Magic_Number와 Dictionary_ID가 없는 Zstandard 사전([RFC8878], 5절)에 해당합니다.

두 경우 모두 content는 사전의 Content이며, 이는 시퀀스가 선행 데이터로 참조할 수 있는 사전의 유일한 부분입니다. content는 길이가 최소 8바이트여야 합니다.

Magic_Number와 Dictionary_ID는 Zstandard 프레임에서만 의미가 있고 프레이밍은 이 형식이 수행하므로 생략됩니다. libzstd를 사용하는 구현은 엔트로피 테이블이 있는 사전을 로드하기 위해 (임의의 0이 아닌 Dictionary_ID와 함께) 이들을 앞에 덧붙일 수 있습니다. 그 경우 구현은 어떤 시퀀스도 앞에 덧붙인 바이트나 entropy_tables를 참조하지 않도록 보장해야 하는데, libzstd의 일부 압축 해제 경로는 이를 검사하지 않기 때문입니다.
엔트로피 테이블은 내용과 독립적으로 학습될 수 있으며(예: ZDICT_finalizeDictionary) 주로 작은 블록에 이점이 있습니다. 1–20 KB 파일의 파일별 블록으로 측정한 결과 크기가 0.4–26% 줄었지만, 64 KB 이상의 블록에서는 이점이 사라졌습니다. libzstd의 빠른 전략(예: 1.5.7의 레벨 1–3)은 블록 자체의 테이블이 더 작은 경우에도 모든 블록에 사전의 엔트로피 테이블을 재사용합니다. 사전의 반복 오프셋이 초기값인 경우, 압축기는 같은 content를 원시 내용 사전으로 사용하여 블록을 한 번 더 압축하고 더 작은 결과를 선택함으로써 이를 피할 수 있습니다. 그렇게 압축된 블록은 사전의 엔트로피 테이블을 결코 참조하지 않기 때문입니다.

5.3. 블록 내용

공유 외부 구조(§ 3.2 공유 외부 구조)를 사용하는 블록의 디코딩된 데이터는 그 compression_method와 checksum_method가 주어졌을 때 다음과 같이 결정됩니다.

데이터 블록의 저장된 내용은 리프 블록인지 여부와 관계없이 그 compressed_data 또는 uncompressed_data의 디코딩된 데이터입니다. 비리프 항목의 저장된 내용은 그것을 제공하는 비리프 데이터 블록의 저장된 내용이거나, 그것을 제공하는 임포트의 임포트된 내용입니다(§ 8.2 임포트 해석). 사전을 사용하는 데이터 블록은 세그먼트 내 dictionary_index 위치의 사전 블록을 사용합니다.

출력 인덱스 i에 대한 리프 블록의 파일 내용은 다음과 같습니다.

6. 공식

6.1. 공식 블록

블록 7016부터 7716까지는 공식 블록이며, 그 type은 0111 0dss 이진수로 다음과 같습니다(§ 3.1.1 블록 유형).

d는 사전 사용 여부(has_dictionary)를 결정합니다. 0이면 거짓이고, 1이면 참입니다.

그 데이터는 다음으로 구성됩니다.

이름 타입 조건
dictionary_index leb128 has_dictionary인 경우
num_outputs leb128
formula_data compressed_data

사전을 사용하는 경우, 데이터 블록에서와 마찬가지로 그 0부터 시작하는 인덱스를 dictionary_index로 지정합니다.

num_outputs는 공식 블록의 출력 개수이며, 1 이상이어야 합니다(§ 6.3 공식 평가).

모든 명령은 피연산자가 정하는 개수만큼 항목을 꺼내고 넣으므로, 출력 개수는 어떤 모듈도 적용하지 않고 명령만으로 결정됩니다. num_outputs는 formula_data의 압축을 해제하기 전에 이 값을 알려 줍니다. 예를 들어 file_output을 검사하거나 출력을 그것을 사용하는 파일로 스트리밍하는 압축 해제기에 알려 줍니다.
formula_data는 체크섬이 있는 Zstandard 데이터입니다(§ 3.2 공유 외부 구조).
공식 블록은 항상 Zstandard를 사용하며, Zstandard에서도 raw Zstandard 블록으로 데이터를 그대로 저장할 수 있습니다. 여러 작은 공식 블록은 공유 압축 컨텍스트가 없는 것을 보완하기 위해 사전을 공유할 것으로 예상됩니다.

공식 블록은 파일의 내용을, 그 뒤에 오는 비리프 항목에 대한 식으로 기술하며, 이 식은 그 앞에 오는 모듈과 리소스로 평가됩니다. formula_data의 내부 구조는 공식 데이터(§ 6.2 공식 데이터)에서 설명합니다.

6.2. 공식 데이터

이 절에서는 공식 블록(§ 6.1 공식 블록)에 있는 formula_data의 압축을 해제한 후의 내부 구조를 설명합니다.

공식은 그것이 참조하는 블록의 목록들과 그 뒤에 오는 스택 기계의 명령으로 구성됩니다. 각 명령은 스택에서 0개 이상의 바이트열을 꺼내고 0개 이상의 바이트열을 넣으며, 마지막 명령 이후 스택에 남아 있는 바이트열이 공식의 출력입니다.

이름 타입
num_blocks leb128
block_ordinal_deltas leb128[num_blocks]
num_resources leb128
resource_indices leb128[num_resources]
num_modules leb128
module_indices leb128[num_modules]
instructions 명령[*]

block_ordinal_deltas는 공식 블록이 참조하는 비리프 항목을 나열합니다. 이름과 달리 num_blocks, block_ordinal_deltas, slice_block은 비리프 항목을 세며, 비리프 항목은 임포트를 제외하면 블록입니다. 세그먼트 안에서 공식 블록 뒤에 오는 비리프 블록이 제공하는 비리프 항목 사이에서 나타나는 순서에 따른 이들의 0부터 시작하는 서수는 o0 = block_ordinal_deltas[0]이고, i ≥ 1에 대해 oi = oi - 1 + block_ordinal_deltas[i] + 1이므로, 서수는 순증가합니다. i번째 참조된 항목은 서수가 oi인 비리프 항목이며, 이 항목은 존재해야 합니다.

resource_indices와 module_indices는 공식 블록이 참조하는 리소스 블록과 모듈 블록을 세그먼트 안에서의 0부터 시작하는 인덱스로 나열합니다(§ 4.2 콘텐츠 블록). 각 인덱스는 존재하는 블록을 가리켜야 하며, 그 블록은 § 4.2.1 블록 순서에 따라 공식 블록보다 앞에 옵니다.

명령이 formula_data의 나머지를 채웁니다. 위 목록에 나열된 각 항목 또는 블록은 적어도 하나의 명령에서 사용되어야 하며, 그 경우에만 공식 블록이 참조한다고 합니다.

비리프 항목은 오프셋이 아니라 서수로 참조되므로, 블록을 재배치하거나 다시 압축하거나 미러 블록 같은 다른 블록을 삽입해도 그 뒤의 블록을 참조하는 공식 블록이 무효가 되지 않습니다. 오프셋을 사용하면 결과가 블록을 찾는 방식에 따라 달라지기도 합니다. 블록 중간을 가리키는 오프셋은 예를 들어 비압축 블록 안에 저장된 아카이브에서처럼 그 위치에서 유효해 보이는 블록을 여전히 찾을 수 있지만, 블록을 순서대로 읽는 압축 해제기는 그 오프셋에서 블록을 찾지 못합니다. 일반적인 파셀에서는 서수가 연속적이므로 block_ordinal_deltas는 대부분 0입니다. 리소스와 모듈은 절대 인덱스로 참조되며, 이 인덱스는 여러 공식 블록에서 반복되므로 공유 사전으로 잘 압축됩니다.

각 명령은 u8 타입의 옵코드와 그 뒤에 오는 피연산자로 구성되며, 피연산자의 구조는 다음 절에서 설명하는 바와 같이 옵코드에 따라 결정됩니다. opcode는 다음 중 하나여야 합니다.

옵코드 명령 팝 푸시
0016 Nop 0 0
0116 Literal 0 1
0F16 Swap 0 0
1016 Slice 0 1
1116 Apply apply_num_inputs apply_num_outputs
1216 Concatenate concat_count 1
1316 Pack pack_count 1
1416 Split 1 split_count
1516 Patch 2 1
2016 체크섬 0 0

옵코드는 역할에 따라 분류됩니다. 0016부터 0F16까지는 공식 자체에 포함된 데이터를 푸시하거나 스택을 재배열하고, 1016부터 1F16까지는 참조 항목이나 스택으로부터 바이트열을 계산하며, 2016부터 2F16까지는 이를 검사합니다. 그 밖의 옵코드는 예약되어 있습니다.

같은 세그먼트 안의 앞선 블록에 대한 참조(예: 세그먼트 앞부분에 저장된 파일을 패치하기 위한 참조)는 아직 정의되지 않았습니다. 다른 파일에 대한 참조는 명령이 아니라 임포트(§ 8.1 임포트 블록)로 이루어지며, 선행 세그먼트에 있는 파일의 내용에 대한 참조는 내부 임포트로 이루어집니다. 후보안은 공식 블록 앞에 놓이는 두 번째 비리프 항목 목록으로, Slice 명령이 참조 항목과 같은 방식으로 읽으며, 다른 공식 블록의 출력이 필요할 때는 그 출력을 참조하는 대신 해당 공식 블록의 명령을 복사합니다. 이를 위해서는 스트리밍 압축 해제기가 어떤 블록을 유지해야 하는지 알 수 있도록 참조되는 블록보다 앞서 선언되는 이러한 참조 거리의 상한과, 세그먼트를 처음부터 읽지 않고도 앞선 블록의 위치를 찾는 방법이 필요합니다(docs/xform-design.md §7).

formula_data는 임의 접근이 불가능하며, 압축 해제기는 공식의 어떤 부분이든 평가하기 전에 이를 전부 읽고 압축 해제할 것으로 예상됩니다. 목록에 대한 인덱스나 스택 위 바이트열의 개수처럼 평가된 바이트열에 의존하지 않는 formula_data에 대한 모든 요구 사항은 아무것도 평가하지 않고 명령을 한 번 훑는 것만으로 검사할 수 있습니다. 같은 훑기 과정에서 스택 위 모든 바이트열의 길이가 결정되므로, formula_data는 다른 형식의 탐색 테이블과 같은 방식으로 출력 내 임의 접근을 위한 인덱스 역할을 합니다. 참조 항목은 공식 블록 뒤에 오는 블록을 각각의 size 필드에 따라 건너뛰어 위치를 찾으며, 임포트 블록의 항목은 해당 블록을 디코딩하여 위치를 찾습니다.

인덱스는 참조되는 비리프 항목의 수에 비례하여 선형으로 커지지만, 대부분의 명령이 같은 형태와 크기를 공유하므로 압축이 잘 됩니다. formula_data 전체의 압축 해제는 파일당 한 번만 드는 비용입니다.

마지막 항목이 맨 위인 바이트열의 리스트인 스택에서 명령을 실행하려면, 다음 절들에서 해당 옵코드에 대해 설명하는 단계를 수행하십시오. n개의 항목을 팝하려면 스택에 적어도 n개의 항목이 있어야 하며, 마지막 n개의 항목이 스택에서 제거되어 스택에 있던 순서대로 사용됩니다.

6.2.1. Nop 명령

옵코드 0016은 Nop 명령을 나타내며, 이 명령은 피연산자가 없고 아무 동작도 하지 않습니다.

6.2.2. Literal 명령

옵코드 0116은 Literal 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다.

이름 타입
literal_size leb128
literal_data u8[literal_size]

literal_data를 푸시합니다.

6.2.3. Swap 명령

옵코드 0F16는 Swap 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다.

이름 타입
swap_depth leb128

스택에는 swap_depth개보다 많은 항목이 있어야 합니다. 이 명령은 스택의 맨 위 항목을 그보다 swap_depth개 아래에 있는 항목과 교환합니다.

6.2.4. Slice 명령

옵코드 1016은 Slice 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다.

이름 타입
slice_block leb128
slice_start leb128
slice_length leb128
  1. slice_block은 num_blocks보다 작아야 합니다.

  2. c를 slice_block번째 참조 항목의 저장된 내용이라고 합니다.

  3. slice_start + slice_length는 c의 길이 이하여야 합니다.

  4. c에서 [slice_start, slice_start + slice_length) 범위의 바이트를 푸시합니다.

6.2.5. Apply 명령

옵코드 1116은 Apply 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다.

이름 타입
apply_module leb128
apply_num_resources leb128
apply_resources leb128[apply_num_resources]
apply_params_size leb128
apply_params u8[apply_params_size]
apply_memory_limit leb128
apply_num_inputs leb128
apply_num_outputs leb128
apply_output_sizes leb128[apply_num_outputs]
  1. apply_module는 num_modules보다 작아야 하며, apply_resources의 각 항목은 num_resources보다 작아야 합니다.

  2. apply_num_outputs는 1 이상이어야 합니다.

  3. apply_num_inputs개의 항목을 꺼내 inputs로 둡니다.

  4. outputs를 module_indices[apply_module]에 있는 모듈 블록을 inputs, apply_resources의 각 r에 대해 resource_indices[r]에 있는 리소스 블록, apply_params, apply_output_sizes, apply_memory_limit와 함께 적용한 결과로 둡니다(§ 7.3 모듈 실행).

  5. outputs의 각 항목을 순서대로 넣습니다.

6.2.6. Concatenate 명령

옵코드 1216은 Concatenate 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다.

이름 타입
concat_count leb128

이 명령은 concat_count개의 항목을 꺼내고, 이들을 순서대로 연결한 결과를 넣습니다.

6.2.7. Pack 명령

옵코드 1316은 Pack 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다.

이름 타입
pack_count leb128

pack_count개의 항목을 꺼내고 그것들의 패킹된 바이트열을 순서대로 넣습니다(§ 6.3.1 패킹된 바이트열).

6.2.8. Split 명령

옵코드 1416은 Split 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다:

이름 타입
split_count leb128
split_sizes leb128[split_count]
  1. split_count는 1 이상이어야 합니다.

  2. 항목 x를 꺼냅니다. split_sizes의 합은 x의 길이와 같아야 합니다.

  3. split_sizes의 각 n에 대해 순서대로 x의 다음 n바이트를 넣습니다.

6.2.9. Patch 명령

옵코드 1516는 Patch 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다:

이름 타입
patch_size leb128
  1. 항목 2개를 source와 patch로 꺼냅니다.

  2. patch는 다른 바이트 없이 하나 이상의 Zstandard 블록으로 구성되어야 하며, 마지막 블록만 블록 헤더에서 최종 블록으로 표시되고, 각 블록은 최대 128 KiB의 데이터를 담습니다.

  3. output을 source를 원시 내용 사전의 Content로 하여 patch를 Zstandard 블록 [RFC8878]의 시퀀스로 압축 해제한 결과라고 합니다. 즉, 엔트로피 테이블은 빈 상태로 시작하고 반복 오프셋은 초깃값(1, 4, 8)으로 시작합니다. 모든 매치의 오프셋은 source의 길이와 이미 압축 해제된 바이트 수를 더한 값 이하여야 합니다.

  4. output의 길이는 patch_size여야 합니다.

  5. output을 넣습니다.

Patch 명령은 zstd --patch-from과 마찬가지로 이전 바이트열을 원시 내용 사전으로 하여 Zstandard 블록을 압축 해제하므로, 압축 해제기는 이를 위해 자신의 Zstandard 압축 해제기를 재사용합니다. 앞선 바이트열은 사전 블록이 아니라 스택 위의 피연산자이므로, 공식이 계산한 임의의 바이트열, 예를 들어 임포트나 모듈이 그로부터 분할한 스트림이 될 수 있습니다. 패치 자체는 일반적으로 압축되지 않은 비리프 데이터 블록의 슬라이스이며, 그 체크섬은 저장된 패치를 대상으로 하고, 출력 체크섬은 결과를 대상으로 합니다.
Patch 명령에는 Zstandard 프레임이 없으므로 Window_Size도 없습니다. 매치는 source의 어떤 바이트나 지금까지의 출력의 어떤 바이트든 참조할 수 있으며, § 3.2.1 Zstandard 압축 방식에서 보장하는 Window_Size의 한도는 적용되지 않습니다. 압축 해제기는 어차피 두 바이트열을 모두 보유하고 있으므로, 먼 거리를 참조하더라도 추가 메모리가 들지 않습니다. libzstd에서는 이것이 source로 ZSTD_DCtx_refPrefix를 호출한 뒤 프레임을 압축 해제하는 것에 해당하며, 그 릴리스 빌드는 오프셋을 프리픽스의 시작 위치에 대해서만 검사합니다. Window_Size를 강제하는 압축 해제기는 해당 프레임에 대해 그 검사를 해제해야 합니다. libzstd를 사용하는 압축기는 zstd --patch-from과 마찬가지로 ZSTD_c_windowLog를 source와 출력을 포괄할 만큼 크게 설정합니다.

6.2.10. Checksum 명령

옵코드 2016은 Checksum 명령을 나타냅니다. 피연산자는 다음과 같이 구성됩니다.

이름 타입
checksum_value u64le

스택은 비어 있어서는 안 됩니다. 스택 최상단의 XXH64 체크섬은 checksum_value와 같아야 합니다.

Checksum 명령은 실행될 때 스택 최상단에 있는 바이트열을 포괄하며, 그 바이트열은 Swap 명령으로 이동되어도 같은 항목으로 유지됩니다. 출력 체크섬은 공식 블록의 출력을 포괄하는 Checksum 명령입니다.

6.3. 공식 평가

공식 블록 block을 평가하려면 다음과 같이 하십시오.

  1. 블록 block의 formula_data의 디코딩된 데이터를 § 6.2 공식 데이터에서와 같이 파싱한 것을 data로 둡니다.

  2. 스택 stack을 바이트열의 빈 리스트로 둡니다.

  3. data의 명령의 각 insn에 대해 순서대로, § 6.2 공식 데이터에서 해당 옵코드에 대해 기술된 대로 스택 stack에서 insn을 실행합니다.

  4. 스택 stack의 항목 수는 블록 block의 num_outputs와 같아야 합니다.

  5. stack을 블록 block의 출력으로 반환하며, 출력에는 스택의 맨 아래부터 순서대로 0부터 번호를 매깁니다.

스택 위의 모든 바이트열은 다른 명령의 입력이나 출력으로서 정확히 한 번 사용되므로, 공식은 출력을 루트로 하는 명령의 트리입니다. 다만 여러 항목을 푸시하는 명령, 즉 Apply 명령이나 Split 명령은 그 항목들이 공유합니다. 바이트열은 완전히 평가된 값으로 정의됩니다. 지연 평가, 스트리밍 평가 또는 부분 평가는 구현 전략이며 결과를 바꾸지 않습니다(§ 13.2 부분 접근). Apply 명령의 출력과 같은 중간 바이트열을 포괄하는 Checksum 명령은, 전체 출력은 계산하지 않고 그 바이트열만 계산한 압축 해제기도 검증할 수 있습니다.

6.3.1. 패킹된 바이트열

바이트열의 리스트 items의 패킹된 바이트열은 다음을 연결한 것이며, 여기서 n은 items의 개수입니다.

바이트열 p는, 그러한 것이 있다면, 정확히 하나의 리스트 items의 패킹된 바이트열입니다. p를 언패킹하려면 다음과 같이 합니다.

  1. p가 8바이트보다 짧으면 실패를 반환합니다.

  2. n을 p의 처음 8바이트를 u64le로 읽은 값으로 하고, h를 8 × (n + 1)로 합니다. h가 p의 길이를 초과하면 실패를 반환합니다.

  3. [0, n)의 각 i에 대해 ei를 오프셋 8 × (i + 1)부터 시작하는 p의 8 바이트를 u64le로 읽은 값이라고 하고, e-1을 0이라고 합니다.

  4. [0, n)의 어떤 i에 대해서든 ei - 1 > ei이면 실패를 반환합니다.

  5. h + en - 1이 p의 길이가 아니면 실패를 반환합니다.

  6. [0, n)의 각 i에 대해 [h + ei - 1, h + ei) 범위에 있는 p의 바이트들로 이루어진 리스트를 반환합니다.

모듈의 입력 개수는 그 엔트리 포인트에 의해 고정되므로(§ 7.3 모듈 실행), 패킹된 바이트열을 사용하면 모듈이 실행 파일의 섹션마다 하나씩처럼 적용할 때마다 개수가 달라지는 바이트열의 리스트를 단일 입력으로 받을 수 있습니다. 경계는 저장되지 않습니다. Pack 명령은 아무것도 평가하지 않고도 알 수 있는 항목들의 길이로부터 경계를 도출합니다(§ 6.2 공식 데이터). 그래도 모듈은 입력을 언패킹하고 실패 시 실패해야 합니다. 같은 바이트는 다른 어떤 방식으로든, 예를 들어 Literal 명령 뒤에 Concatenate 명령을 두는 방식으로도 주어질 수 있기 때문입니다. 고정 폭의 경계를 사용하면 모듈은 다른 항목을 파싱하지 않고도 어떤 항목이든 찾을 수 있으며, 경계는 항목들 앞에 위치하므로 부분 모듈이 경계를 먼저 요청할 수 있습니다.

반대 방향으로, 바이트열의 리스트를 생성하는 모듈은 그것들을 단순히 연결한 결과를 출력하며, Split 명령이 이를 공식에 선언된 크기에 따라 분리합니다. 그러한 모듈의 출력은 Split 명령 뒤에 Pack 명령을 두어 패킹된 바이트열을 받는 다른 모듈로 전달됩니다.

7. 모듈

7.1. 모듈 블록

블록 6016부터 6316까지는 모듈 블록이며, 그 유형은 0110 00ss입니다(§ 3.1.1 블록 유형). 이들의 데이터는 체크섬이 있는 Zstandard 데이터입니다(§ 3.2 공유 외부 구조). 단, 블록 6016은 등록 모듈에 대한 약칭 블록입니다(§ 4.2.2 등록 사전과 등록 모듈).

압축 해제 후의 내부 구조는 다음과 같습니다.

이름 타입
wasm_module u8[*]

wasm_module은 바이너리 형식 [WASM-CORE-3]의 WebAssembly 모듈, 즉 일반적인 .wasm 파일의 내용으로, 매직 00 61 73 6D와 버전 01 00 00 00(16진수)으로 시작하며 디코딩된 데이터 전체에 걸쳐 있습니다. 이 모듈은 § 7.3 모듈 실행에서 허용하는 기능으로 이루어진 모듈로서 디코딩 및 검증되어야 하며, 그곳에서 정한 프로필과 인터페이스를 준수해야 합니다. 그렇지 않으면 잘못된 아카이브 실패가 발생합니다. 요컨대, 모듈은 자체 메모리를 정의하지 않고 모든 메모리와 호스트 함수를 모듈 이름 aqr에서 임포트하며, 시작 함수가 없고, 호스트가 적용할 때마다 한 번씩 호출하는 단일 엔트리 포인트를 익스포트합니다. 사용자 정의 섹션은 허용되며 결과에 영향을 주지 않습니다. 이 형식은 모듈 주위에 아무것도 덧붙이지 않으므로, 디코딩된 데이터는 어떤 WebAssembly 도구로도 검사하고 실행할 수 있으며, 등록 모듈에 대한 모듈 블록은 해당 엔트리와 같은 wasm_module을 가집니다.

모듈 블록의 모듈 해시는 그 wasm_module의 SHA-512/256 해시 [FIPS180-4]이며, 이는 등록 모듈의 등록 해시이기도 합니다(§ 4.2.2 등록 사전과 등록 모듈). 모듈의 모든 적용은 wasm_module과 그 인수에만 의존하므로(§ 7.3 모듈 실행), 압축 해제기는 모듈 해시를 알고 있는 모듈을 wasm_module을 인스턴스화하는 대신 자체 구현(예: 네이티브 코드)으로 적용할 수 있습니다. 단, 그 결과는 잘못된 아카이브 실패를 포함하여 정의와 구별할 수 없어야 합니다. 그러한 압축 해제기는 모듈이 약칭 블록으로 주어지든 전체로 주어지든 모듈의 바이트도 WebAssembly 엔진도 필요로 하지 않습니다. 압축 해제기는 자신의 적합성 수준이 허용하는 바에 따라 모듈의 적용을 거부할 수 있으며(§ 13.1 적합성 수준), 이 경우 리소스 실패가 발생합니다.

압축기는 등록 모듈에 약칭 블록을 사용하여 모듈의 크기를 절약할 수 있지만, 그 대가로 해당 엔트리를 모르는 압축 해제기에서는 미지원 실패가 발생합니다. 전체로 주어진 모듈은 WebAssembly 엔진을 갖춘 어떤 압축 해제기로도 읽을 수 있습니다.
압축 해제기는 각 모듈을 어떻게 적용할지 선택하며, 어떤 선택도 모듈을 네이티브 코드로 변환할 것을 요구하지 않습니다. 수준 1에서의 등록 모듈처럼 모듈 해시를 알고 있는 모듈에 대해서는 다음 중 하나를 할 수 있습니다.
  • 자체 구현을 적용합니다. 이는 가장 빠르지만 알고 있는 모든 모듈마다 만들어야 합니다. 결과가 실패를 포함하여 모듈과 구별할 수 없어야 하므로, 새로 작성하기보다는 자동화된 수단(예: wasm2c)으로 모듈을 변환하는 편이 바람직합니다. 또는

  • 해시가 충분히 강력하여 다른 어떤 모듈도 같은 해시를 가질 수 없으므로, 격리하지 않고 WebAssembly 엔진으로 모듈을 인스턴스화합니다. 이 방식은 전체로 주어진 모듈에 대해서는 해시만 필요로 하며, 바이트는 약칭 블록에 대해서만 필요로 합니다.

모듈 해시를 알지 못하는 모듈에 대해서는 다음 중 하나를 할 수 있습니다.

예를 들어, 수준 1의 압축 해제기는 모든 등록 모듈을 네이티브로 구현한다면 엔진도 샌드박스도 필요하지 않으며, 수준 2의 압축 해제기는 둘 다 필요하지만 네이티브 구현은 필요하지 않습니다.

7.2. 리소스 블록

블록 6416부터 6716까지는 리소스 블록이며, 그 타입은 이진수로 0110 01ss입니다(§ 3.1.1 블록 유형). 이들의 데이터는 체크섬이 있는 Zstandard 데이터입니다(§ 3.2 공유 외부 구조).

압축 해제 후의 내부 구조는 해석되지 않은 바이트열이며, 이를 참조하는 공식 블록에 의해 모듈에 제공됩니다(§ 6.2 공식 데이터, § 7.3 모듈 실행).

7.3. 모듈 실행

이 섹션은 잠정적이며 docs/xform-design.md §5를 따릅니다.

모듈은 wasm_module로 저장된 WebAssembly 모듈 [WASM-CORE-3]이며, [WASM-CORE-3]의 부록에 정의된 임베딩 인터페이스를 통해 인스턴스화되고 호출됩니다. 모듈은 다음 프로필을 준수해야 하며, 그렇지 않으면 잘못된 아카이브 실패가 발생합니다:

Zstandard Window_Size(§ 3.2.1 Zstandard 압축 방식)에 대해 보장되는 227바이트(128 MiB) 한도를 메모리 한도에도 적용해야 하는지 여부.

모듈의 모든 임포트는 모듈 이름 aqr에서 다음 이름 중 하나로 이루어집니다. 임포트와 익스포트의 이름에는 이들을 하나의 네임스페이스로 링크하는 툴체인과 호스트에서의 충돌을 피하기 위해 aqr_ 접두사가 붙습니다. 그 밖의 임포트는 잘못된 아카이브 실패입니다.

aqr_memory

메모리로, 작업 메모리입니다.

aqr_params

매개변수의 메모리입니다.

aqr_resourcei

i번째 리소스의 메모리이며, 여기서 i는 앞자리 0이 없는 십진수입니다.

aqr_inputk

k번째 입력의 메모리이며, 여기서 k는 앞자리 0이 없는 십진수입니다.

aqr_outputj

j번째 출력의 메모리이며, 여기서 j는 앞자리 0이 없는 십진수입니다.

aqr_request, aqr_release, aqr_commit

아래에 정의된 부분 모듈의 함수입니다. 전체 모듈은 이것들을 임포트해서는 안 됩니다.

aqr_heartbeat

아래에 정의된 함수입니다.

aqr_spawn, aqr_result, aqr_join, aqr_take

§ 7.3.1 태스크에 정의된 태스크의 함수입니다.

aqr_chan, aqr_send, aqr_recv, aqr_close

§ 7.3.1 태스크에 정의된 채널의 함수입니다.

aqr_log

아래에 정의된 진단 함수입니다.

메모리의 최솟값과 최댓값이 모두 ⌈L / 65536⌉ 페이지이면, 그 메모리는 길이 L에 크기가 맞춰진 것입니다.

입력 바이트열의 리스트 inputs, 리소스 블록의 리스트 resources, 매개변수 바이트열 params, 선언된 출력 크기의 리스트 sizes, 그리고 메모리 한도가 주어졌을 때 module에 대해 모듈을 적용하려면:

  1. sizes는 비어 있어서는 안 됩니다.

  2. 임포트되는 모든 aqr_resourcei는 i < resources의 리소스 수를 만족해야 하고, 임포트되는 모든 aqr_inputk는 k < inputs의 입력 수를 만족해야 하며, 임포트되는 모든 aqr_outputj는 j < sizes의 크기 수를 만족해야 합니다.

  3. 임포트된 메모리를 생성하십시오. 각 메모리는 처음에 0으로 채워집니다:

    • 작업 메모리는 임포트 타입의 최소 크기를 가집니다.

    • 매개변수의 메모리는 params의 길이에 크기가 맞춰진 것이며, 오프셋 0부터 params를 담습니다.

    • i번째 리소스의 메모리는 그 저장된 내용 c에 크기가 맞춰진 것이며, 오프셋 0부터 c를 담습니다.

    • k번째 입력의 메모리는 inputs[k]의 길이에 크기가 맞춰집니다. module이 전체 모듈이면, 그 메모리는 오프셋 0부터 inputs[k]를 담습니다. 그렇지 않으면 Wk를 그 메모리의 바이트 단위 크기라고 하고, k번째 입력의 가시 범위를 [0, 0)이라고 합니다.

    • j번째 출력의 메모리는 sizes[j]에 맞게 크기가 정해집니다. module이 부분 모듈이면, Wout,j를 그 메모리의 바이트 단위 크기라고 합니다.

  4. outputs를 sizes와 같은 개수의 빈 바이트열로 이루어진 리스트라고 합니다.

  5. 메모리들의 바이트 단위 총 크기가 메모리 한도를 초과하면 리소스 실패를 반환합니다. 메모리 한도는 애플리케이션의 메모리(그 태스크의 메모리 포함)와 그 채널에 대기 중인 메시지의 총 크기에 적용됩니다.

  6. 아래의 메모리와 호스트 함수로 module을 인스턴스화합니다.

  7. i64 타입의 인수를 다음 순서대로 전달하여 엔트리 포인트를 호출합니다: params의 길이, resources(리소스)의 각 항목의 저장된 내용의 길이, inputs의 각 항목의 길이와 sizes의 각 항목. 엔트리 포인트는 이 매개변수들을 가져야 하며 결과는 없어야 합니다. 진입점이 트랩되거나 아래의 호스트 함수 중 하나라도 오류를 알리면 잘못된 아카이브 실패를 반환합니다. 진입점이 메모리 한도나 구현 정의 한도를 초과하면 리소스 실패를 반환합니다. 애플리케이션의 태스크 중 하나라도 실패하면 그 실패를 반환합니다(여러 개가 실패하면 그중 어느 것의 실패든 반환합니다). 애플리케이션의 모든 인스턴스가 대기하면 잘못된 아카이브 실패를 반환합니다. 진입점이 반환되면, 진입점이 생성한 모든 태스크는 회수된 상태여야 합니다.

  8. module이 전체 모듈이면, 각 outputs[j]를 j번째 출력의 메모리가 임포트된 경우 그 메모리의 처음 sizes[j] 바이트로, 그렇지 않으면 sizes[j]개의 0 바이트로 설정합니다.

  9. 각 outputs[j]의 길이는 sizes[j]여야 합니다.

  10. outputs를 반환합니다.

부분 모듈에서 데이터보다 작은 윈도, 즉 링 버퍼. 이는 최대값이 있는 임포트 타입을 허용할 것입니다.

memory.grow 명령은 애플리케이션의 메모리(그 태스크의 메모리와 그 채널에 대기 중인 메시지 포함)의 총 크기가 메모리 한도를 초과하게 될 때 메모리 한도를 초과하며, 그렇지 않으면 작업 메모리의 임포트 타입의 최대값이 허용하는 경우 성공해야 합니다.

각 애플리케이션은 모듈을 새로 인스턴스화하므로, 이전 애플리케이션에서 남는 것은 없습니다. 작업 메모리를 제외한 모든 메모리의 크기는 그 데이터의 길이로 고정되므로, 애플리케이션의 선언에는 윈도가 필요하지 않습니다. 데이터에 따라 달라지는 윈도가 필요한 모듈은 그 바이트들을 작업 메모리에 보관합니다. 전체 모듈의 출력 메모리는, 오프셋 sizes[j]부터의 바이트가 0으로 채워져 있는 한 결과가 같으므로, 그 출력을 소비하는 모듈에 전체 모듈의 입력 메모리로 전달할 수 있습니다.

호스트 함수는 다음과 같이 정의됩니다. 여기서 lok와 hik는 k번째 입력의 가시 범위를 나타내고, k, j, ptr, len은 i32 타입이며, 모든 오프셋은 i64 타입입니다. 태스크가 호출하면 aqr_request, aqr_release, aqr_commit은 오류를 알립니다.

aqr_request(k, hi)

k번째 입력은 임포트되어야 합니다. hi는 [hik, inputs[k]의 길이] 범위에 있어야 하며, hi - lok는 최대 Wk여야 합니다. [hik, hi)의 각 오프셋 o에 대해, inputs[k][o]를 k번째 입력의 메모리의 o % Wk 위치에 씁니다. 그런 다음 hik를 hi로 설정합니다.

aqr_release(k, lo)

k번째 입력은 임포트되어야 합니다. lo는 [lok, hik] 범위에 있어야 합니다. lok를 lo로 설정합니다.

aqr_commit(j, hi)

j번째 출력은 임포트되어야 합니다. hi는 [outputs[j]의 길이, sizes[j]] 범위에 있어야 하며, hi - outputs[j]의 길이는 최대 Wout,j여야 합니다. [outputs[j]의 길이, hi) 범위의 각 오프셋 o에 대해, j번째 출력의 메모리에서 o % Wout,j 위치의 바이트를 outputs[j]에 덧붙입니다.

aqr_heartbeat()

아무 동작도 하지 않습니다.

aqr_log(ptr, len)

아무 동작도 하지 않습니다. 구현은 작업 메모리의 오프셋 ptr부터 len바이트를 진단 메시지로 표시할 수 있으며, 이는 아카이브에서 온 신뢰할 수 없는 데이터입니다. 다만 그렇게 하는 것이 애플리케이션에 영향을 주어서는 안 되며, 범위가 경계를 벗어나는 경우에도 마찬가지입니다. 이것은 유일한 진단 함수입니다. 모듈은 aqr_log 다음에 트랩을 발생시켜 실패를 보고합니다.

호스트 함수의 MUST 요구 사항 중 어느 하나라도 위반하면 모듈 호출에 오류로 알려집니다.

호스트는 부분 모듈의 입력 메모리를 aqr_request 안에서만 쓰고 출력 메모리를 aqr_commit 안에서만 읽으므로, 모든 메모리의 내용은 모듈이 수행한 호출의 순서에만 의존합니다. 구현은 결과가 이 정의와 구별할 수 없는 한, 요청된 바이트를 사용할 수 있을 때까지 aqr_request에서, 소비자가 따라잡을 때까지 aqr_commit에서 블록할 수 있습니다.

7.3.1. 태스크

태스크는 모듈의 독자적인 인스턴스로, 자신을 생성한 인스턴스의 작업 메모리 사본 위에서 실행되며 바이트열, 즉 자신의 결과를 돌려줍니다. 애플리케이션의 인스턴스와 모든 태스크는 애플리케이션의 인스턴스입니다. 각 인스턴스는 처음에 비어 있는 핸들의 리스트를 가지며, 각 핸들은 한 번에 생성된 태스크의 리스트입니다. 태스크는 보류 중, 실행 중, 완료됨(결과와 함께), 실패함(잘못된 아카이브 실패 또는 리소스 실패와 함께), 또는 회수됨 중 하나의 상태입니다.

인스턴스가 생성한 태스크 중 동시에 실행 중인 것은 최대 모듈의 태스크 슬롯(모듈의 사용자 정의 섹션 aqr_task_slots) 수만큼입니다. 태스크는 생성된 때부터 시작될 때까지 보류 중입니다. 그 인스턴스가 생성한 태스크 중 실행 중인 것이 태스크 슬롯보다 적을 때마다, 생성된 순서(핸들 순, 그다음 인덱스 순)에 따라 그중 보류 중인 첫 번째 태스크가 시작됩니다. 그 태스크는 그때부터 실행 중이며, 실행됩니다. 실행 중인 태스크는 완료되거나 실패하면 실행 중 상태가 끝납니다. 보류 중인 태스크는 아직 애플리케이션의 인스턴스가 아닙니다. 메모리가 없으며 대기하지 않습니다.

채널은 애플리케이션의 인스턴스들을 연결하며, 최대 용량만큼의 메시지(바이트열)로 이루어진 큐, 닫힘 여부, 그리고 처음에는 없는 인스턴스인 송신자와 수신자로 구성됩니다. 애플리케이션은 처음에 비어 있는 채널의 리스트를 가집니다. 인스턴스가 종료되면(반환하거나 실패하면), 그 인스턴스가 송신자인 모든 채널이 닫힙니다. 인스턴스는 aqr_join, aqr_send 또는 aqr_recv 안에 있고 기다리는 조건이 성립하지 않는 동안 대기합니다.

바이트열 S, arg, i가 주어졌을 때 태스크를 실행하려면 다음과 같이 합니다:

  1. 임포트되는 메모리를 생성합니다. 작업 메모리는 S.length / 65536 페이지와 그 임포트 타입의 최대값을 가지며, 다른 모든 메모리는 최소값과 최대값으로 0을 가집니다.

  2. 메모리와 호스트 함수로 모듈을 인스턴스화한 다음, 작업 메모리의 내용을 S로 설정합니다.

  3. arg와 i로 태스크 엔트리 포인트를 호출합니다. 트랩이 발생하거나 어떤 호스트 함수가 오류를 알리면, 태스크는 잘못된 아카이브 실패로 실패합니다. 메모리 한도나 구현 정의 한도를 초과하면, 태스크는 리소스 실패로 실패합니다.

  4. 반환된 후에는 그것이 생성한 모든 태스크가 회수됨 상태여야 하며, 그렇지 않으면 태스크는 잘못된 아카이브 실패로 실패합니다.

  5. 태스크는 aqr_result로 마지막에 설정된 결과를, 설정된 것이 없으면 빈 바이트열을 결과로 하여 완료됩니다.

태스크의 호스트 함수는 이를 호출하는 인스턴스 I에 대해 다음과 같이 정의되며, 여기서 arg, n, h, i, ptr, dst, len은 부호 없는 i32 타입입니다:

aqr_spawn(arg, n)

S를 I의 작업 메모리의 내용이라고 합니다. I의 핸들에 n개의 새 대기 중 태스크로 이루어진 리스트를 추가합니다. 그중 (0부터 센) i번째 태스크는 (태스크 슬롯이 허용하는 대로) 시작되면 S, arg, i를 받아 태스크를 실행합니다. 그리고 I의 핸들 중에서 그 리스트의 인덱스를 반환합니다.

aqr_result(ptr, len)

I는 태스크여야 하며, ptr + len은 최대 그 작업 메모리의 크기여야 합니다. I의 결과를 그 작업 메모리의 오프셋 ptr부터 len 바이트로 설정합니다.

aqr_join(h, i)

I의 h번째 핸들의 i번째 태스크는 존재해야 하며 회수된 상태가 아니어야 합니다. 그 태스크가 완료되거나 실패할 때까지 대기합니다. 그 태스크가 실패했다면 인스턴스 I는 그 실패로 실패합니다. 그렇지 않으면 그 결과의 길이를 반환합니다.

aqr_take(h, i, dst, len)

I의 h번째 핸들의 i번째 태스크는 완료(조인)된 상태여야 하고, len은 최대 그 결과의 길이여야 하며, dst + len은 최대 I의 작업 메모리의 크기여야 합니다. 그 결과의 처음 len 바이트를 I의 작업 메모리의 오프셋 dst부터 복사하며, 그 태스크는 회수됩니다.

채널의 호스트 함수는 다음과 같이 정의됩니다. 여기서 c, ptr, dst, len, cap은 부호 없는 i32 타입이고, 채널 C는 애플리케이션의 (0부터 센) c번째 채널로서 존재해야 하며, I에 대해 C의 한쪽 끝을 취한다는 것은 다음을 뜻합니다: 그 끝이 없음이면 그 끝을 I로 설정하고, 그렇지 않으면 그 끝은 I여야 합니다.

aqr_chan(cap)

cap은 1 이상이어야 합니다. 애플리케이션의 채널에 용량이 cap인 새 채널을 추가하고, 그 인덱스를 반환합니다.

aqr_send(c, ptr, len)

I에 대해 C의 송신자를 취합니다. C는 닫혀 있어서는 안 되며, ptr + len은 I의 작업 메모리 크기 이하여야 합니다. 대기 중인 메시지가 C의 용량보다 적어질 때까지 기다린 다음, I의 작업 메모리에서 오프셋 ptr부터 len 바이트를 큐에 넣습니다.

aqr_recv(c, dst, cap)

I에 대해 C의 수신자를 취합니다. 메시지가 큐에 들어오거나 C가 닫힐 때까지 대기합니다. 큐에 메시지가 없으면 −1(0xFFFFFFFF)을 반환합니다. 그렇지 않으면 M을 첫 번째 메시지라고 합니다. M의 길이가 cap을 초과하면 M을 큐에 남겨 둔 채 그 길이를 반환합니다. 그렇지 않으면 dst + M의 길이는 I의 작업 메모리 크기 이하여야 합니다. M을 큐에서 꺼내 I의 작업 메모리의 오프셋 dst부터 복사하고, 그 길이를 반환합니다.

aqr_close(c)

I에 대해 C의 송신자를 취하고, C를 닫습니다.

태스크는 자신을 생성한 인스턴스와 시작 시 받은 작업 메모리의 사본, 돌려주는 결과, 그리고 둘이 양 끝을 이루는 채널의 메시지 외에는 아무것도 공유하지 않으며, 채널의 각 끝은 하나의 인스턴스에 속합니다: 인스턴스들은 Kahn 프로세스 네트워크이므로, 출력은 태스크가 언제, 어떤 순서로, 또는 한 번에 몇 개씩 실행되는지에 의존하지 않습니다. 구현은 대기 중인 인스턴스가 다른 인스턴스의 실행을 막지 않는 한, 태스크를 시작 이후 언제든 어떤 스레드에서든 실행할 수 있습니다. 태스크 슬롯은 메모리 한도를 포함하여 애플리케이션의 메모리를 제한합니다: 보류 중인 태스크는 한도 아래의 메모리를 차지하지 않으며(구현이 그 태스크를 위해 S에서 보관하는 것은 구현 자신의 몫입니다), 더 많은 단계로 된 파이프라인처럼 한 인스턴스의 태스크를 태스크 슬롯보다 많이 동시에 필요로 하는 애플리케이션은 실행 중인 인스턴스가 모두 보류 중인 태스크 하나를 기다릴 때마다 잘못된 아카이브입니다. 인스턴스가 생성하는 태스크는 다른 태스크가 무엇을 하든 생성된 순서대로 시작하므로, 그런 일이 일어나는지 여부 역시 태스크가 언제 실행되는지에 의존하지 않습니다. 채널은 송신자가 그 채널에 송신했거나 그 채널을 닫은 경우에만 송신자의 종료로 닫히므로, 채널에 메시지를 보내지 않을 수도 있는 인스턴스는 그 채널을 직접 닫습니다. 작업 메모리를 제외한 모든 메모리는 태스크에게 비어 있으므로, 태스크가 다루는 데이터는 작업 메모리에 있습니다; 태스크의 전역 변수는 새 인스턴스의 전역 변수입니다. 한 번의 생성으로 만들어진 태스크들은 같은 사본에서 시작하며, 구현은 쓰기 시 복사 방식으로 이 사본을 태스크 간에 공유할 수 있습니다. 메모리 한도를 초과하는지 여부는 동시에 실행 중인 태스크 수에 따라 달라질 수 있으며, 이는 리소스 실패만을 결정합니다.

작업 메모리 이외의 메모리를 읽기 전용으로 태스크에 보이게 해야 하는지 여부(이 경우 그 메모리에 쓰는 명령을 거부해야 합니다), 그리고 태스크에서의 부분 모듈의 윈도입니다.

8. 오버레이 아카이브와 임포트

8.1. 임포트 블록

블록 5816부터 5F16까지는 임포트 블록이며, 이는 세그먼트 외부에 있는 파일의 바이트 범위를 제공하는 비리프 블록입니다. 그 타입은 다음과 같으며 0101 1ess(이진 표기)(§ 3.1.1 블록 유형)이며, 이들의 데이터는 체크섬이 있는 Zstandard 데이터입니다(§ 3.2 공유 외부 구조).

e는 출처(외부)를 결정합니다. 0이면 선행 세그먼트이고, 1이면 베이스입니다.

임포트 블록은 external이 거짓이면 내부 임포트 블록이고, 그렇지 않으면 외부 임포트 블록입니다. 외부 임포트 블록은 오버레이 아카이브 이외의 아카이브에 나타나서는 안 됩니다 (§ 3.5 암호화되지 않은 아카이브).

압축 해제 후의 내부 구조는 다음과 같습니다:

이름 타입
num_imports leb128 ≠ 0
imports 임포트[num_imports]

각 임포트는 다음으로 구성됩니다:

이름 타입
import_path u8[*] ≠ 0
import_path_sentinel u8 = 0
import_offset leb128
import_size leb128
import_checksum u8[32]

내부 또는 외부 임포트 블록에 있는 임포트는 각각 내부 임포트 또는 외부 임포트입니다. 각 임포트는 하나의 비리프 항목을 제공하므로, 임포트 블록은 imports의 순서대로 num_imports개의 항목을 제공합니다. 항목의 저장된 내용은 임포트를 해석하여 얻는 그 임포트의 임포트된 내용으로, import_path에서 찾은 파일의 [import_offset, import_offset + import_size) 범위의 바이트이며, 다음과 같습니다.

import_path는 2F16 (/)으로 구분된 0개 이상의 구성 요소로 이루어진 시퀀스입니다. 각 구성 요소는 entry_name과 같은 인코딩을 사용하며, 디코딩된 이름은 엔트리 이름과 같은 제한을 받으므로 어떤 구성 요소도 비어 있을 수 없습니다. 빈 import_path는 루트 자체를 나타내며, 베이스가 단일 파일이면 루트는 파일입니다.

import_checksum은 임포트된 내용의 SHA-512/256 해시 [FIPS180-4]입니다.

엔트리 이름에는 /가 포함될 수 없고 . 또는 ..일 수도 없으므로, import_path를 나누는 방식은 모호하지 않으며 어떤 경로도 루트 바깥의 것을 가리킬 수 없습니다. 경로는 그것을 사용하는 파일의 디렉터리가 아니라 루트를 기준으로 합니다. 임포트를 참조하는 공식 블록은 메타데이터 블록이 해당 파일에 이름을 붙이기 전에 평가되며, 하나의 리프 블록을 서로 다른 디렉터리의 파일들이 공유할 수 있기 때문입니다. 같은 블록 안의 경로들이 공유하는 접두사는 잘 압축됩니다.
임포트는 파일 전체가 아니라 바이트 범위를 가리킵니다. 따라서 큰 파일의 일부를 읽는 압축 해제기는 실제로 임포트된 부분만 읽고 검증합니다. 같은 파일의 여러 범위는 각각 별개의 임포트입니다.
import_checksum은 베이스로부터 무엇이든 평가하기 전에 잘못되었거나 수정된 베이스를 식별합니다. 이는 잘못된 베이스로 인해 평가가 실패하지 않는 경우가 많기 때문에 중요합니다. 예를 들어 Patch 명령은 다른 소스로부터 다른 출력을 생성합니다. 이 명세의 다른 체크섬과 달리 이 값은 암호학적 해시를 사용합니다. 베이스는 아카이브의 일부가 아니므로, 그렇지 않으면 다른 수단(예: 서명)으로 인증된 아카이브라도 그 결과는 인증되지 않습니다. 이 값은 임포트 하나당 32바이트를 차지하며, 이 바이트는 압축되지 않습니다.
임포트는 임포트된 내용을 여러 스트림으로 나누는 모듈, 즉 변환의 전처리 부분을 통해 사용되는 경우가 많습니다. 그러면 스트림들을 다시 합치는 모듈을 적용하기 전에 변경된 스트림만 패치하면 됩니다. 그러한 모듈은 압축기가 결정하여 매개변수로 전달한 분할만 수행하면 됩니다.

8.2. 임포트 해석

세그먼트에 있는 임포트 블록의 import를 임포트 해석하려면 다음을 수행합니다.

  1. import.import_path가 비어 있지 않으면 이를 모든 2F16에서 구성 요소로 분할하고, 각 구성 요소를 엔트리 이름으로 디코딩한 결과를 names라 합니다. 어떤 구성 요소라도 디코딩에 실패하면 잘못된 아카이브 실패를 반환합니다.

  2. import가 내부 임포트이면 세그먼트의 앞선 결과를, 그렇지 않으면 아카이브 베이스의 루트를 node라 합니다.

  3. names의 각 name에 대해 순서대로 다음을 수행합니다:

    1. node는 디렉터리 노드여야 하며, node.children[name]이 존재해야 합니다.

    2. node를 node.children[name]로 설정합니다.

  4. node는 파일 노드여야 합니다.

  5. import.import_offset + import.import_size는 node.content의 길이 이하여야 합니다.

  6. [import.import_offset, import.import_offset + import.import_size) 범위에 있는 node.content의 바이트를 content라 합니다.

  7. content의 SHA-512/256 해시 [FIPS180-4]는 import.import_checksum과 같아야 합니다.

  8. content를 import의 임포트된 내용으로 반환합니다.

이 알고리즘의 MUST 중 어느 하나라도 위반하면 오버레이 아카이브에서는 베이스 실패이고, 독립 아카이브에서는 잘못된 아카이브 실패입니다.

아카이브 값에서의 다른 모든 조회와 마찬가지로, 링크 노드는 디렉터리 노드가 아니므로 임포트 해석은 링크 노드를 따라가지 않습니다. 독립 아카이브에서는 선행 결과가 아카이브만으로 결정되므로, 실패는 아카이브의 속성입니다. 오버레이 아카이브에서는 내부 임포트의 경우에도 선행 결과가 베이스에 의존합니다. 내부 임포트의 경로는 선행 세그먼트가 대체하지 않은 베이스 부분으로 이어질 수 있기 때문입니다.

8.3. 베이스 읽기

파일 시스템에서 베이스를 읽는 것은 추출과 마찬가지로 아카이브 모델의 범위 밖입니다. 베이스를 읽는 압축 해제기는 디렉터리 또는 단일 파일을 아카이브 값에 대응시키며, 다음 요구 사항을 따릅니다.

아카이브는 베이스의 어떤 바이트든 결과로 복사할 수 있으며, 내용을 추측한 임포트의 베이스 실패는 그 추측이 맞는지를 알려 줍니다. 따라서 신뢰할 수 없는 아카이브는 자신의 결과에 대한 것만큼 베이스에 대한 접근 권한을 얻으며, 이 때문에 베이스는 명시적으로 주어진 경우에만 사용됩니다. 압축 해제기는 자신이 읽은 베이스의 파일을 보고하는 것이 권장됩니다.

압축 해제기는 오버레이 아카이브의 결과를 그 베이스 디렉터리에 쓸 수 있습니다. 즉, 제자리에서 적용할 수 있습니다. 이 경우에는 다음과 같습니다.

일반적으로 쓰기 순서를 정하는 것만으로는 충분하지 않습니다. 예를 들어 두 파일을 맞바꾸는 경우처럼, 한 파일이 다른 파일로부터 패치되고 그 다른 파일이 다시 앞의 파일로부터 패치될 수 있습니다.

9. 암호화 아카이브

아카이브는 암호화되어 있을 수 있으며, 이 경우 아카이브는 ENCRYPTED_HEADER_TYPE 타입의 블록 형태 헤더와 그 뒤에 오는 암호화된 청크들로 구성됩니다. 암호화 아카이브가 ENCRYPTED_MIRRORED_HEADER_MAGIC으로 시작하면 미러링된 것이며, 이 경우 헤더의 복사본이 암호화된 청크들 사이에 배치됩니다.

이름 타입 조건
checksum_start offset
encrypted_header_magic u8[8] ∈ {ENCRYPTED_HEADER_MAGIC, ENCRYPTED_MIRRORED_HEADER_MAGIC}
lg_work_factor u8 ∈ [0C16, 3F16)
scrypt_ephemeral_secret u8[16]
encrypted_file_key u8[32]
age_header_mac u8[32]
payload_nonce u8[16]
checksum_end offset
header_checksum u64le If mirrored
chunks u8[*]

lg_work_factor부터 payload_nonce까지의 97바이트를 헤더 필드라고 합니다.

header_checksum은 checksum_start부터 checksum_end까지의 모든 바이트에 대해 시드 0으로 계산한 XXH64 [XXHASH] 해시입니다.

header_checksum은 scrypt를 실행하기 전에 손상된 헤더와 잘못된 패스프레이즈를 구별해 줍니다. 이것이 없으면 lg_work_factor, scrypt_ephemeral_secret 또는 encrypted_file_key의 손상은 어떤 패스프레이즈에 대해서도 복호화 실패로 이어지며, lg_work_factor의 손상은 scrypt를 임의로 오랜 시간 동안 실행되게 할 수 있습니다.

lg_work_factor부터 age_header_mac까지의 모든 필드는 다음과 같이 age v1 텍스트 파일 헤더 [AGE]를 재구성하는 데 사용됩니다.

age-encryption.org/v1
-> scrypt scrypt_ephemeral_secret_base64 lg_work_factor_decimal
encrypted_file_key_base64
--- age_header_mac_base64

여기서,

age 페이로드는 payload_nonce와 그 뒤에 오는 chunks 안의 암호화된 청크들로 구성되며, age v1 형식에 따라 해석됩니다. 마지막 청크를 제외한 모든 암호화된 청크의 길이는 65,552바이트입니다. 즉, 64 KiB의 평문 뒤에 16바이트 인증 태그가 붙은 것입니다.

아카이브가 mirrored가 아니면 chunks는 암호화된 청크만으로 구성됩니다. 그렇지 않으면 chunks에는 미러 헤더도 포함됩니다. 0부터 번호를 매긴 각 암호화된 청크 Cm에 대해, m + 1이 2부터 32까지의 2의 거듭제곱이거나 64의 양의 배수인 경우에만 미러 헤더가 Cm 바로 앞에 옵니다. 따라서 1부터 번호를 매긴 k번째 미러 헤더는 Cm 앞에 오며, 여기서 k ≤ 6이면 m = 2k − 1이고 k ≥ 6이면 m = 64(k − 5) − 1입니다. 또한 마지막 암호화된 청크 뒤에는 미러 헤더가 오지 않습니다. 미러 헤더는 다음으로 구성됩니다.

이름 타입
checksum_start offset
encrypted_mirror_magic u8[8] = ENCRYPTED_MIRROR_MAGIC
mirror_index u32le = k
mirrored_fields u8[97] = 헤더 필드
checksum_end offset
mirror_checksum u64le

mirror_checksum은 checksum_start부터 checksum_end까지의 모든 바이트에 대해 시드 0으로 계산한 XXH64 [XXHASH] 해시입니다.

미러 블록과 마찬가지로, 미러링된 헤더의 필드에 대한 제약은 적합한 압축기에 대한 요구 사항입니다. 미러링된 헤더는 그 위치에서 항상 117바이트를 차지하며, 그 내용은 아카이브 해석의 결과에 영향을 주지 않습니다. 미러링된 헤더는 그 mirror_checksum이 검증되고 mirrored_fields 이외의 필드가 위의 제약을 만족하면 온전합니다. 압축 해제기는 온전하지 않은 미러링된 헤더의 mirrored_fields를 사용해서는 안 됩니다.

미러링된 헤더를 사용하면 아카이브의 헤더가 손상되었을 때에도 아카이브를 복호화할 수 있습니다. 마지막 것을 제외한 모든 암호화된 청크는 길이가 같으므로, k번째 미러 헤더는 항상 encrypted_header_magic의 시작으로부터 113 + 65,552m + 117(k − 1) 바이트 뒤에 있으며, 여기서 m은 위와 같습니다. 따라서 ENCRYPTED_MIRROR_MAGIC을 탐색하여 찾고 mirror_checksum으로 검증한 미러링된 헤더 하나만으로 아카이브의 시작, 모든 암호화된 청크의 위치, 그리고 이를 복호화하는 데 필요한 모든 필드를 알 수 있습니다. 미러 블록과 달리, 미러링된 헤더는 아카이브를 서로 구별하기 위한 오프셋이 필요하지 않습니다. scrypt_ephemeral_secret과 payload_nonce는 무작위이므로, 같은 mirrored_fields를 가진 미러링된 헤더는 같은 아카이브에 속합니다.
미러링된 헤더 사이의 간격은 암호화된 청크 1개에서 64개까지 두 배씩 늘어나고 이후에는 64개 청크로 유지되며, 이는 § 10.3 미러 블록에서 권장하는 4 MiB 거리를 평문 기준으로 측정한 것과 같습니다. 따라서 작은 아카이브는 헤더 직후에 곧 미러링된 헤더를 가지며, 큰 아카이브의 경우 오버헤드는 64개 청크당 117바이트, 약 0.003%로 유지됩니다.

64개의 암호화된 청크라는 간격은 § 10.3 미러 블록의 잠정적인 4 MiB 거리를 따르며, 그 거리가 바뀌면 함께 바뀝니다(docs/recovery-design.md §5.5).

age 페이로드는 유효한 암호화되지 않은 아카이브(§ 3.5 암호화되지 않은 아카이브), 즉 독립 아카이브 또는 오버레이 아카이브로 복호화되어야 하며, 다음과 같은 추가 제약이 적용됩니다.

10. 무결성과 복구

10.1. 체크섬

다음 표는 이 명세에서 정의하는 모든 체크섬을 요약합니다.

구조 체크섬 계산 대상 존재 여부
약칭 블록을 제외한 메타데이터, 사전, 임포트, 모듈 및 리소스 블록 XXH64 디코딩된 데이터 항상
약칭 블록 없음(등록 해시는 엔트리를 식별하지만 검사되지는 않습니다) 없음
비리프 데이터 블록 XXH64 디코딩된 데이터 항상
리프 데이터 블록 XXH64 디코딩된 데이터 유형에 따라 다름
공식 블록의 formula_data XXH64 디코딩된 데이터 항상
공식 내의 바이트열 XXH64 (Checksum 명령) 대상이 되는 바이트열 선택
임포트 SHA-512/256 (import_checksum) 임포트된 내용 항상
미러 블록 XXH64 (mirror_checksum) 저장된 바이트 항상
암호화 아카이브의 헤더와 미러링된 헤더 XXH64 (header_checksum, mirror_checksum) 저장된 바이트 미러링된 경우

암호화 아카이브의 암호화된 청크는 [AGE]에 의해서도 인증됩니다.

명시적으로 달리 요청되지 않는 한, 적합한 압축기는 모든 리프 블록에 None이 아닌 checksum_method를 사용하는 것이 좋습니다.

명시적으로 달리 요청되지 않는 한, 적합한 압축기는 모든 공식 블록에 출력 체크섬을 선언하는 것이 좋습니다.

출력 체크섬은 모듈 적용이나 패치를 검증하는 유일한 체크섬이므로, Apply 명령이나 Patch 명령으로 계산된 출력에서 가장 중요합니다. 다른 명령으로만 계산되는 출력은 이미 formula_data의 체크섬과 그 출력을 계산하는 데 쓰인 비리프 항목의 체크섬으로 보호되지만, 그 출력 체크섬은 여전히 파일 내용을 그 자체로 검증할 수 있게 해 주므로, 보존 프로필이 요구하는 대로 모든 파일이 체크섬으로 보호되는지 여부가 공식의 명령에 좌우되지 않습니다.
저장된 바이트에 대한 체크섬은 그 바이트가 올바르게 읽혔는지만 검증할 수 있습니다. 디코딩된 데이터에 대한 체크섬은 압축 해제까지 검증하며, 일부 비리프 블록만 읽는 압축 해제기가 실제로 읽은 파일 내용을 검증할 수 있게 합니다.
리프 블록과 달리 메타데이터, 사전, 임포트, 모듈, 리소스 블록에서는 체크섬이 필수입니다. 이름과 엔트리의 구조는 다른 어떤 체크섬으로도 보호되지 않으며, 이러한 블록의 손상은 여러 파일에 한꺼번에 영향을 미칩니다. 이러한 블록은 아카이브의 작은 부분에 불과하고 각각 한 번만 읽히므로 비용은 무시할 수 있을 만큼 작습니다. 또한 압축 해제기가 이러한 블록만 읽어서 그 손상을 감지할 수 있게 하며, 이는 미러 블록을 사용하기 위한 전제 조건입니다.
비리프 블록에서는 체크섬이 필수입니다. 공식 블록의 출력 체크섬은 출력 전체를 대상으로 하므로, 출력의 일부만 읽는 압축 해제기는 출력 전체를 읽지 않고서는 이를 검증할 수 없습니다. 대신 압축 해제기는 실제로 읽은 비리프 블록의 체크섬을 검증할 수 있습니다. 또한 체크섬이 있으면 비리프 블록이 모듈 적용에 사용되기 전에 그 손상을 감지할 수 있으므로, 아래의 formula_data와 마찬가지로 그 손상은 리소스 실패가 아니라 항상 잘못된 아카이브 실패가 됩니다.
formula_data의 체크섬은 필수이며, formula_data 안에서 선언되는 선택적인 출력 체크섬(§ 6.2.10 Checksum 명령)과는 별개입니다. formula_data는 평가 전에 항상 전체가 디코딩되므로, 출력의 일부만 읽는 압축 해제기도 그 체크섬을 검증할 수 있습니다. 이 체크섬 덕분에 손상된 공식 블록을 아무것도 평가하기 전에 해당 블록만 읽어서 감지할 수 있으므로, 손상된 공식 블록은 손상된 리소스 선언으로 인한 리소스 실패가 아니라 항상 잘못된 아카이브 실패가 되며, 그 블록이 참조하는 비리프 블록의 손상과도 구별됩니다. 반면 출력 체크섬은 모듈 적용을 포함한 평가까지 검증하지만, 전체 출력을 계산한 후에만 검증할 수 있습니다.
리프 블록의 체크섬은 다음을 가능하게 하기 위해 선택입니다.

어느 쪽도 장기 보존에서는 고려 대상이 아니며, 장기 보존에서는 보존 프로필이 체크섬을 요구합니다.

10.2. 보존 프로필

모든 파일의 내용이 체크섬으로 보호될 때 아카이브는 보존 프로필을 준수합니다.

장기 보존용 아카이브를 생성하는 적합한 압축기는 보존 프로필을 준수하는 것이 좋습니다. 아카이브를 검증하는 도구는 해당 아카이브가 보존 프로필을 준수하는지 보고하는 것이 좋습니다.

다른 블록은 항상 체크섬을 가지므로, 보존 프로필을 준수하는 아카이브는 압축 해제만으로 파일 내용의 모든 손상을 감지합니다. 아카이브의 준수 여부는 데이터 블록의 압축을 해제하지 않고도 블록 유형과 공식 블록으로부터 판단할 수 있습니다. 보존 프로필을 준수하는 아카이브는 베이스든 레지스트리든 자신 외부의 어떤 것에도 의존하지 않으므로, 레지스트리 엔트리를 하나도 알지 못하는 레벨 2(§ 13.1 적합성 수준)의 압축 해제기도 이를 읽을 수 있습니다. 그래도 등록되지 않은 모듈은 사용할 수 있으며, 더 낮은 레벨의 압축 해제기는 이러한 모듈의 적용을 거부할 수 있습니다.

10.3. 미러 블록

미러 블록은 대상 블록이라고 하는 다른 블록의 그대로의 사본을 담아, 대상 블록이 손상되었을 때 이를 복원할 수 있게 합니다. 미러 블록은 손상이 둘 이상의 파셀에 영향을 미치고 해당 블록만 읽어서 감지할 수 있는, 즉 체크섬을 가진 블록 유형에 대해서만 정의됩니다.

블록 유형 대상 블록 유형
C1016 — C1716 1016 — 1716 (§ 4.3 메타데이터 블록)
C5016 — C5716 5016 — 5716 (§ 5.2 사전 블록)
C5816 — C5F16 5816 — 5F16 (§ 8.1 임포트 블록)
C6016 — C6316 6016 — 6316 (§ 7.1 모듈 블록)
C6416 — C6716 6416 — 6716 (§ 7.2 리소스 블록)
C7016 — C7716 7016 — 7716 (§ 6.1 공식 블록)

다른 모든 블록과 마찬가지로, 미러 블록의 size 필드는 자신의 type에 의해 결정되며 자신의 data의 바이트 수를 셉니다. 미러 블록은 대상 블록보다 길기 때문에, 그 size 필드가 더 넓어야 할 수도 있습니다. 따라서 미러 블록은 다음 중 하나입니다.

대상 블록은 대상 타입의 블록이어야 하며, 이 타입은 미러 블록의 type에 대해 위에 나열된 블록 유형이어야 합니다. 미러 블록이 확장되었는지 여부는 target_offset으로 나타냅니다.

예를 들어, 유형 C1316의 미러 블록은 확장되지 않은 경우 압축되지 않고 u64le size 필드를 가지는 유형 1316의 메타데이터 블록을 미러링하고, 확장된 경우 u32le size 필드를 가지는 유형 1216의 메타데이터 블록을 미러링합니다. C0016 % 4 = 0이므로 둘 다 위 표의 같은 행에 남습니다.

그 데이터는 다음으로 구성됩니다.

이름 타입
mirror_magic u8[8] ∈ {MIRROR_MAGIC, OVERLAY_MIRROR_MAGIC}
archive_offset leb128 = block_start - archive_start
target_offset leb128 ≠ 0
target_data u8[*]
checksum_end offset
mirror_checksum u64le

mirror_magic은 헤더, 푸터, 메타데이터 체인과 같은 주변 구조가 손상되었을 때 스캔으로 미러 블록을 찾을 수 있게 해 주는 고정된 바이트열입니다. 이 값은 오버레이 아카이브에서는 OVERLAY_MIRROR_MAGIC이어야 하며, 그 밖의 경우에는 MIRROR_MAGIC이어야 합니다.

archive_offset은 archive_start에서 미러 블록의 block_start까지의 오프셋입니다.

archive_offset은 이 아카이브의 미러 블록을 이 아카이브 안에 저장된 다른 아카이브의 미러 블록과 구별합니다. 후자 역시 이 아카이브의 미러 블록과 마찬가지로 스캔으로 찾을 수 있고 그 자체만으로 검증할 수 있습니다. 그러한 미러 블록은, 예를 들어 내부 아카이브가 여러 블록으로 나뉘어 있을 때, 이 아카이브에서 자신의 대상 블록이 아닌 바이트를 가리킬 수 있습니다. 내부 아카이브는 이 아카이브의 archive_start 뒤에 저장되므로, 그 미러 블록의 archive_offset이 암시하는 아카이브 시작 위치는 항상 더 큽니다.

target_offset은 대상 블록의 block_start에서 미러 블록의 block_start까지의 오프셋이며, 미러 블록이 확장된 경우에는 여기에 1을 더한 값입니다. 임의의 두 블록 사이의 오프셋은 짝수이므로, target_offset % 2로 미러 블록이 확장되었는지 알 수 있습니다. 대상 블록은 항상 미러 블록보다 앞에 있습니다.

target_data는 대상 블록의 데이터의 사본입니다. 대상 블록의 block_start부터 block_end까지의 바이트는 다음을 이어 붙인 것과 같아야 합니다.

  1. 대상 타입의 가장 짧은 leb128 인코딩

  2. 대상 타입의 크기 필드로 인코딩한 target_data의 길이

  3. target_data

대상 블록의 패딩은, 있더라도 비교하지 않으며 0 바이트로 복원됩니다. 모든 블록 유형은 가장 짧은 leb128 인코딩을 사용하므로, 미러 블록 자체의 type은 2바이트 길이입니다.

mirror_checksum은 미러 블록의 block_start부터 checksum_end까지, 타입 필드와 크기 필드를 포함한 모든 바이트에 대해 시드 0으로 계산한 XXH64 [XXHASH] 해시입니다.

이 명세의 다른 체크섬과 달리 mirror_checksum은 저장된 바이트를 대상으로 계산되므로, 복사본을 디코딩하지 않고도 검증할 수 있고 스캔으로 찾은 미러 블록도 검증할 수 있습니다. 압축 해제기는 보통 대상 블록의 체크섬 검증이 실패한 뒤에만 미러 블록을 사용하지만, 검증기는 복사본을 대상 블록과 직접 비교할 수도 있습니다.
미러 블록은 바이트의 복사본이며, 자신의 위치에서 해석되는 블록이 아닙니다. 이는 메타데이터 블록 안의 상대 오프셋이 메타데이터 블록 자체를 기준으로 측정되기 때문에 필요합니다.

미러 블록은 data가 대상 타입의 size 필드에 들어가지 않는 경우가 아니라면 확장되지 않은 상태인 것이 좋습니다.

미러 블록의 데이터는 대상 블록의 데이터보다 최소 18바이트 길므로, 이는 대상 블록이 size 필드의 한계에 가까울 때만 해당됩니다. 다음 너비는 추가되는 바이트보다 훨씬 많은 양을 담을 수 있으므로, 항상 한 단계만으로 충분합니다. 블록은 필요한 것보다 넓은 size 필드를 사용할 수 있으므로, 대상 블록의 size 필드 너비는 target_data의 크기로부터 추론할 수 없습니다.
u64le size 필드를 가지는 블록은 그 데이터가 264 바이트에 너무 가까우면 미러링할 수 없습니다. 그 미러 블록을 확장할 수 없기 때문입니다. 이는 실제로 문제가 되지 않습니다. 그러한 블록은 263 바이트보다 훨씬 크며, 이는 부호 있는 64비트 파일 오프셋을 사용하는 대부분의 시스템이 단일 파일에 저장할 수 있는 크기를 넘어서기 때문입니다.
스캔으로 미러 블록을 찾으려면 디코더는 MIRROR_MAGIC을 찾은 뒤 그 앞에 오는 size 필드의 가능한 네 가지 너비를 각각 시도할 수 있습니다. size 필드 앞의 2바이트가 그 너비에 맞는 미러 블록 유형을 인코딩하고 mirror_checksum의 검증이 성공하면, 그 후보가 올바른 것입니다. 그런 다음 후보의 block_start에서 archive_offset을 뺀 값이 archive_start와 같지 않으면 그 후보는 버려집니다. 그렇지 않은 후보는 이 아카이브 안에 저장된 다른 아카이브에 속하기 때문입니다. header_magic이나 size_hint로부터 archive_start를 결정할 수 없으면, 검증된 후보들이 나타내는 가장 작은 값을 대신 사용할 수 있습니다. 예를 들어 헤더와 푸터가 모두 손상되어 아카이브가 오버레이 아카이브인지 알지 못하는 디코더는 두 매직을 모두 찾으며, 검증된 후보의 매직이 이를 알려 줍니다.
오버레이 아카이브에서 미러 블록이 별도의 매직을 갖는 이유는 헤더와 푸터의 경우(§ 3.5 암호화되지 않은 아카이브)와 같습니다. 헤더 없이 찾을 수 있는 모든 구조는 아카이브가 오버레이 아카이브인지 알려 주어야 합니다.

미러 블록은 어떤 세그먼트에도 속하지 않으며, 대상 블록이 그보다 앞에 와야 한다는 요구 사항만 지킨다면 마지막 메타데이터 블록 뒤를 포함하여 블록 내 어디에나 나타날 수 있습니다. 하나의 대상 블록은 여러 개의 미러 블록을 가질 수 있습니다. 이 절에서 미러 블록에 대한 요구 사항은 적합한 압축기에 대한 요구 사항이며, 이를 위반하는 미러 블록이 있다고 해서 아카이브가 잘못된 아카이브가 되지는 않습니다. 미러 블록은 아카이브 해석의 결과에 전혀 영향을 주지 않습니다.

미러 블록은 mirror_checksum이 검증되고, 대상 블록과 같아야 한다는 것 외의 위 요구 사항을 충족하면 온전한 것입니다. 압축 해제기는 온전한 것이 아닌 미러 블록으로부터 대상 블록을 복원해서는 안 되며, 온전하지 않거나 온전한 대상 블록과 다른 미러 블록을 읽으면 이를 보고하는 것이 좋습니다.

미러 블록은 다른 블록과 마찬가지로 손상될 수 있지만, 대상 블록이 온전한 한 그 손상으로 잃는 것은 없습니다. 이를 잘못된 아카이브로 취급하면 모든 파일을 읽을 수 있는 아카이브를 거부하게 되며, 그것도 우연히 미러 블록을 읽는 압축 해제기에서만 그렇게 됩니다.

미러 블록과 대상 블록이 서로 가까이 있으면 함께 손상될 것으로 예상됩니다. 적합한 압축기는 대상 블록의 끝과 그 미러 블록의 block_start 사이를 최소 4096바이트 떨어뜨려 기반 저장 장치의 정렬된 4 KiB 단위를 결코 공유하지 않도록 하는 것이 좋으며, 가능하면 최소 4 MiB(4,194,304바이트) 떨어뜨리는 것이 좋습니다.

권장 거리는 잠재 섹터 오류의 버스트 길이 측정(docs/recovery-design.md §8, R1)이 이루어질 때까지 잠정적인 값입니다. 암호화 아카이브(§ 9 암호화 아카이브)에서 미러링된 헤더 사이의 간격은 뒤의 거리에 따라 달라집니다.

손상된 암호화된 청크는 청크 전체가 인증에 실패하며, 하부 저장 장치의 정렬된 4 KiB 단위는 인접한 두 암호화된 청크에 걸쳐 있을 수 있습니다. 따라서 적합한 압축기는 암호화 아카이브에서 위에서 권장한 4096바이트의 거리 대신, 미러 블록과 그 대상 블록 사이에 적어도 하나의 온전한 암호화된 청크가 놓이도록 배치하는 것이 좋습니다.

사전, 모듈 및 공식 블록은 보통 해당 세그먼트의 앞부분에 나타나므로, 그 미러 블록은 세그먼트의 끝부분 근처에 배치할 수 있습니다. 반면 메타데이터 블록은 해당 세그먼트의 끝에서야 사용할 수 있게 되므로, 그 미러 블록은 다음 세그먼트가 충분한 양만큼 기록된 뒤에 배치할 수 있습니다. 마지막 메타데이터 블록의 미러 블록은 압축기가 마지막 세그먼트를 지연시키지 않는 한 그 메타데이터 블록에서 멀리 떨어질 수 없으며, 이 경우에는 세그먼트를 주기적으로 닫으면 손실이 제한됩니다.

10.4. 복구

미러 블록으로부터 손상된 블록을 복원하는 것은 아카이브 해석과는 별개의 과정입니다. 이 과정은 손상된 바이트열을 받아, 바이트가 온전한 미러 블록과 다른 각 대상 블록을 미러 블록으로부터 재구성한 바이트로 대체하며, 대상 블록의 위치는 target_offset만으로 찾습니다.

복구 과정은 이 절에서 규정될 예정입니다(docs/recovery-design.md §5.6).

미러 블록이나 미러 헤더가 있는 암호화 아카이브를 복구하려면 인증에 실패한 청크 이후의 암호화된 청크를 복호화해야 하지만, 표준 age 구현은 이를 수행하지 않습니다. 이는 복구 과정과 함께, 복호화된 바이트를 생성하는 별도의 단계로 규정될 예정이며, 이 단계에서 그러한 청크의 바이트는 소거로 표시되고 아카이브 해석은 변경되지 않습니다(docs/recovery-design.md §5.6).

복구된 암호화된 청크는 그 평문이 완전히 복원되지 않는 한 원래의 파일 키와 payload_nonce로 다시 암호화할 수 없습니다. 그렇게 하면 다른 평문에 대해 nonce를 재사용하게 되기 때문입니다. 손상된 청크에는 보통 미러 블록이 다루지 않는 리프 블록이 포함되어 있으므로, 복구된 암호화 아카이브는 복호화되거나 새 파일 키로 다시 암호화될 것으로 예상됩니다.

11. 선택적 메타데이터

메타데이터 블록(§ 4.3 메타데이터 블록)의 optional_metadata 필드는 CBOR [RFC8949]로 인코딩된 선택적 메타데이터를 담습니다. 이 부분은 연관된 날짜와 시각, 접근 제어 정보, 주석 등 파일을 읽는 데 꼭 필요하지는 않은 모든 메타데이터를 인코딩합니다.

선택적 메타데이터는 다음 CDDL [RFC8610] 정의로 기술됩니다:

aqr-opt-metadata = {
  aqr-archive-comment
  ? &(entry-extras: -2) => [* aqr-entry-extra / null]  ; Indexed by entry numbers, starting from the root directory

    * $$aqr-opt-metadata-member  ; For future extensions
}

$$aqr-opt-metadata-member = ()  ; No extension is defined by this specification

aqr-entry-extra = {
  aqr-entry-timestamps
  aqr-entry-comment
  aqr-entry-link-group
  aqr-entry-windows
  aqr-entry-unix
  aqr-entry-zip

    * $$aqr-entry-extra-member  ; For future extensions
}

$$aqr-entry-extra-member = ()  ; No extension is defined by this specification

; TODO: other extras for round trip

이는 뒤따르는 바이트가 없는 하나의 올바른 형식의 CBOR 데이터 항목 [RFC8949]이어야 하며, 그 안의 어떤 맵도 중복 키를 가질 수 없습니다. 이 명세가 정의하는 모든 키의 값은 해당 정의와 일치해야 합니다. optional_metadata_size가 0이면 선택적 메타데이터는 빈 맵 {}입니다.

aqr-entry-extra의 멤버는 용도별로 묶여 있으며, 각 그룹은 다음 절들에서 설명합니다. -1부터 -59까지의 aqr-entry-extra 키는 플랫폼 중립적입니다. -60부터 -69까지의 키는 Windows에, -70부터 -79까지는 Unix에, -80부터 -89까지는 macOS에, -90부터 -99까지는 기타 플랫폼에 특화되어 있습니다. -100부터의 키는 엔트리가 변환되기 전의 형식에 특화되어 있습니다.

11.1. 타임스탬프

aqr-entry-extra의 키 crtime, ctime, mtime, atime, arctime은 파일 시스템과 아카이브 형식이 파일, 디렉터리 또는 링크에 연관 짓는 타임스탬프를 기록합니다. 각각은 아래에서 설명하는 aqr-time이며, 원본에 해당 타임스탬프가 없으면 존재하지 않습니다.

aqr-entry-timestamps = (
  ? &(crtime: -1) => aqr-time
  ? &(ctime: -2) => aqr-time
  ? &(mtime: -3) => aqr-time
  ? &(atime: -4) => aqr-time
  ? &(arctime: -5) => aqr-time
)
crtime

생성 시간(출생 시간이라고도 합니다): 파일이 해당 위치에 생성된 시점입니다. 이후에는 갱신되지 않지만, 파일을 복사하면 보통 복사한 시각을 가진 새 파일이 생성됩니다. 예로는 NTFS와 FAT의 생성 시간, HFS+와 APFS의 생성 날짜, ext4의 i_crtime과 Btrfs의 otime이 있으며, 이들은 BSD와 macOS에서는 st_birthtime으로, Linux에서는 statx의 stx_btime으로 노출됩니다.

ctime

상태 변경 시간: 파일의 메타데이터(예: 권한, 소유권 또는 링크 수)나 내용이 마지막으로 변경된 시점입니다. 이는 POSIX의 st_ctime이며, NTFS의 변경 시각(change time)과 HFS+의 속성 수정 날짜(attribute modification date)가 이와 유사합니다. POSIX는 이를 임의의 시간으로 설정하는 것을 허용하지 않으므로, 추출하는 압축 해제기는 보통 이를 복원할 수 없습니다.

mtime

수정 시간: 파일의 내용이 마지막으로 변경된 시점입니다. 디렉터리의 경우, 그 안에서 엔트리가 마지막으로 추가, 제거 또는 이름 변경된 시각입니다. 이는 사실상 모든 파일 시스템과 아카이브 형식이 가지고 있는 타임스탬프입니다.

atime

접근 시간: 파일의 내용이 마지막으로 읽힌 시점입니다. 많은 시스템은 성능을 위해 이를 느긋하게 갱신하거나 전혀 갱신하지 않으며(예: Linux의 relatime과 noatime, 그리고 비활성화할 수 있는 NTFS의 접근 시각 갱신), FAT는 날짜만 보관합니다. 아카이빙을 위해 파일을 읽는 것 자체가 이를 갱신할 수 있습니다.

arctime

백업 시간: 백업 소프트웨어가 기록한, 파일이 마지막으로 백업된 시점입니다. 파일 시스템은 이를 스스로 갱신하지 않습니다. 예로는 HFS와 HFS+의 백업 날짜(backup date)가 있으며, macOS에서는 getattrlist의 ATTR_CMN_BKUPTIME으로 노출됩니다. 이는 원본이 저장한 시각이며, 아카이브 자체가 만들어진 시각이 아닙니다.

"ctime"이라는 이름은 모호합니다. Windows와 Windows에서 유래한 형식은 "C"를 생성 시각(creation time)의 의미로 사용합니다. Microsoft C 런타임의 st_ctime, ZIP 추가 필드 0x000a의 Ctime, 7z와 RAR의 CTime은 모두 ctime이 아니라 crtime에 해당합니다. pax와 같은 POSIX 형식은 이를 상태 변경 시각의 의미로 사용하며, 이는 ctime에 해당합니다.

aqr-time은 시점 또는 로컬 날짜 및 시각을 양자화 간격 및 선택적인 시간대 정보와 함께 나타냅니다. 이는 다음 CDDL 정의로 기술되며, 여기서 etime은 [RFC9581]에 정의된 확장 시간(extended time)에 대한 CDDL 타입 이름입니다.

aqr-time =
  $aqr-base-time /                          ; UTC timestamp, scaled and quantized
  [$aqr-base-time] /                        ; Local time in unknown time zone, encoded like UTC (*)
  [$aqr-instant, offset: aqr-tz-offset] /   ; UTC timestamp with time zone offset ($)
  etime /                                   ; Extended time defined in [[!RFC9581]]
  [etime]                                   ; Local time in unknown time zone, encoded like UTC

aqr-tz-offset = -48..56  ; In 15 minutes, positive east of UTC as in ISO 9660

$aqr-base-time  = #6.100(int)    ; 1d (FAT atime*), date as in [[!RFC8943]]
$aqr-base-time /= $aqr-instant  ; Can be given a time zone offset

$aqr-instant  = #6.121(int)  ; 2s (FAT mtime*, exFAT atime$, ZIP*, CAB*)
$aqr-instant /= int          ; 1s (~ext3, HFS/HFS+, tar, ISO 9660$)
$aqr-instant /= #6.122(int)  ; 10ms (FAT ctime*, ISO 9660$, exFAT$)
$aqr-instant /= #6.123(int)  ; 1ms
$aqr-instant /= #6.124(int)  ; 1us (UDF)
$aqr-instant /= #6.125(int)  ; 100ns (NTFS, RAR*, 7z)
$aqr-instant /= #6.126(int)  ; 1ns (ext4, ZFS, Btrfs, APFS)

베이스 시간 값 $aqr-base-time은 정수이며, 태그가 없거나 다음과 같이 태그가 붙습니다. 정수에 스케일을 곱한 값은 1970-01-01T00:00:00Z 이후의 초 수이며, CBOR 태그 1 [RFC8949]에서와 같이 윤초를 세지 않습니다. 음수 값은 그 기준 시점 이전의 시각을 나타냅니다. 태그는 양자화 간격도 결정하며, 양자화 간격은 시간 값에 대한 상대값으로 § 15.4 양자화 간격에 규정되어 있습니다. #6.100은 1970-01-01 이후의 일 수 [RFC8943]이며, $aqr-instant가 아닌 유일한 베이스 시간 값입니다. 즉, 여기에는 시간대 오프셋을 지정할 수 없습니다.

인코딩 양자화 간격 (s) 스케일 (s) 예
#6.100(int) [0, 86400] 86400 FAT atime*
#6.121(int) [0, 2] 2 FAT mtime, exFAT atime$, ZIP, CAB*
int [0, 1] 1 ext3 이하, HFS/HFS+, tar, ISO 9660$ (디렉터리 레코드)
#6.122(int) [0, 10-2] 10-2 FAT ctime*, ISO 9660$ (볼륨 디스크립터), exFAT$
#6.123(int) [0, 10-3] 10-3 -
#6.124(int) [0, 10-6] 10-6 UDF
#6.125(int) [0, 10-7] 10-7 NTFS, RAR*, 7z
#6.126(int) [0, 10-9] 10-9 ext4, ZFS, Btrfs, APFS

예시 열에서 *로 표시된 형식은 알 수 없는 시간대의 지역 시간을 저장하며(또는 저장할 수 있으며), $로 표시된 형식은 UTC로부터의 오프셋과 함께 지역 시간을 저장합니다.

태그 121부터 126까지는 열거형 대안 0부터 5까지를 위한 태그로 [CBOR-NOTABLE-TAGS], 그 의미는 태그 자체가 아니라 사용 중인 스키마에 의해 정해집니다. 따라서 이 태그들은 $aqr-instant로서만 의미를 가지며, 일반적인 확장 시간의 축약형이 아닙니다.
시간대 오프셋은 15분의 배수이므로, 지역 시간을 UTC로 변환해도 2초의 배수가 유지됩니다. 그러나 지역 날짜는 오프셋이 0이 아닌 한 1일의 배수로 유지되지 않으므로, 시간대 오프셋이 알려진 날짜는 확장 시간으로만 나타낼 수 있습니다(예: UTC+9의 2023-10-19에 대한 1001({1: 1697641200, -14: 86400, -10: "+09:00"})).

aqr-time의 각 대안은 다음과 같은 의미를 가집니다:

$aqr-base-time

시간대 정보가 없는 시점입니다.

[$aqr-base-time]

알 수 없는 시간대의 지역 날짜와 시각으로, UTC인 것처럼 인코딩됩니다. 이는 단일한 시점을 식별하지 않습니다.

[$aqr-instant, aqr-tz-offset]

단독 $aqr-base-time과 같은 시점에, 그것이 기록된 시간대의 오프셋을 ISO 9660과 같이 15분 단위로 덧붙인 것입니다. 오프셋은 UTC의 동쪽에서 양수입니다: UTC+x시간은 4x이고 UTC−x시간은 −4x입니다. 지역 시간은 베이스 시간 값에 오프셋의 900배를 초 단위로 더한 값입니다.

etime

다른 대안에 맞지 않는 모든 값을 위한 확장 시간 [RFC9581](태그 1001)입니다. 시간대 정보가 있는 경우 Time Zone Hint(키 -10 또는 10)로 주어지며, 양자화 간격은 § 15.4 양자화 간격에서 설명하는 키로 주어집니다.

[etime]

알 수 없는 시간대의 지역 날짜와 시각으로, UTC인 것처럼 인코딩됩니다. 확장 시간은 시간대 힌트(키 -10 또는 10)를 포함해서는 안 되며, 시간 척도(키 -1, -13 또는 13)가 있다면 0(UTC)이어야 합니다.

aqr-time의 다른 대안이 같은 시간 값, 같은 양자화 간격, 같은 시간대 정보를 나타낸다면 etime 또는 [etime]을 사용해서는 안 됩니다. 시간대 힌트는 그것이 해당 오프셋과 같은 숫자 오프셋(예: "+09:00")일 때에만 aqr-tz-offset과 같습니다. 양자화 간격이 없는 확장 시간은 결코 다른 대안과 같지 않습니다.

결과적으로 일반적인 파일 시스템과 아카이브 형식의 타임스탬프는 확장 시간을 전혀 필요로 하지 않으며, [RFC9581]을 완전히 지원하지 않는 구현도 이를 처리할 수 있습니다. 추출하는 압축 해제기는 추가 메타데이터를 무시하는 것이 허용되므로(§ 12 추출), 그러한 구현은 확장 시간을 사용하는 aqr-time을 무시할 수 있습니다.
실제로 쓰이는 일부 정밀도는 확장 시간으로만 표현할 수 있으며(예: ASN.1 UTCTime의 분, NTP의 2-32초, SQL Server DATETIME의 1/300초), UDF에서처럼 15분의 배수가 아닌 시간대 오프셋도 마찬가지입니다. 이들은 파일 메타데이터에서 충분히 드물기 때문에 별도의 인코딩을 둘 가치가 없습니다.

서로 다른 인코딩의 두 aqr-time 값은 같은 시점을 나타내더라도 서로 다릅니다. 예를 들어 1과 122(100)은 양자화 간격이 다릅니다. 파일 시스템이나 다른 아카이브 형식의 타임스탬프를 변환하는 압축기는 그 정밀도를 보존하는 것이 좋습니다. 즉, 양자화 간격이 원본과 일치하는 인코딩을 사용하는 것이 좋습니다.

정밀도가 서로 다른 파일 시스템의 타임스탬프를 비교하는 것은 흔한 문제의 원인입니다. 예를 들어 ext4에서 FAT으로 복사한 변경되지 않은 파일이 수정된 것처럼 보입니다. 양자화 간격을 아카이브마다가 아니라 타임스탬프마다 기록하면, 편집으로 인해 아카이브에 여러 출처가 섞인 후에도 모든 소비자가 두 타임스탬프가 같은 시간을 나타낼 수 있는지 판단할 수 있습니다.
다음은 출처에 따라 2023-10-19T14:12:34.87Z, 즉 UTC+9의 23:12:34.87을 인코딩할 수 있는 방식입니다:

ZIP에서 가져온 타임스탬프는 § 16.2 ZIP의 타임스탬프에서 다룹니다.

11.2. 주석

aqr-opt-metadata의 archive-comment와 aqr-entry-extra의 comment는 각각 아카이브 전체와 각 엔트리에 붙는 자유 형식의 주석으로, 예를 들어 ZIP이나 RAR의 아카이브 주석과 파일 주석이 이에 해당합니다. 둘 다 aqr-text이며, 이는 유니코드 문자열, 짝이 없는 서로게이트를 포함하는 WTF-8 [WTF-8] 문자열, 또는 인코딩을 알 수 없는 바이트열 중 하나입니다:

aqr-archive-comment = (
  ? &(archive-comment: -1) => aqr-text
)

aqr-entry-comment = (
  ? &(comment: -6) => aqr-text
)

aqr-text =
  tstr /          ; Unicode
  #6.273(bstr) /  ; WTF-8, i.e. UTF-16 + surrogate compat
  bstr            ; In unknown encoding

aqr-text는 tstr로 표현할 수 있는 경우, 즉 유니코드 스칼라 값의 시퀀스인 경우 그렇게 표현해야 합니다. #6.273(bstr) 안의 바이트 문자열은 유효한 UTF-8 문자열이 아닌 유효한 WTF-8 문자열이어야 합니다. 즉, 인코딩된 짝이 없는 서로게이트를 적어도 하나 포함해야 합니다. bstr은 그 인코딩을 알 수 없으므로, 우연히 유효한 UTF-8 문자열이더라도 이 요구 사항의 적용을 받지 않습니다.

ZIP에서 가져온 주석은 § 16.3 ZIP의 주석에서 다룹니다.

aqr-entry-link-group = (
  ? &(link-group: -7) => int  ; File nodes only
)

link-group은 파일 노드가 원래 같은 파일이었다는 것, 즉 Unix에서 같은 inode에 대한 하드 링크이거나 NTFS에서 같은 파일 레코드에 대한 하드 링크였다는 것을 기록합니다. 이는 다른 노드에는 아무런 영향이 없으며, 압축기는 다른 노드에 이를 부여하지 않는 것이 좋습니다. 음이 아닌 값은 세그먼트 로컬이며 같은 세그먼트 안에서만 같은 그룹을 나타내고, 음수 값은 아카이브 전역이며 모든 세그먼트에서 같은 그룹을 나타냅니다. 아카이브 값에서 최종적으로 정해지는 링크 그룹은 세그먼트 해석에 의해 결정됩니다.

압축기는 같은 파일이 아니었던 파일들에 같은 링크 그룹을 부여해서는 안 됩니다. 압축기는 다른 어떤 파일 노드도 가지지 않는 링크 그룹을 부여하지 않는 것이 좋습니다. link-group이 있는 파일 노드는 같은 그룹의 다른 파일 노드와 같은 리프 블록을 참조하는 것이 좋으며, link-group과 comment를 제외하고 그 파일 노드들과 같은 엔트리 추가 정보를 가지는 것이 좋습니다.

세그먼트 로컬 그룹은 CBOR가 임의의 64비트 부호 없는 정수를 음이 아닌 int로 인코딩할 수 있으므로 inode 번호(st_ino)를 그대로 사용할 수 있게 해 주며, 이전 세그먼트를 읽지 않고도 할당할 수 있습니다. 그래도 inode 번호는 단일 파일 시스템 안에서만 고유하므로, 여러 파일 시스템에서 읽어 들이는 압축기는 그중 일부의 번호를 다시 매겨야 합니다. 아카이브 전역 그룹은 세그먼트를 넘나들며 파일을 연결하며, 예를 들어 압축기가 단일 트리를 아카이브하는 동안 주기적으로 새 세그먼트를 시작하는 경우나 기존 아카이브에 세그먼트를 덧붙이는 경우에 사용됩니다. 아카이브 전역 그룹을 사용하는 압축기는 예를 들어 덧붙일 때 이전 메타데이터 블록을 읽는 방식 등으로 그 그룹을 다른 모든 세그먼트와 일관되게 유지할 책임이 있습니다. inode 번호는 파일이 제거된 후 재사용되거나 관련 없는 파일 시스템에서 올 수 있으므로 그대로 아카이브 전역 값으로 사용하기에 적합하지 않으며, 내용이 같은 관련 없는 파일들을 연결하는 것은 어떤 압축 해제기도 감지할 수 없습니다.

최종 아카이브 값에서 해당 그룹을 가진 모든 파일 노드가 같은 내용을 가지면 그 링크 그룹은 일관됩니다. 예를 들어 이후 세그먼트가 그 그룹의 파일 노드 중 일부만 대체한 경우처럼 일관되지 않은 그룹은 잘못된 아카이브 실패가 아니지만, 추출(§ 12 추출)에는 아무런 영향을 주지 않습니다.

이후 세그먼트가 그룹 안의 어떤 파일 노드든 대체할 수 있으므로, 일관성은 최종 아카이브 값으로부터만 판단할 수 있습니다. 일관되지 않은 그룹을 잘못된 아카이브로 취급하면 일부 세그먼트만 읽는 압축 해제기가 그러한 실패를 감지할 수 없게 됩니다(§ 13.2 부분 접근). 대신 압축 해제기는 하드 링크를 만들기 전에 일관성을 확인할 수 있으며, 파일 노드가 같은 리프 블록을 참조하는 경우에는 비용이 들지 않습니다. 하드 링크는 아카이브 모델의 일부가 아니므로, link-group을 무시하는 압축 해제기도 같은 내용을 가진 올바른 파일들을 추출합니다. 일부 macOS Time Machine 볼륨에서 볼 수 있는 디렉터리 하드 링크는 표현하지 않습니다.

11.4. Windows 전용 메타데이터

aqr-entry-windows = (
  ? &(win-link-type: -60) => &(file: 0, directory: 1, junction: 2)  ; Link nodes only

  ; TODO: file attributes (e.g. ZIP external file attributes)
)

win-link-type은 Windows에서 생성될 링크의 종류를 결정합니다(§ 12 추출). 이것은 링크 노드 이외의 노드에는 아무런 영향을 주지 않으며, 압축기는 그러한 노드에 이것을 부여하지 않는 것이 좋습니다. "file"과 "directory"는 각각 파일에 대한 심볼릭 링크와 디렉터리에 대한 심볼릭 링크를 나타내며, "junction"은 정션(마운트 포인트 리파스 포인트)을 나타냅니다.

11.5. Unix 전용 메타데이터

aqr-entry-unix = (
  ? &(unix-mode: -70) => uint .le 4095  ; Permission bits only (0o7777), without S_IFMT
  ? &(unix-uid: -71) => uint
  ? &(unix-gid: -72) => uint
)

unix-mode는 set-user-ID, set-group-ID, sticky 비트를 포함한 권한 비트만을 담습니다. 엔트리의 종류는 오직 그 노드(§ 4.1 아카이브 모델)에 의해서만 결정되므로, unix-mode는 파일 타입 비트(S_IFMT)를 결코 포함하지 않습니다.

unix-uid와 unix-gid는 소유자의 숫자 사용자 ID와 그룹 ID입니다.

ZIP에서 온 Unix 메타데이터는 § 16.4 ZIP의 Unix 메타데이터에서 다룹니다.

11.6. zip-extras: ZIP 추가 필드

zip-extras는 엔트리가 변환되어 온 원래 ZIP 엔트리의 추가 필드[APPNOTE]가 이 명세에서 달리 표현할 방법이 없는 파일 정보를 담고 있을 때, 그 추가 필드를 보존합니다.

aqr-entry-zip = (
  ? &(zip-extras: -100) => aqr-zip-extras
)

aqr-zip-extras = { * aqr-zip-extra-key => bstr .size (0..65531) }
aqr-zip-extra-key =
  0..65535 /   ; Header ID n in the local header
  -65536..-1   ; Header ID n in the central header, as -1 - n
; TODO: multiple extra fields with the same header ID

0부터 65535까지의 키 n은 로컬 헤더에서 헤더 ID가 n인 추가 필드를 나타내고, −65536부터 −1까지의 키 −1 − n은 중앙 헤더에서 헤더 ID가 n인 추가 필드를 나타냅니다. 값은 헤더 ID와 데이터 크기로 이루어진 4바이트 헤더를 제외한 추가 필드의 데이터입니다. 따라서 음이 아닌 키를 가진 값은 항상 로컬 헤더용 형식이며, 음수 키를 가진 값은 항상 중앙 헤더용 형식입니다.

ZIP 아카이브를 변환하는 압축기는 다음 요구 사항에 따라 엔트리의 어떤 추가 필드든 zip-extras에 넣을 수 있습니다.

이러한 요구 사항의 적용을 받는 것으로 알려진 추가 필드는 § 16.5 보존하지 않는 추가 필드에 나열되어 있습니다. 등록되지 않은 헤더 ID를 가진 것을 포함하여 그 밖의 추가 필드는 있는 그대로 zip-extras에 넣을 수 있습니다.

zip-extras는 결코 권위 있는 정보가 아닙니다. 보존된 추가 필드의 정보가 아카이브 모델 또는 aqr-entry-extra의 다른 키와 일치하지 않는 경우, 후자를 사용해야 합니다. 압축기가 넣도록 허용되지 않은 추가 필드를 발견한 압축 해제기는 해당 필드만 무시하는 것이 좋으며, 아카이브 전체를 유효하지 않은 것으로 취급하지 않는 것이 좋습니다.

zip-extras는 ZIP으로부터의 단방향 변환을 위해 설계되었으며, 변환된 아카이브로부터 원래 ZIP 아카이브를 재구성하는 것은 목표가 아닙니다. 추가 필드를 보존하는 것은 플랫폼 전용 속성, 접근 제어 목록, 리소스 포크처럼 아직 고유한 키가 없는 정보를, 전용 키가 정의되었을 때 이미 변환된 아카이브로부터 복구할 수 있도록 하기 위함입니다. 알 수 없는 추가 필드도 같은 이유로 보존되며, 그중 일부는 ZIP 컨테이너에만 의미가 있을 수도 있습니다. 같은 헤더 ID의 로컬 헤더 버전과 중앙 헤더 버전은 형식이 서로 다른 경우가 많습니다. 예를 들어 0x5455의 중앙 헤더 버전은 수정 시각만 가지며, 많은 Info-ZIP 속성 블록의 중앙 헤더 버전은 요약만 가집니다. 키로 이들을 구별하면 소비자가 각 값을 올바른 형식으로 해석할 수 있습니다. 중앙 헤더 버전의 키가 −n이 아니라 −1 − n인 것은 헤더 ID 0이 로컬 헤더 버전과 충돌하지 않도록 하고, 두 버전이 CBOR에서 같은 인코딩 길이를 갖도록 하기 위함입니다.

12. 추출

아카이브 값을 파일 시스템에 쓰는 것은 아카이브 모델의 범위 밖입니다. 이를 수행하는 압축 해제기는 다음 요구 사항을 따릅니다.

아카이브 모델은 링크 노드를 거치는 경로를 표현할 수 없지만(§ 4.1 아카이브 모델), 추출 중에 생성된 링크는 여전히 이후의 쓰기를 리디렉션할 수 있습니다. 예를 들어 대소문자를 구분하지 않는 파일 시스템에서 링크 A와 파일 a/x가 같은 디렉터리로 매핑되는 경우가 그렇습니다. 위의 이름 충돌 규칙이 이미 이를 방지하며, 첫 번째 요구 사항이 이를 명시적으로 규정합니다. 가능한 구현 방법으로는 다른 모든 노드를 생성한 후에 모든 링크를 생성하거나, 링크를 따라가지 않고 각 경로를 부모 디렉터리에 대해 상대적으로 여는 방법(예: O_NOFOLLOW를 사용한 openat, 또는 Linux의 RESOLVE_BENEATH)이 있습니다.

링크 노드를 추출하는 압축 해제기는 다음의 추가 요구 사항을 따릅니다.

압축 해제기는 같은 일관된 링크 그룹을 가진 파일 노드 사이에 하드 링크를 생성할 수 있습니다. 그 밖의 파일 노드 사이에는, 내용이 같더라도 하드 링크를 생성해서는 안 됩니다.

하드 링크를 사용하면 추출된 파일들이 디스크 공간과 그중 하나에 가해진 변경 사항을 공유할 수 있으며, 이는 예를 들어 rsync --link-dest로 만든 스냅숏 백업에 매우 중요합니다. 하지만 독립적이었던 파일은 독립적으로 남아 있어야 합니다.

압축 해제기는 소유권 정보와 마찬가지로, 명시적으로 요청되지 않는 한 특수 노드에 대해 FIFO나 장치 파일을 생성하지 않는 것이 좋습니다. 이러한 이유로, 또는 플랫폼이 표현할 수 없다는 이유로(예: 권한 부족, 또는 플랫폼의 범위를 벗어난 장치 번호) 생성되지 않은 특수 노드는 사용자에게 보고해야 합니다.

장치 파일을 생성하려면 일반적으로 슈퍼유저 권한이 필요하며, 그러한 추출로 생성된 느슨한 권한의 장치 파일은 디스크 전체를 노출할 수 있습니다. FIFO는 그 자체로는 무해하지만, 단순성을 위해 동일하게 취급합니다.

압축 해제기는 미지 노드에 대해 아무것도 생성해서는 안 되며, 이를 사용자에게 보고해야 합니다. 압축 해제기는 다른 노드의 추출을 계속할 수 있습니다.

13. 적합성

비규범적이라고 표시된 절뿐만 아니라, 이 명세의 모든 다이어그램, 예시, 참고는 비규범적입니다. 이 명세의 그 밖의 모든 내용은 규범적입니다.

이 명세의 핵심어 MAY, MUST, MUST NOT, RECOMMENDED, SHOULD, SHOULD NOT은 여기에 표시된 것처럼 모두 대문자로 나타날 때, 그리고 그때에만 BCP 14 [RFC2119] [RFC8174]에 설명된 대로 해석됩니다.

13.1. 적합성 수준

적합한 압축 해제기는 자신이 적용하는 모듈(§ 7 모듈)에 따라 다음 중 하나의 적합성 수준을 가집니다:

레벨 0

어떤 모듈도 적용하지 않습니다.

레벨 1

모든 등록 모듈, 즉 모듈 해시가 Aqueous 모듈 레지스트리(§ 15.6 Aqueous 모듈 레지스트리)의 한 엔트리의 등록 해시인 모든 모듈을 적용하며, 그 밖의 모듈은 적용하지 않습니다.

레벨 2

모든 모듈을 적용합니다.

수준 0 또는 수준 1의 압축 해제기는 자신의 수준에 포함되지 않는 모듈의 적용을 거부할 수 있으며, 이 경우 리소스 실패가 발생합니다. 수준 2의 압축 해제기는 모듈의 적용을 거부해서는 안 되지만, 모듈이 자신의 한도를 초과하면 여전히 리소스 실패로 실패할 수도 있습니다. 수준은 압축 해제기에 대한 그 밖의 어떤 요구 사항도 바꾸지 않습니다. 예를 들어 수준 0의 압축 해제기도 Apply 명령이 없는 공식 블록은 여전히 읽습니다.

수준 0 또는 수준 1의 압축 해제기는 신뢰할 수 없는 코드를 결코 실행하지 않습니다. 수준 1의 압축 해제기가 만들어진 후에 등록된 모듈은 그 압축 해제기에 알려져 있지 않으며, 이로 인해 압축 해제기가 갱신될 때까지 해당 모듈이 약칭 블록으로 주어진 경우에는 미지원 실패가, 그 밖의 경우에는 리소스 실패가 발생합니다. 각 수준이 자신의 모듈을 적용하는 방법은 § 7.1 모듈 블록을 참조하십시오.

13.2. 부분 접근

압축 해제기는 엔트리의 부분 집합, 콘텐츠의 바이트 범위, 또는 콘텐츠 없는 엔트리 이름과 같이 아카이브 해석 결과의 일부만 계산할 수 있습니다. 전체 결과가 아카이브 값이라면, 그러한 부분 결과는 전체 결과의 해당 부분과 같아야 합니다.

부분 결과를 계산하는 압축 해제기는 자신이 계산하지 않는 부분만으로 인해 발생하는 잘못된 아카이브 실패를 감지하지 않아도 됩니다. 압축 해제기는 자신이 계산한 바이트만을 대상으로 하는 모든 체크섬을 검증하는 것이 좋습니다.

예를 들어, 공식 블록으로 정의된 파일의 바이트 범위를 읽는 압축 해제기는 전체 파셀이 아니라 그 범위를 대상으로 하는 비리프 블록만 읽습니다. 이 압축 해제기는 공식 블록의 출력 체크섬은 검증할 수 없지만, 자신이 읽은 비리프 블록의 체크섬은 검증할 수 있습니다. 단일 경로를 조회하는 압축 해제기는 그 경로를 대체할 수 있는 모든 세그먼트를 고려해야 하며, 이는 경로 또는 그 조상 중 하나가 어떤 세그먼트에서 디렉터리가 아닌 엔트리로 발견될 때까지 metadata_backward_offset부터 메타데이터 블록을 거꾸로 읽음으로써 가능합니다. 그 엔트리가 경로 자체이고 툼스톤이 아니라면 그 엔트리가 해당 경로의 노드를 제공하며, 그렇지 않은 경우, 즉 툼스톤이거나 조상이라면 해당 경로는 존재하지 않습니다. 그러한 엔트리를 가진 세그먼트가 없다면, 독립 아카이브에서는 해당 경로가 존재하지 않으며, 오버레이 아카이브에서는 베이스에서 조회됩니다. 마찬가지로, 임포트를 사용하는 파일을 읽는 데에는 임포트된 바이트 범위만 필요하며, 내부 임포트는 선행 세그먼트에서부터 같은 방식의 조회로 해석됩니다.

14. 보안 고려 사항

아카이브는 임의의 데이터를 담는 컨테이너이며, 압축 해제기가 파일 내용을 재구성하기 위해 실행하는 WebAssembly 모듈(§ 7.3 모듈 실행)을 제외하면 그 자체로 능동적 콘텐츠를 포함하지 않습니다. 이러한 모듈은 입력과 출력 이외에는 아무것에도 접근할 수 없는 결정적 프로필에서 실행되지만, 압축 해제기는 그 모듈 해시(§ 7.1 모듈 블록)를 알고 있지 않는 한 여전히 이를 신뢰할 수 없는 코드로 취급하고 그에 맞게 격리해야 합니다. 공식 블록과 모듈은 아카이브 자체보다 훨씬 큰 출력을 생성할 수 있으며, 일부 필드(예: lg_work_factor)는 계산량을 직접 제어합니다. 압축 해제기는 구현 정의 한도를 적용하고, 그 한도를 초과하면 리소스 실패로 실패할 것으로 기대됩니다. 아카이브를 파일 시스템에 기록하는 압축 해제기는 § 12 추출의 요구 사항을 따라야 하며, 특히 대상 디렉터리 바깥에 기록하는 것을 방지해야 합니다. 암호화되지 않은 아카이브는 기밀성을 제공하지 않으며, 그 XXH64 체크섬(§ 10.1 체크섬)은 우발적인 손상만 탐지할 뿐 의도적인 변조는 탐지하지 못합니다. 암호화 아카이브(§ 9 암호화 아카이브)는 [AGE]를 통해 기밀성과 무결성을 제공하며, 그 강도는 사용된 패스프레이즈의 강도에 달려 있습니다. 엔트리 이름, 타임스탬프, 소유권을 포함한 아카이브의 메타데이터는 해당 아카이브를 생성한 시스템에 대한 정보를 드러낼 수 있습니다.

오버레이 아카이브는 베이스(일반적으로 파일 시스템의 디렉터리)를 기준으로 해석되며, 베이스의 어떤 부분이든 그 결과에 복사할 수 있습니다. 베이스를 읽는 압축 해제기는 § 8.3 베이스 읽기의 요구 사항을 따라야 하며, 특히 베이스가 명시적으로 주어진 경우에만 베이스를 사용하고 절대 그 바깥을 읽지 않아야 합니다. 오버레이 아카이브를 제자리에서 적용하면 베이스 디렉터리의 파일이 수정되고 삭제됩니다.

15. IANA 고려 사항

15.1. application/aqr 미디어 타입

이 문서는 다음과 같이 Media Types Registry([RFC6838], 5.6절)에 새로운 미디어 타입의 등록을 요청합니다.

타입 이름
application
서브타입 이름
aqr
필수 매개변수
해당 없음
선택 매개변수
해당 없음
인코딩 고려 사항
바이너리
보안 고려 사항
§ 14 보안 고려 사항를 참조하십시오.
상호 운용성 고려 사항

암호화되지 않은 아카이브의 앞과 뒤에는 임의의 바이트(예: 자동 압축 해제 실행 파일 스텁)가 올 수 있으며, 아카이브의 범위는 header_magic과 푸터(§ 3.6 아카이브 찾기)로 결정됩니다. 메타데이터 블록이 하나도 없는 아카이브는 디렉터리 트리가 아니라 이름 없는 단일 파일로 압축 해제됩니다. 오버레이 아카이브는 자체 미디어 타입 application/aqo(§ 15.2 application/aqo 미디어 타입)를 가지며, 페이로드가 오버레이 아카이브인 암호화 아카이브도 해당 타입입니다.

공개된 명세
이 문서
이 미디어 타입을 사용하는 애플리케이션
Aqueous 아카이브 형식을 구현하는 아카이버 및 압축기.
프래그먼트 식별자 고려 사항
해당 없음
추가 정보
이 타입의 폐기된 별칭
해당 없음
매직 넘버
암호화되지 않은 아카이브의 경우 F8 04 03 6A D6 BE(16진수, HEADER_MAGIC)이며, 그 앞에 다른 데이터가 올 수 있습니다. 암호화 아카이브의 경우 오프셋 0에 F9 CA F2 A6 D0 2A(16진수, ENCRYPTED_HEADER_MAGIC와 ENCRYPTED_MIRRORED_HEADER_MAGIC의 공통 접두사)가 옵니다.
파일 확장자
.aqr
단일 파일을 메타데이터 블록 없이 아카이브로 압축하는 경우, 대신 원래 파일 이름에 확장자를 덧붙일 수 있습니다(예: example.tar.aqr).
Macintosh 파일 타입 코드
해당 없음
추가 정보를 위한 연락 담당자 및 이메일 주소
Kang Seonghoon <public+aqr@mearie.org>
사용 목적
COMMON
사용 제한
해당 없음
작성자
Kang Seonghoon
변경 관리자
Kang Seonghoon

15.2. application/aqo 미디어 타입

이 문서는 미디어 타입 레지스트리([RFC6838], 5.6절)에 다음과 같이 새 미디어 타입의 등록을 요청합니다.

타입 이름
application
서브타입 이름
aqo
필수 매개변수
해당 없음
선택 매개변수
해당 없음
인코딩 고려 사항
바이너리
보안 고려 사항
§ 14 보안 고려 사항를 참조하십시오.
상호 운용성 고려 사항

오버레이 아카이브는 베이스 없이는 해석할 수 없습니다. 오버레이 아카이브의 매직 넘버는 application/aqr의 매직 넘버와 다르므로, 오버레이 아카이브를 지원하지 않는 압축 해제기가 이를 독립 아카이브로 오인하지 않습니다. 그 밖에는 application/aqr의 상호 운용성 고려 사항이 적용됩니다.

공개된 명세
이 문서
이 미디어 타입을 사용하는 애플리케이션
Aqueous 오버레이 아카이브 형식을 구현하는 아카이버, 패치 도구 및 업데이터입니다.
프래그먼트 식별자 고려 사항
해당 없음
추가 정보
이 타입의 폐기된 별칭 이름
해당 없음
매직 넘버
암호화되지 않은 오버레이 아카이브의 경우 69 0D 15 AF F6 B2(16진수; OVERLAY_HEADER_MAGIC)이며, 그 앞에 다른 데이터가 올 수 있습니다. 암호화된 오버레이 아카이브는 application/aqr의 암호화 아카이브와 같은 매직 넘버를 가집니다.
파일 확장자
.aqo
Macintosh 파일 타입 코드
해당 없음
추가 정보를 위한 연락처 담당자 및 이메일 주소
Kang Seonghoon <public+aqr@mearie.org>
의도된 용도
COMMON
사용 제한
해당 없음
작성자
Kang Seonghoon
변경 관리자
Kang Seonghoon

15.3. +aqr 구조화된 구문 접미사

이 문서는 구조화된 구문 접미사 레지스트리([RFC6838], 6.2절)에 다음과 같이 새로운 구조화된 구문 접미사의 등록을 요청합니다.

이름
Aqueous 아카이브 형식
+suffix
+aqr
참고 문헌
이 문서.
인코딩 고려 사항
바이너리
상호 운용성 고려 사항

+aqr 접미사를 가진 미디어 타입은 암호화되지 않았거나 암호화된 독립 아카이브를 나타내며, 그 아카이브 값에는 해당 미디어 타입에 의해 보다 구체적인 의미가 부여됩니다. 일반적인 Aqueous 압축 해제기는 그 의미를 알지 못하더라도 그러한 미디어 타입의 아카이브를 해석할 수 있습니다. application/aqr(§ 15.1 application/aqr 미디어 타입)에 대한 상호 운용성 고려 사항도 적용됩니다.

프래그먼트 식별자 고려 사항

+aqr에 대해 지정된 프래그먼트 식별자의 구문과 의미는 application/aqr에 대해 지정된 것과 같은 것이 좋습니다. (이 문서의 발행 시점에는 application/aqr에 대해 정의된 프래그먼트 식별 구문이 없습니다.)

특정 xxx/yyy+aqr에 대한 프래그먼트 식별자의 구문과 의미는 다음과 같이 처리하는 것이 좋습니다.

  • +aqr에 정의된 경우로서 프래그먼트 식별자가 +aqr 규칙에 따라 해석되는 경우에는 +aqr에 지정된 대로 처리합니다.
  • +aqr에 정의된 경우로서 프래그먼트 식별자가 +aqr 규칙에 따라 해석되지 않는 경우에는 xxx/yyy+aqr에 지정된 대로 처리합니다.
  • +aqr에 정의되지 않은 경우에는 xxx/yyy+aqr에 지정된 대로 처리합니다.
보안 고려 사항
§ 14 보안 고려 사항의 보안 고려 사항은 +aqr 접미사를 가진 모든 미디어 타입에 적용됩니다. 그러한 각 미디어 타입에는 아카이브에 대한 해당 미디어 타입의 해석에 고유한 추가 보안 고려 사항이 있을 수 있습니다.
연락처
Kang Seonghoon <public+aqr@mearie.org>
작성자/변경 관리자
Kang Seonghoon

15.4. 양자화 간격

이 문서는 Time Tag Map Keys Registry([RFC9581], 섹션 7.3)에 다음과 같이 새 키를 등록할 것을 요청합니다:

키 이름
양자화 간격
키 값
-14 (잠정)
참조
이 문서의 양자화 간격 절
의미

양자화 간격은 주어진 시간 값을 기준으로 실제 시간이 놓인 구간을 지정합니다. 즉, 시간 값이 t이고 양자화 간격이 a부터 b까지라면, 실제 시간은 t + a 이상 t + b 이하의 범위에 있습니다. 양자화란 실제 시간 값을 이산적인 표현으로 절사하거나 반올림하는 모든 수단을 말하며, 특히 기반 시계의 정확도와는 관련이 없습니다. 예를 들어, 시계의 정밀도는 높지만 시간 값이 초 단위로 절삭되어 있을 수 있으며, 이 경우 양자화 간격은 [0, 1초]가 됩니다.

구간은 항상 닫힌 구간입니다. 구간은 가능한 실제 시간의 정확한 집합보다 약간 더 넓을 수 있지만(예: 절사의 경우 [0, 1초]이며, 이때 실제 시간은 결코 정확히 1초 뒤일 수 없습니다), 반올림에서 동점인 경우를 포함하여 가능한 실제 시간을 결코 배제하지 않습니다.

양자화 간격은 다음과 같은 방식으로 주어질 수 있습니다.

  • 초 단위의 숫자 값 또는 기간 y이며, [0, y] 구간을 나타냅니다.
  • 각각이 초 단위의 숫자 값 또는 기간인 두 요소의 배열 [x, y]이며, [−x, y] 구간을 나타냅니다. 즉, x는 시간 값 이전의 범위이고 y는 시간 값 이후의 범위입니다.

주어진 시간 값이 항상 구간에 포함되도록, 모든 숫자 값은 유한하고 음수가 아니어야 하며, 모든 기간은 음수가 아닌 키 1을 가져야 합니다. 구간은 단일 점이어서는 안 됩니다. 따라서 단일 숫자 값 또는 기간은 0이어서는 안 되며, 배열 [x, y]는 x와 y가 모두 0이어서는 안 됩니다. x가 0이면 배열을 사용해서는 안 되며, 대신 y만 단독으로 주어야 합니다.

확장 시간에 이 키가 없으면 그 양자화 간격은 알 수 없습니다. 특히, 시간 값이 정확하다는 의미를 함축하지 않습니다.

예를 들어 다음 중 처음 두 개는 10밀리초 단위로 절사된 시간 값을 나타내고, 마지막 하나는 가장 가까운 초로 반올림된 시간 값을 나타냅니다:

1001({1: 1697724754, -3: 870, -14: 0.01}),
1001({1: 1697724754, -3: 870, -14: {1: 0, -3: 10}}),
1001({1: 1697724754, -14: [0.5, 0.5]})

다음 CDDL 선언을 사용하여 양자화 간격 키를 나타낼 수 있습니다.

quantization-interval = ~time / ~duration / [ 2*2 ~time/~duration ]
$$ETIME-ELECTIVE //= (-14: quantization-interval)

15.5. Aqueous 사전 레지스트리

이 문서는 IANA에 "Aqueous 사전"이라는 제목의 새 레지스트리를 만들어 줄 것을 요청하며, 이 레지스트리에는 유형 5016의 약칭 블록이 참조할 수 있는 등록 사전이 등재됩니다(§ 4.2.2 등록 사전과 등록 모듈). 새 엔트리는 Specification Required 정책([RFC8126], 섹션 4.6)에 따라 할당됩니다. 각 엔트리는 다음 필드를 가집니다:

해시
등록 해시, 즉 사전 바이트의 SHA-512/256 해시 [FIPS180-4]이며, 64개의 소문자 16진수 숫자로 표기합니다.
이름
사람이 알아보기 위한 사전의 짧은 이름이며, 형식에는 아무런 영향을 주지 않습니다.
엔트로피 테이블
사전에 엔트로피 테이블이 있는지 여부, 즉 has_entropy_tables의 값입니다.
데이터
사전의 바이트로, 디코딩된 데이터이며 § 5.2 사전 블록에 따른 사전 블록의 데이터입니다. 이 데이터는 사전 블록의 데이터로서 유효해야 하며 등록 시 함께 제출해야 합니다.
참조
사전과 그 용도를 설명하는 문서입니다.

지정 전문가는 해시가 데이터와 일치하는지, 그리고 같은 해시를 가진 엔트리가 없는지 확인합니다. 아카이브는 엔트리를 해시로만 참조하므로, 한 번 등록된 엔트리는 수정하거나 삭제해서는 안 됩니다. 엔트리는 데이터가 같더라도 Aqueous 모듈 레지스트리(§ 15.6 Aqueous 모듈 레지스트리)와 절대 공유되지 않습니다.

레지스트리의 초기 내용은 다음과 같습니다.

해시 이름 엔트로피 테이블 참조
TBD text TBD 이 문서
TBD modules TBD 이 문서

사전 "text"는 일반 텍스트를 위한 것이고, 사전 "modules"는 전체가 주어지는 모듈 블록(§ 7.1 모듈 블록)을 위한 것입니다.

초기 사전의 데이터와 해시는 아직 만들어지지 않았습니다.

15.6. Aqueous 모듈 레지스트리

이 문서는 IANA에 "Aqueous 모듈"이라는 제목의 새 레지스트리를 만들어 줄 것을 요청하며, 이 레지스트리에는 유형 6016의 약칭 블록이 참조할 수 있는 등록 모듈이 등재됩니다(§ 4.2.2 등록 사전과 등록 모듈). 새 엔트리는 Specification Required 정책([RFC8126], 4.6절)에 따라 할당됩니다. 각 엔트리에는 다음 필드가 있습니다.

해시
등록 해시, 즉 모듈의 모듈 해시로, 64개의 소문자 16진수 숫자로 기재합니다.
이름
사람이 읽기 위한 모듈의 짧은 이름으로, 형식에는 아무런 영향을 주지 않으며, 모듈의 이름과 버전을 @로 연결하여 기재합니다(예: flate-set@1). 한 모듈의 서로 다른 버전은 @ 앞의 이름이 같고 해시가 다릅니다.
데이터
모듈 블록(§ 7.1 모듈 블록)의 wasm_module에 해당하는 모듈의 바이트로, 그러한 것으로서 유효해야 하며 등록 시 함께 제출되어야 합니다.
참조
모듈이 역변환하는 변환과 그 입력, 리소스, 매개변수를 설명하는 문서이며, 가급적 모듈을 빌드한 소스 코드도 포함합니다.

지정 전문가는 해시가 데이터와 일치하는지, 같은 해시를 가진 엔트리가 없는지, 그리고 모듈이 § 7.3 모듈 실행을 준수하는지 확인합니다. 아카이브는 엔트리를 해시로만 참조하므로, 한 번 등록된 엔트리는 수정되거나 제거되어서는 안 됩니다. 엔트리는 두 레지스트리의 데이터가 같더라도 Aqueous 사전 레지스트리(§ 15.5 Aqueous 사전 레지스트리)와 결코 공유되지 않습니다.

레지스트리의 초기 내용은 이 문서의 참조 구현이 빌드한 모듈이며, 각 엔트리의 데이터(D)와 참조(R)를 연결하여 다음과 같습니다.

해시 이름 링크
caca82f410e08aec7048aa663fb5fc4d376394b44b4ad89f4373dd8053427f1c bcn-recons@1 D R
30ebd39ea3f768f06cf4d3e79872666d6923eb8e608ed5878bcc86c040179a21 bundle-recons@1 D R
8531516a33c8327cd7423dc838e88edcb16d520f1a621f2b47eda75f27de8922 clip-recons@1 D R
6a9359342d4e3712baeb3d615ce0bc39a3283412da430f4c3098e2a71560e48d exe-recons/arm64@1 D R
7b22bbac03ed25bd765b8d27207f6ddd9d14bd4a683b3046e27af801202f4d2a exe-recons/cil@1 D R
5cc8b375caa7adbeadd5ec65845c6996070f3bab79e6ed5dc4ad8c62756eb60b exe-recons/pe-reloc@1 D R
7d11c15482190e15bdaef0b1206151ea75bf0aad3092136e6c25e4f8931ca257 exe-recons/x86@3 D R
f15b8d3355fc8929c25d263b6b752516a3e675cbd476ee5933f6900a8cb9718b fasta-recons@1 D R
3944fa75e9f3991a741c305f7e410cfdb2b73b17f57691da9de94cd142fbf968 fits-recons-image@1 D R
4433f4e59eca939357f8d3c5821b7ba887b38f27c7c9c438dedcfb40bcf6b796 fits-recons-rice@1 D R
c25884adc026793bca3729098d4adc0d10cc9015b5094e9b2a2d2043d10bc80f fits-recons-table@1 D R
568697ef4a3d656873eaf79124c4910134f1fd5c27368cff151f4be592eae8bd flac-recons@3 D R
9d3e8b19692ad0949412eeb156b0ababceb2c032b9bb90ee2ff33b4788fbdf7c flate-set@1 D R
17849cd2f570cd4ff266e6c7c7e7656954fff0b7b53a032682e630874b1d27e3 git-pack-recons@1 D R
04372842501e11c7b00cb9318413f5e2e60b208b4b51a316514d22c16491784a gnu-gettext-recons@1 D R
8cac476b42906e38a2356829e851368e0f50d7a9e19fcaf1a5c1f37ea53a0f81 ima-recons@2 D R
211a60941bbf02f93c8631eac928329c5df633aeea60a60dce24bea161a537e6 jpeg-recons@2 D R
987cf097113c1744fc5c4152d2c2a0690f9bf7619050ecdf83dde7194e3dd1a7 leveldb-recons@1 D R
fd1cad1d213fa4842374deb2b699034867b3de8133ab6b2595a5f5653f9f2368 lz4-recons@1 D R
1585d87b3e78ab476c07e3cbb7294a8ab8957221996db7e56641e7958352ff7f lzma-recons@1 D R
88d1abe44c2ff42ecaffa3b983ba7271d67d6a0015d67621bfde47589bbecb5c lzo-recons@1 D R
57a624b911608583a899696341843976301146b0b0da8e1e9bdcb2e89738c47a mp3-recons@5 D R
7802fa7cd30c778f5a28f6928c14e4facd2f7bfa16d0559f8f3d9001625b78d3 obj-recons@1 D R
94de97052f0648d4a46838cff795de9fa50d54f0fed1d9626d4468daefddb91b pcm-recons@1 D R
59b6c21e5ce4e15f567b6af7af919ce474ecfdfe0317b1c5a24a2ebd1069a428 pdf-recons@2 D R
874cc85df19af27651e5b9904298a068eda45f331778958f84dbd8966c42bb26 png-against@1 D R
ff7fddf83d8f02aed7d9e00dfcd6536c657674093e56f235526a7e15a632ddd1 png-recons@3 D R
e9255925acd09473d6c3456e9213635af299edbe66aef6ef21f065d31a504884 png-set@2 D R
0e04bbf105c680af27c18865587bd24b3ae2b48e7481cb8cb1df130296b641df psd-recons@1 D R
cb0f12a5a24d451e9c2ba996d47f3f5f6586d6b3ec9ec0e842a52f4bf1e4f3cc raster-recons-16@1 D R
0f77bdf94f1fd11542160d25cc26737c01b68c5f557c885d0cda0a88e092738e raster-recons@1 D R
df80c12c9ad47bc6cd4f66d39a7ac02e124777a1a5a3a3e84eba79d569864f31 recs-entries@1 D R
03fae1c3201f91c5b8512b43d0d261a19ab5568f280233df170b7026e90e5275 recs-fields@1 D R
672c1f43cc0d93e5bd5dc733f7fa890ff37bb3b796c338198a21ed67b5bbe14d snappy-recons@2 D R
fdfd0dfefc7da381bfd33e7e5a4e338d08a11a88e68616c6077d279fe9072801 sqlite-recons@1 D R
3ac92678e2aad46f7f9d12b9b951ec970b8e2ed039acc25f61e834d4410a0f80 vorbis-recons@2 D R
46a1e89ed82d1585732dc8ac52d78d472618feb5ed715d9b943aa141dac1d4c2 wrt-recons-hangul@1 D R
78114341a3981b9b877f74beff267c6fcb83f23e7106d344107fbc178dab3117 wrt-recons@1 D R
928d51f81c0b917f2cd38519d76703eeaae67766d2d41e3f117204382ce6979f xsv-recons@1 D R
63150a666d6cb14bf9162a5f353343612b2cfc2a4111f5d4b2a15324598d91cc zip-recons@3 D R

초기 엔트리는 아직 확정되지 않았습니다. 이 엔트리의 모듈과 이를 설명하는 문서는 이 문서의 첫 번째 버전이 나오기 전까지 변경될 수 있으며, 그때까지 변경된 엔트리는 새 버전으로 추가되지 않고 이 표에서 대체됩니다.

16. ZIP에서 변환하기

이 절은 비규범적입니다.

이 절에서는 ZIP이 동일한 정보에 대해 여러 출처나 인코딩을 제공하는 경우에, 압축기가 ZIP 아카이브 [APPNOTE]의 엔트리를 정보 손실 없이 아카이브 값으로 변환하는 방법을 설명합니다.

16.1. ZIP의 엔트리 이름

ZIP 아카이브 [APPNOTE]는 각 엔트리의 경로를 로컬 헤더와 중앙 헤더에 바이트열로 저장하며, 구성 요소는 /로 구분됩니다. 그 인코딩은 헤더만으로 항상 알 수 있는 것은 아니며, 여러 추가 필드가 같은 경로의 유니코드 버전을 담고 있습니다. ZIP 아카이브를 변환하는 압축기는 다음 출처로부터 각 구성 요소의 인코딩을 결정할 수 있으며, 앞에 있는 출처가 우선합니다.

  1. NameCRC32가 헤더에 있는 경로의 CRC-32와 일치하는 버전 1의 추가 필드 0x7075(Info-ZIP Unicode Path) [INFOZIP-EXTRAFLD]. 이 필드는 경로를 UTF-8로 담고 있습니다. CRC-32가 일치하지 않는 필드는 필드를 갱신하지 않고 경로만 변경한 도구가 남긴 것일 수 있으므로, [APPNOTE]에 명시된 대로 무시됩니다.

  2. 범용 비트 플래그의 비트 11(언어 인코딩 플래그)이 설정된 경우, 헤더에 있는 경로입니다. 이 경로는 UTF-8입니다.

  3. 추가 필드 0x554e(Xceed 유니코드)는 경로를 UTF-16LE로 담습니다. 이는 WTF-8로 변환되며, WTF-8은 짝이 없는 서로게이트도 보존합니다.

  4. 같은 필드에 주어진 코드 페이지로 인코딩된 경로를 담고 있는 추가 필드 0x5a4c(ZipArchive code page). 이 필드에 코드 페이지만 있는 경우에는 대신 헤더에 있는 경로를 그 코드 페이지로 디코딩합니다.

  5. 헤더에 있는 경로의 코드 페이지를 알려 주는 추가 필드 0xe57a(ALZip code page).

  6. 그렇지 않으면 헤더에 있는 경로의 인코딩은 지정되어 있지 않습니다. 전통적으로는 IBM 코드 페이지 437이지만, 아카이브를 만든 기기의 시스템 코드 페이지인 경우가 많으므로 압축기는 인코딩을 추측해야 합니다. 추측할 수 없으면 각 구성 요소에 대해 (C116을 앞에 붙인) 원시 바이트열을 사용합니다.

이름이 마지막 출처가 아닌 다른 출처에서 온 경우, UTF-8이 아닌 경로의 원래 바이트는 보존되지 않습니다. 그 바이트는 ZIP 컨테이너가 이름을 인코딩하는 방식을 반영할 뿐이며, 파일 자체의 이름은 디코딩된 유니코드 문자열입니다.

16.2. ZIP의 타임스탬프

ZIP 아카이브 [APPNOTE]는 같은 타임스탬프를 여러 곳에 담을 수 있습니다. 로컬 헤더와 중앙 헤더의 DOS 날짜 및 시각, 그리고 추가 필드 0x000a(NTFS), 0x5455(extended timestamp), 0x5855(Info-ZIP UNIX type 1), 0x000d(UNIX) [INFOZIP-EXTRAFLD]가 그것입니다. ZIP 아카이브를 변환하는 압축기는 각 키의 출처를 다음과 같이 선택할 수 있으며, 앞에 있는 출처가 우선합니다. 이 순서는 더 정밀한 출처를 먼저 우선하고, 정밀도가 같으면 더 최근의 명세를 우선합니다.

키 출처
crtime 0x000a Ctime; 0x5455 CrTime(로컬 헤더에만 있음)
ctime -
mtime 0x000a Mtime; 0x5455 ModTime; 0x5855 또는 0x000d ModTime; DOS 날짜 및 시각
atime 0x000a Atime; 0x5455 AcTime(로컬 헤더에만 있음); 0x5855 또는 0x000d AcTime
arctime -

각 출처는 다음과 같이 인코딩됩니다.

mtime에 대해 DOS 날짜 및 시각과 UTC 출처가 모두 있으면, 그 차이로 아카이브가 만들어진 곳의 시간대 오프셋을 알 수 있습니다. k가 aqr-tz-offset 범위의 정수일 때 DOS 시각에서 UTC 시각을 뺀 값이 900k초와 2초 미만으로 차이 나면, u를 UTC 출처의 인코딩이라 할 때 mtime을 [u, k]로 인코딩할 수 있습니다. 그렇지 않으면, 예를 들어 시계 오차나 일광 절약 시간 전환 때문이라면, DOS 날짜 및 시각은 그냥 버립니다.

같은 키에 대한 두 출처가 양자화 간격보다 크게 어긋나면 압축기는 이에 대해 경고할 수 있으며, 그래도 우선하는 출처를 사용합니다.

16.3. ZIP의 주석

ZIP 아카이브 [APPNOTE]는 각 엔트리의 주석을 중앙 헤더에만 저장하고, 아카이브 전체의 주석은 중앙 디렉터리 끝 레코드에 저장합니다. 둘 다 바이트열이며, 빈 주석은 주석이 없는 것과 다르지 않으므로 압축기는 빈 주석에 대해 comment 또는 archive-comment를 생략할 수 있습니다.

ZIP 아카이브를 변환하는 압축기는 다음 출처로부터 각 엔트리 주석의 인코딩을 결정할 수 있으며, 앞의 출처가 우선합니다.

  1. ComCRC32가 헤더에 있는 주석의 CRC-32와 일치하는, 중앙 헤더에 있는 버전 1의 추가 필드 0x6375(Info-ZIP Unicode Comment) [INFOZIP-EXTRAFLD]. 이 필드는 주석을 UTF-8로 담습니다. CRC-32가 일치하지 않는 필드는 § 16.1 ZIP의 엔트리 이름의 0x7075와 마찬가지로 무시됩니다.

  2. 범용 비트 플래그의 비트 11(언어 인코딩 플래그)이 설정된 경우, 헤더에 있는 주석입니다. 이 주석은 UTF-8입니다.

  3. 중앙 헤더의 추가 필드 0x554e(Xceed 유니코드)는 주석을 UTF-16LE로 담습니다. 이는 짝이 없는 서로게이트를 보존하는 WTF-8로 변환됩니다.

  4. 그 밖의 경우에는 주석의 인코딩이 지정되지 않습니다. 압축기는 같은 아카이브의 엔트리 이름에 대해 추측한 인코딩으로 주석을 디코딩할 수 있습니다. 디코딩할 수 없으면 원시 바이트열을 bstr로 사용합니다.

아카이브 주석에는 언어 인코딩 플래그도 추가 필드도 없으므로, 그 인코딩은 아카이브 자체에 의해 지정되는 일이 결코 없습니다. 압축기는 위의 마지막 출처에서와 같이 아카이브 주석을 디코딩할 수 있으며, 그 결과를 archive-comment에 넣습니다.

아카이브 주석은 사람이 읽을 수 있는 텍스트 이외의 용도로 쓰이는 경우가 많으며, 예를 들어 자동 압축 해제 아카이브는 그곳에 설정을 저장할 수 있으므로, 추측한 인코딩으로 아카이브 주석을 디코딩하면 정보가 손실될 수 있습니다. 인코딩을 확신할 수 없는 압축기는 대신 원시 바이트열을 유지할 수 있습니다.

16.4. ZIP의 Unix 메타데이터

ZIP 아카이브 [APPNOTE]는 엔트리의 Unix 메타데이터를 중앙 헤더의 외부 파일 속성과 추가 필드 0x7875(Info-ZIP UNIX type 3), 0x7855(Info-ZIP UNIX type 2), 0x756e(ASi UNIX), 0x5855(Info-ZIP UNIX type 1), 0x000d(UNIX) [INFOZIP-EXTRAFLD]에 담을 수 있습니다. ZIP 아카이브를 변환하는 압축기는 각 키의 출처를 다음과 같이 선택할 수 있으며, 앞의 출처가 우선하고 같은 항목에 있는 출처는 같은 우선순위를 가집니다.

키 출처
unix-mode 외부 파일 속성; 0x756e Mode
unix-uid 0x7875 UID; 0x7855 UID(로컬 헤더만); 0x756e, 0x5855(로컬 헤더만) 또는 0x000d UID
unix-gid 0x7875 GID; 0x7855 GID(로컬 헤더만); 0x756e, 0x5855(로컬 헤더만) 또는 0x000d GID

각 출처는 다음과 같이 인코딩됩니다.

같은 키에 대한 두 출처가 서로 일치하지 않으면 압축기는 이에 대해 경고할 수 있으며, 그래도 우선하는 출처를 사용합니다.

16.5. 보존하지 않는 추가 필드

다음 추가 필드 [APPNOTE] [INFOZIP-EXTRAFLD]는 zip-extras에 넣지 않습니다. 그 정보가 아카이브 모델과 다른 키에 흡수되거나, ZIP 컨테이너에서만 의미가 있으므로 폐기되기 때문입니다.

헤더 ID 이름 처리
0x0001 Zip64 extended information 폐기: 크기와 오프셋
0x0007 AV Info 폐기: 서명
0x000a NTFS 흡수: 타임스탬프(§ 16.2 ZIP의 타임스탬프)
0x000d UNIX (PKWARE) 흡수: 타임스탬프(§ 16.2 ZIP의 타임스탬프), unix-uid와 unix-gid(§ 16.4 ZIP의 Unix 메타데이터), 링크 노드의 대상, 하드 링크의 link-group, 특수 노드의 장치 번호
0x0014 PKCS#7 Store for X.509 Certificates 폐기: 서명
0x0015 X.509 Certificate ID and Signature for individual file 폐기: 서명
0x0016 중앙 디렉터리용 X.509 Certificate ID 폐기: 서명
0x0017 Strong Encryption 헤더 폐기됨: 암호화
0x0019 PKCS#7 Encryption Recipient Certificate List 폐기됨: 암호화
0x0021 Policy Decryption Key Record 폐기됨: 암호화
0x0022 Smartcrypt Key Provider Record 폐기됨: 암호화
0x0023 Smartcrypt Policy Key Data Record 폐기됨: 암호화
0x10c5 minizip CMS signature 폐기: 서명
0x1986 Pixar USD 헤더 ID 폐기됨: 정렬용 패딩
0x5455 확장 타임스탬프 흡수됨: 타임스탬프(§ 16.2 ZIP의 타임스탬프)
0x554e Xceed 유니코드 흡수됨: 엔트리 이름(§ 16.1 ZIP의 엔트리 이름) 및 주석(§ 16.3 ZIP의 주석)
0x5855 Info-ZIP UNIX type 1 흡수됨: 타임스탬프(§ 16.2 ZIP의 타임스탬프), unix-uid 및 unix-gid(§ 16.4 ZIP의 Unix 메타데이터)
0x5a4c ZipArchive code page 흡수됨: 엔트리 이름(§ 16.1 ZIP의 엔트리 이름)
0x5a4d ZipArchive 오프셋 배열 폐기됨: 오프셋
0x6375 Info-ZIP Unicode Comment 흡수됨: 주석(§ 16.3 ZIP의 주석)
0x6c78 libarchive "xl" 흡수됨: 중앙 헤더 필드의 사본
0x7075 Info-ZIP Unicode Path 흡수됨: 엔트리 이름(§ 16.1 ZIP의 엔트리 이름)
0x7855 Info-ZIP UNIX type 2 흡수됨: unix-uid 및 unix-gid(§ 16.4 ZIP의 Unix 메타데이터)
0x7875 Info-ZIP UNIX type 3 흡수됨: unix-uid 및 unix-gid(§ 16.4 ZIP의 Unix 메타데이터), 버전 1에 한함
0x9901 AE-x(WinZip AES) 폐기됨: 암호화
0x9903 WinZip Reference 폐기됨: 다른 엔트리에 대한 참조, 변환 시 해소됨
0xa11e Data Stream Alignment(Apache Commons Compress) 폐기됨: 정렬용 패딩
0xa220 Microsoft Open Packaging Growth Hint 폐기됨: 제자리 편집용 패딩
0xcafe Java JAR 마커 폐기됨: ZIP 파일 자체에 관한 정보
0xcdcd minizip 중앙 디렉터리 폐기됨: 중앙 디렉터리의 암호화 및 서명
0xd935 Android ZIP Alignment 폐기됨: 정렬용 패딩
0xe57a ALZip 코드 페이지 흡수됨: 해당 코드 페이지로 디코딩된 경우 엔트리 이름(§ 16.1 ZIP의 엔트리 이름)
일부 추가 필드는 특정한 경우에만 보존됩니다. 로컬 헤더 버전과 중앙 헤더 버전은 별도로 고려됩니다.

색인

이 명세가 정의하는 용어

참조로 정의된 용어

참고 문헌

규범적 참고 문헌

[AGE]
Filippo Valsorda. age. Version 1.1.0. URL: https://c2sp.org/age@v1.1.0
[FIPS180-4]
Secure Hash Standard (SHS). FIPS PUB 180-4. URL: https://csrc.nist.gov/pubs/fips/180-4/upd1/final
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
Scott Bradner. Key words for use in RFCs to Indicate Requirement Levels. BCP 14. URL: https://www.rfc-editor.org/info/rfc2119
[RFC4648]
Simon Josefsson. The Base16, Base32, and Base64 Data Encodings. STD 51. URL: https://www.rfc-editor.org/info/rfc4648
[RFC6838]
N. Freed; J. Klensin; T. Hansen. Media Type Specifications and Registration Procedures. January 2013. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc6838/
[RFC8174]
Barry Leiba. Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. BCP 14. URL: https://www.rfc-editor.org/info/rfc8174
[RFC8610]
Henk Birkholz; Christoph Vigano; Carsten Bormann. Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures. URL: https://www.rfc-editor.org/info/rfc8610
[RFC8878]
Yann Collet; Murray Kucherawy. Zstandard Compression and the 'application/zstd' Media Type. URL: https://www.rfc-editor.org/info/rfc8878
[RFC8943]
M. Jones; A. Nadalin; J. Richter. Concise Binary Object Representation (CBOR) Tags for Date. November 2020. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc8943/
[RFC8949]
Carsten Bormann; Paul Hoffman. Concise Binary Object Representation (CBOR). STD 94. URL: https://www.rfc-editor.org/info/rfc8949
[RFC9581]
C. Bormann; B. Gamari; H. Birkholz. Concise Binary Object Representation (CBOR) Tags for Time, Duration, and Period. August 2024. Proposed Standard. URL: https://www.rfc-editor.org/info/rfc9581/
[WASM-CORE-3]
Andreas Rossberg. WebAssembly Core Specification. Release 3.0. URL: https://webassembly.github.io/spec/versions/core/WebAssembly-3.0.pdf
[WTF-8]
Simon Sapin. The WTF-8 encoding. URL: https://wtf-8.codeberg.page/
[XXHASH]
Yann Collet. xxHash fast digest algorithm. Version 0.2.0. URL: https://github.com/Cyan4973/xxHash/blob/v0.8.3/doc/xxhash_spec.md

비규범적 참고 문헌

[APPNOTE]
.ZIP File Format Specification. Version 6.3.10. URL: https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
[CBOR-NOTABLE-TAGS]
Carsten Bormann. Notable CBOR Tags. Internet-Draft. URL: https://datatracker.ietf.org/doc/draft-bormann-cbor-notable-tags/
[INFOZIP-EXTRAFLD]
Info-ZIP Extra Field Descriptions (proginfo/extrafld.txt in UnZip 6.0). URL: https://infozip.sourceforge.net/
[RFC8126]
M. Cotton; B. Leiba; T. Narten. Guidelines for Writing an IANA Considerations Section in RFCs. June 2017. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc8126/

CDDL 색인

aqr-opt-metadata = {
  aqr-archive-comment
  ? &(entry-extras: -2) => [* aqr-entry-extra / null]  ; Indexed by entry numbers, starting from the root directory

    * $$aqr-opt-metadata-member  ; For future extensions
}

$$aqr-opt-metadata-member = ()  ; No extension is defined by this specification

aqr-entry-extra = {
  aqr-entry-timestamps
  aqr-entry-comment
  aqr-entry-link-group
  aqr-entry-windows
  aqr-entry-unix
  aqr-entry-zip

    * $$aqr-entry-extra-member  ; For future extensions
}

$$aqr-entry-extra-member = ()  ; No extension is defined by this specification

; TODO: other extras for round trip

aqr-entry-timestamps = (
  ? &(crtime: -1) => aqr-time
  ? &(ctime: -2) => aqr-time
  ? &(mtime: -3) => aqr-time
  ? &(atime: -4) => aqr-time
  ? &(arctime: -5) => aqr-time
)

aqr-time =
  $aqr-base-time /                          ; UTC timestamp, scaled and quantized
  [$aqr-base-time] /                        ; Local time in unknown time zone, encoded like UTC (*)
  [$aqr-instant, offset: aqr-tz-offset] /   ; UTC timestamp with time zone offset ($)
  etime /                                   ; Extended time defined in [[!RFC9581]]
  [etime]                                   ; Local time in unknown time zone, encoded like UTC

aqr-tz-offset = -48..56  ; In 15 minutes, positive east of UTC as in ISO 9660

$aqr-base-time  = #6.100(int)    ; 1d (FAT atime*), date as in [[!RFC8943]]
$aqr-base-time /= $aqr-instant  ; Can be given a time zone offset

$aqr-instant  = #6.121(int)  ; 2s (FAT mtime*, exFAT atime$, ZIP*, CAB*)
$aqr-instant /= int          ; 1s (~ext3, HFS/HFS+, tar, ISO 9660$)
$aqr-instant /= #6.122(int)  ; 10ms (FAT ctime*, ISO 9660$, exFAT$)
$aqr-instant /= #6.123(int)  ; 1ms
$aqr-instant /= #6.124(int)  ; 1us (UDF)
$aqr-instant /= #6.125(int)  ; 100ns (NTFS, RAR*, 7z)
$aqr-instant /= #6.126(int)  ; 1ns (ext4, ZFS, Btrfs, APFS)

aqr-archive-comment = (
  ? &(archive-comment: -1) => aqr-text
)

aqr-entry-comment = (
  ? &(comment: -6) => aqr-text
)

aqr-text =
  tstr /          ; Unicode
  #6.273(bstr) /  ; WTF-8, i.e. UTF-16 + surrogate compat
  bstr            ; In unknown encoding

aqr-entry-link-group = (
  ? &(link-group: -7) => int  ; File nodes only
)

aqr-entry-windows = (
  ? &(win-link-type: -60) => &(file: 0, directory: 1, junction: 2)  ; Link nodes only

  ; TODO: file attributes (e.g. ZIP external file attributes)
)

aqr-entry-unix = (
  ? &(unix-mode: -70) => uint .le 4095  ; Permission bits only (0o7777), without S_IFMT
  ? &(unix-uid: -71) => uint
  ? &(unix-gid: -72) => uint
)

aqr-entry-zip = (
  ? &(zip-extras: -100) => aqr-zip-extras
)

aqr-zip-extras = { * aqr-zip-extra-key => bstr .size (0..65531) }
aqr-zip-extra-key =
  0..65535 /   ; Header ID n in the local header
  -65536..-1   ; Header ID n in the central header, as -1 - n
; TODO: multiple extra fields with the same header ID

quantization-interval = ~time / ~duration / [ 2*2 ~time/~duration ]
$$ETIME-ELECTIVE //= (-14: quantization-interval)

이슈 색인

예를 들어 아카이브 전체의 선택적 메타데이터나 루트 디렉터리의 엔트리 추가 정보만 갱신하기 위해, 루트 디렉터리 외에 엔트리가 하나도 없는 세그먼트를 허용해야 하는지 여부입니다. ↵
같은 세그먼트 안의 앞선 블록에 대한 참조(예: 세그먼트 앞부분에 저장된 파일을 패치하기 위한 참조)는 아직 정의되지 않았습니다. 다른 파일에 대한 참조는 명령이 아니라 임포트(§ 8.1 임포트 블록)로 이루어지며, 선행 세그먼트에 있는 파일의 내용에 대한 참조는 내부 임포트로 이루어집니다. 후보안은 공식 블록 앞에 놓이는 두 번째 비리프 항목 목록으로, Slice 명령이 참조 항목과 같은 방식으로 읽으며, 다른 공식 블록의 출력이 필요할 때는 그 출력을 참조하는 대신 해당 공식 블록의 명령을 복사합니다. 이를 위해서는 스트리밍 압축 해제기가 어떤 블록을 유지해야 하는지 알 수 있도록 참조되는 블록보다 앞서 선언되는 이러한 참조 거리의 상한과, 세그먼트를 처음부터 읽지 않고도 앞선 블록의 위치를 찾는 방법이 필요합니다(docs/xform-design.md §7). ↵
이 섹션은 잠정적이며 docs/xform-design.md §5를 따릅니다. ↵
Zstandard Window_Size(§ 3.2.1 Zstandard 압축 방식)에 대해 보장되는 227바이트(128 MiB) 한도를 메모리 한도에도 적용해야 하는지 여부. ↵
부분 모듈에서 데이터보다 작은 윈도, 즉 링 버퍼. 이는 최대값이 있는 임포트 타입을 허용할 것입니다. ↵
작업 메모리 이외의 메모리를 읽기 전용으로 태스크에 보이게 해야 하는지 여부(이 경우 그 메모리에 쓰는 명령을 거부해야 합니다), 그리고 태스크에서의 부분 모듈의 윈도입니다. ↵
64개의 암호화된 청크라는 간격은 § 10.3 미러 블록의 잠정적인 4 MiB 거리를 따르며, 그 거리가 바뀌면 함께 바뀝니다(docs/recovery-design.md §5.5). ↵
권장 거리는 잠재 섹터 오류의 버스트 길이 측정(docs/recovery-design.md §8, R1)이 이루어질 때까지 잠정적인 값입니다. 암호화 아카이브(§ 9 암호화 아카이브)에서 미러링된 헤더 사이의 간격은 뒤의 거리에 따라 달라집니다. ↵
복구 과정은 이 절에서 규정될 예정입니다(docs/recovery-design.md §5.6). ↵
미러 블록이나 미러 헤더가 있는 암호화 아카이브를 복구하려면 인증에 실패한 청크 이후의 암호화된 청크를 복호화해야 하지만, 표준 age 구현은 이를 수행하지 않습니다. 이는 복구 과정과 함께, 복호화된 바이트를 생성하는 별도의 단계로 규정될 예정이며, 이 단계에서 그러한 청크의 바이트는 소거로 표시되고 아카이브 해석은 변경되지 않습니다(docs/recovery-design.md §5.6). ↵
초기 사전의 데이터와 해시는 아직 만들어지지 않았습니다. ↵
초기 엔트리는 아직 확정되지 않았습니다. 이 엔트리의 모듈과 이를 설명하는 문서는 이 문서의 첫 번째 버전이 나오기 전까지 변경될 수 있으며, 그때까지 변경된 엔트리는 새 버전으로 추가되지 않고 이 표에서 대체됩니다. ↵