| 인터넷 엔지니어링 태스크 포스 (IETF) | C. Krasic |
| 의견 요청(Request for Comments): 9204 | M. Bishop |
| 카테고리: 표준 트랙 | Akamai Technologies |
| ISSN: 2070-1721 | A. Frindell, Editor |
| 2022년 6월 |
QPACK: HTTP/3용 필드 압축
초록
이 규격은 HTTP/3에서 사용하기 위한 HTTP 필드를 효율적으로 표현하기 위한 압축 형식인 QPACK을 정의합니다. 이는 HPACK 압축의 변형으로, 헤드-오브-라인(blocking)을 줄이는 것을 목표로 합니다.
이 메모의 상태
이 문서는 인터넷 표준 트랙 문서입니다.
이 문서는 인터넷 엔지니어링 태스크 포스(IETF)의 산물입니다. 이는 IETF 커뮤니티의 합의를 나타냅니다. 공개 검토를 거쳤고 인터넷 엔지니어링 운영 그룹(IESG)에 의해 출판 승인을 받았습니다. 인터넷 표준에 관한 추가 정보는 RFC 7841의 섹션 2에서 확인할 수 있습니다.
이 문서의 현재 상태, 정정 사항, 및 이에 대한 피드백 제공 방법은 https://www.rfc-editor.org/info/rfc9204에서 확인할 수 있습니다.
Copyright Notice
Copyright (c) 2022 IETF Trust and the persons identified as the document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.
1. 소개
The QUIC transport protocol ([QUIC-TRANSPORT]) 은 HTTP 의미론을 지원하도록 설계되었으며, 그 설계는 HTTP/2([HTTP/2])의 많은 기능을 포함합니다. HTTP/2는 헤더 및 트레일러 섹션의 압축을 위해 HPACK([RFC7541])을 사용합니다. 만약 HPACK이 HTTP/3([HTTP/3])에 사용된다면, 모든 스트림의 프레임에 대한 전체 순서에 대한 내재된 가정 때문에 필드 섹션에서 헤드-오브-라인 블로킹을 유발할 것입니다.
QPACK은 HPACK의 핵심 개념을 재사용하지만, 순서가 뒤바뀐 전달이 있는 상황에서도 올바르게 동작하도록 재설계되었고, 구현체가 헤드-오브-라인 블로킹에 대한 회복력과 최적의 압축률 사이에서 균형을 맞출 수 있도록 유연성을 제공합니다. 설계 목표는 동일한 손실 조건에서 HPACK의 압축률에 가깝게 접근하면서 헤드-오브-라인 블로킹을 크게 줄이는 것입니다.
1.1. 규약 및 정의
문서에서 사용된 핵심 단어 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", 및 "OPTIONAL"은 모두 대문자로 나타나는 경우에 한해 BCP 14([RFC2119]), 및 RFC8174([RFC8174])에 설명된 대로 해석됩니다.
본 문서에서 다음 용어들이 사용됩니다:
- HTTP fields:
-
HTTP 메시지의 일부로 전송되는 메타데이터. 이 용어는 헤더 및 트레일러 필드를 모두 포함합니다. 관용적으로 "headers"라는 용어가 헤더 필드와 트레일러 필드를 가리키는 데 자주 사용되어 왔으며, 본 문서에서는 일반성을 위해 "fields"라는 용어를 사용합니다.
- HTTP field line:
- HTTP field value:
-
필드 이름과 연관된 데이터로, 해당 섹션에서 동일한 필드 이름을 가진 모든 필드 라인 값들을 쉼표로 연결하여 구성됩니다.
- Field section:
-
HTTP 메시지와 연관된 정렬된 HTTP 필드 라인 모음. 필드 섹션은 동일한 이름을 가진 여러 필드 라인을 포함할 수 있으며, 중복 필드 라인도 포함할 수 있습니다. HTTP 메시지는 헤더 및 트레일러 섹션을 모두 포함할 수 있습니다.
- Representation:
-
동적 및 정적 테이블을 참조하여 필드 라인을 표현하는 명령어.
- Encoder:
-
필드 섹션을 인코딩하는 구현체.
- Decoder:
-
인코딩된 필드 섹션을 디코드하는 구현체.
- Absolute Index:
-
동적 테이블의 각 항목에 대한 고유 인덱스.
- Base:
-
상대 및 포스트-베이스 인덱스의 기준점. 동적 테이블 항목을 참조하는 표현은 Base를 기준으로 합니다.
- Insert Count:
-
동적 테이블에 삽입된 항목의 총 수.
QPACK은 약어가 아니라 이름임에 유의하십시오.
1.2. 표기 규약
이 문서의 도표는 RFC 2360의 섹션 3.1에서 설명된 형식을 사용하며, 다음의 추가 규약을 따릅니다:
- x (A)
-
x가 A비트 길이임을 나타냅니다.
- x (A+)
-
x가 A비트 접두사로 시작하는 접두사 정수 인코딩(섹션 4.1.1)을 사용함을 나타냅니다.
- x ...
-
x가 가변 길이이며 영역의 끝까지 확장됨을 나타냅니다.
2. 압축 프로세스 개요
HPACK처럼 QPACK도 필드 라인("headers")을 인덱스에 연결하기 위해 두 개의 테이블을 사용합니다. 정적 테이블(섹션 3.1)은 미리 정의되어 자주 사용되는 헤더 필드 라인(일부는 빈 값 포함)을 담고 있습니다. 동적 테이블(섹션 3.2)은 연결 동안 구축되며, 인코더가 인코딩된 필드 섹션에서 헤더 및 트레일러 필드 라인을 인덱싱하는 데 사용할 수 있습니다.
QPACK은 인코더에서 디코더로, 그리고 디코더에서 인코더로 명령을 보내기 위한 단방향 스트림을 정의합니다.
2.1. 인코더
인코더는 헤더 또는 트레일러 섹션을 일련의 표현으로 변환하며, 각 필드 라인에 대해 인덱스형 표현 또는 리터럴 표현을 방출합니다(자세한 내용은 섹션 4.5 참조). 인덱스형 표현은 리터럴 이름과 경우에 따라 값을 정적 또는 동적 테이블의 인덱스로 대체하여 높은 압축을 달성합니다. 정적 테이블 참조와 리터럴 표현은 동적 상태를 필요로 하지 않으며 헤드-오브-라인 블로킹을 유발하지 않습니다. 동적 테이블을 참조하는 경우에는, 인코더가 해당 항목이 디코더에 사용 가능하다는 확인(ack)을 받지 못했다면 헤드-오브-라인 블로킹 위험이 있습니다.
인코더는 인원 삽입을 포함하여 원하는 어떤 항목도 동적 테이블에 삽입할 수 있습니다(MAY).
QPACK은 각 필드 섹션 내의 필드 라인 순서를 보존합니다. 인코더는 입력 필드 섹션에 나타나는 순서대로 필드 표현을 방출해야 합니다(MUST).
QPACK은 선택적 상태 추적의 부담을 인코더에 두도록 설계되어, 디코더는 상대적으로 단순하게 유지됩니다.
2.1.1. 동적 테이블 삽입 제한
동적 테이블에 항목을 삽입하는 것이 불가능할 수 있으며, 특히 테이블에 축출할 수 없는 항목이 포함된 경우가 있습니다.
동적 테이블 항목은 삽입 직후 즉시 축출될 수 없습니다. 삽입이 확인(ack)되고, 확인되지 않은 표현들에서 해당 항목에 대한 미해결 참조가 없을 때 그 항목은 축출 가능해집니다. 인코더 스트림에서의 참조는 해당 항목을 축출하는 명령이 처리되기 전에 항상 처리된다는 보장이 있으므로 이러한 참조는 항목의 축출을 방해하지 않습니다.
만약 새 항목을 추가하기 위해 다른 항목을 축출해야 하는데, 축출되어야 할 항목들이 축출 불가능한 경우, 인코더는 해당 항목(이미 존재하는 항목의 중복 포함)을 동적 테이블에 삽입해서는 안 됩니다(MUST NOT). 이를 피하려면, 동적 테이블을 사용하는 인코더는 디코더가 확인할 때까지 각 필드 섹션이 참조하는 동적 테이블 항목을 추적해야 합니다(섹션 4.4.1 참조).
2.1.1.1. 금지된 삽입 피하기
인코더가 새 항목을 추가하지 못하는 상황을 방지하기 위해, 인코더는 곧 축출될 가능성이 있는 항목을 참조하지 않도록 할 수 있습니다. 그런 항목을 직접 참조하는 대신 인코더는 Duplicate 명령(섹션 4.3.4)을 발행하고 복제된 항목을 참조할 수 있습니다.
어떤 항목이 축출에 너무 가까워 참조해서는 안 되는지를 판단하는 것은 인코더의 선호에 따른 문제입니다. 한 가지 휴리스틱은 동적 테이블에서 고정된 양의 사용 가능한 공간(사용되지 않은 공간 또는 비차단 항목을 축출하여 회수할 수 있는 공간)을 목표로 하는 것입니다. 이를 달성하기 위해 인코더는 draining index(배수 인덱스)를 유지할 수 있는데, 이는 인코더가 참조할 인덱스 중 가장 작은 절대 인덱스(섹션 3.2.4)입니다. 새 항목이 삽입됨에 따라 인코더는 참조하지 않을 테이블 구간을 유지하기 위해 draining index를 증가시킵니다. 인코더가 draining index보다 작은 절대 인덱스를 가진 항목에 대한 새로운 참조를 만들지 않으면, 해당 항목들에 대한 미확인 참조 수는 결국 0이 되어 축출될 수 있게 됩니다.
<-- Newer Entries Older Entries -->
(Larger Indices) (Smaller Indices)
+--------+---------------------------------+----------+
| Unused | Referenceable | Draining |
| Space | Entries | Entries |
+--------+---------------------------------+----------+
^ ^ ^
| | |
Insertion Point Draining Index Dropping
Point
그림 1: 동적 테이블 항목 드레이닝
2.1.2. 차단된 스트림
QUIC은 서로 다른 스트림 간의 데이터 순서를 보장하지 않기 때문에, 디코더는 아직 수신하지 못한 동적 테이블 항목을 참조하는 표현을 만날 수 있습니다.
각 인코딩된 필드 섹션은 Required Insert Count(섹션 4.5.1)를 포함하며, 이는 해당 필드 섹션을 디코딩할 수 있는 Insert Count의 최저 가능 값을 나타냅니다. 동적 테이블 참조를 사용하여 인코딩된 필드 섹션의 경우 Required Insert Count는 참조된 모든 동적 테이블 항목의 가장 큰 절대 인덱스보다 하나 큽니다. 동적 테이블 참조가 없는 필드 섹션의 경우 Required Insert Count는 0입니다.
디코더가 자신의 Insert Count보다 큰 Required Insert Count를 가진 인코딩된 필드 섹션을 수신하면, 해당 스트림은 즉시 처리할 수 없으며 "차단됨"으로 간주됩니다(섹션 2.2.1 참조).
디코더는 SETTINGS_QPACK_BLOCKED_STREAMS 설정을 사용하여 차단될 수 있는 스트림 수의 상한을 지정합니다(섹션 5 참조). 인코더는 항상 SETTINGS_QPACK_BLOCKED_STREAMS 값으로 차단될 수 있는 스트림 수를 제한해야 합니다(MUST). 만약 디코더가 지원하겠다고 약속한 것보다 더 많은 차단된 스트림을 만나면, 이는 QPACK_DECOMPRESSION_FAILED 유형의 연결 오류로 처리되어야 합니다.
디코더가 위험할 수 있는 모든 스트림에서 실제로 차단 상태가 되는 것은 아님에 유의하십시오.
인코더는 스트림이 차단되는 위험을 감수할지 여부를 결정할 수 있습니다. SETTINGS_QPACK_BLOCKED_STREAMS의 값이 허용하는 경우, 전송 중인 동적 테이블 항목을 참조함으로써 종종 압축 효율을 개선할 수 있지만, 손실이나 재정렬이 발생하면 해당 스트림이 디코더에서 차단될 수 있습니다. 인코더는 확인된 항목만 참조하여 차단 위험을 피할 수 있지만, 이는 리터럴을 사용해야 할 수 있음을 의미합니다. 리터럴은 인코딩된 필드 섹션을 더 크게 만들기 때문에, 인코더가 혼잡 또는 흐름 제어 제한으로 인해 차단되는 결과를 초래할 수 있습니다.
2.1.3. 흐름 제어 교착 상태 방지
흐름 제어에 의해 제한되는 스트림에 명령을 쓰는 것은 교착 상태를 초래할 수 있습니다.
디코더는 필요한 업데이트가 인코더 스트림에서 수신될 때까지 인코딩된 필드 섹션을 전달하는 스트림에 대한 흐름 제어 크레딧을 중단할 수 있습니다. 만약 인코더 스트림(또는 연결 전체)에 대한 흐름 제어 크레딧 부여가 인코딩된 필드 섹션을 포함하는 스트림의 데이터 소비 및 해제에 의존한다면 교착 상태가 발생할 수 있습니다.
일반적으로, 큰 명령을 포함하는 스트림은 디코더가 그 명령이 완전히 수신될 때까지 흐름 제어 크레딧을 보류하면 교착 상태에 빠질 수 있습니다.
이러한 교착 상태를 피하기 위해, 인코더는 전체 명령에 대해 충분한 스트림 및 연결 흐름 제어 크레딧이 있는 경우에만 명령을 작성하는 것이 권고됩니다(SHOULD NOT 쓰지 말아야 합니다).
2.1.4. 알려진 수신 횟수
Known Received Count는 디코더가 확인한 동적 테이블 삽입 및 중복의 총 수입니다. 인코더는 어떤 동적 테이블 항목을 잠재적으로 차단 없이 참조할 수 있는지 식별하기 위해 Known Received Count를 추적합니다. 디코더는 Insert Count Increment 명령을 보낼 수 있도록 Known Received Count를 추적합니다.
섹션 확인 명령(4.4.1)은 디코더가 해당 필드 섹션을 디코딩하는 데 필요한 모든 동적 테이블 상태를 수신했음을 의미합니다. 만약 확인된 필드 섹션의 Required Insert Count가 현재 Known Received Count보다 크면, Known Received Count는 그 Required Insert Count 값으로 업데이트됩니다.
Insert Count Increment 명령(4.4.3)은 그 Increment 매개변수만큼 Known Received Count를 증가시킵니다. 지침은 섹션 2.2.2.3을 참조하십시오.
2.2. 디코더
HPACK과 마찬가지로 디코더는 일련의 표현을 처리하고 해당 필드 섹션을 출력합니다. 또한 동적 테이블을 수정하는 인코더 스트림에서 수신된 명령을 처리합니다. 인코딩된 필드 섹션과 인코더 스트림 명령이 별도의 스트림에서 도착한다는 점에 유의하십시오. 이는 HPACK과 달라서, HPACK에서는 인코딩된 필드 섹션(헤더 블록)이 동적 테이블을 수정하는 명령을 포함할 수 있고 전용의 HPACK 명령 스트림이 없습니다.
디코더는 필드 라인을 인코딩된 필드 섹션에 나타나는 표현의 순서대로 내보내야 합니다(MUST).
2.2.1. 차단된 디코딩
인코딩된 필드 섹션을 수신하면, 디코더는 Required Insert Count를 검사합니다. Required Insert Count가 디코더의 Insert Count 이하이면 해당 필드 섹션은 즉시 처리할 수 있습니다. 그렇지 않으면 필드 섹션이 수신된 스트림은 차단됩니다.
차단 상태 동안 인코딩된 필드 섹션 데이터는 차단된 스트림의 흐름 제어 윈도우에 남아 있어야 합니다(SHOULD). 이 데이터는 스트림이 차단 해제될 때까지 사용할 수 없으며, 흐름 제어를 조기에 해제하면 디코더가 메모리 고갈 공격에 취약해집니다. 스트림은 디코더가 해당 스트림에서 읽기 시작한 모든 인코딩된 필드 섹션에 대해 Insert Count가 Required Insert Count 이상이 될 때 차단 해제됩니다.
인코딩된 필드 섹션을 처리할 때, 디코더는 Required Insert Count가 해당 필드 섹션을 디코딩할 수 있는 Insert Count의 최저 가능 값과 일치할 것으로 기대합니다(섹션 2.1.2 참조). 만약 기대보다 작은 Required Insert Count를 만나면, 이는 QPACK_DECOMPRESSION_FAILED 유형의 연결 오류로 처리되어야 합니다(섹션 2.2.3 참조). 기대보다 큰 Required Insert Count를 만나면, 디코더는 QPACK_DECOMPRESSION_FAILED 유형의 연결 오류로 처리할 수 있습니다(MAY).
2.2.2. 상태 동기화
디코더는 디코더 스트림에서 디코더 명령(섹션 4.4)을 발행함으로써 다음 이벤트들을 신호합니다.
2.2.2.1. 필드 섹션 처리 완료
디코더가 동적 테이블 참조를 포함하는 표현으로 인코딩된 필드 섹션의 디코딩을 마치면, 디코더는 섹션 확인 명령(4.4.1)을 발행해야 합니다(MUST). 스트림은 중간 응답, 트레일러 및 푸시된 요청의 경우 여러 필드 섹션을 가질 수 있습니다. 인코더는 각 섹션 확인 명령을 해당 스트림에서 전송된 동적 테이블 참조를 포함하는 가장 이른 미확인 필드 섹션을 확인한 것으로 해석합니다.
2.2.2.2. 스트림 포기
엔드포인트가 스트림의 끝 전에 스트림 리셋을 받거나 해당 스트림에서 모든 인코딩된 필드 섹션이 처리되기 전에 읽기를 포기하면, 디코더는 스트림 취소 명령을 생성합니다(섹션 4.4.2 참조). 이는 해당 스트림에 대한 동적 테이블 참조가 더 이상 미해결 상태가 아님을 인코더에 신호합니다. 최대 동적 테이블 용량(섹션 3.2.3)이 0인 디코더는 스트림 취소를 생략할 수 있습니다(MAY), 왜냐하면 인코더는 동적 테이블 참조를 가질 수 없기 때문입니다. 인코더는 이 명령만으로 동적 테이블에 대한 어떤 업데이트가 수신되었다고 추론할 수 없습니다.
섹션 확인 및 스트림 취소 명령은 인코더가 동적 테이블 항목에 대한 참조를 제거할 수 있게 합니다. Known Received Count보다 작은 절대 인덱스를 가진 항목이 참조를 모두 잃으면 축출 가능하다고 간주됩니다(섹션 2.1.1 참조).
2.2.2.3. 새 테이블 항목
인코더 스트림에서 새 테이블 항목을 수신한 후, 디코더는 Insert Count Increment 명령을 언제 발행할지 선택합니다(섹션 4.4.3 참조). 각 새 동적 테이블 항목을 추가한 후 즉시 이 명령을 발행하면 인코더에 가장 신속한 피드백을 제공하지만, 다른 디코더 피드백과 중복될 수 있습니다. Insert Count Increment 명령을 지연함으로써 디코더는 여러 개를 병합하거나 섹션 확인으로 대체할 수 있습니다(섹션 4.4.1 참조). 그러나 너무 오래 지연하면 인코더가 항목이 확인되기를 기다려 사용을 미루는 동안 압축 비효율이 발생할 수 있습니다.
2.2.3. 잘못된 참조
디코더가 필드 라인 표현에서 이미 축출되었거나 선언된 Required Insert Count(섹션 4.5.1)보다 크거나 같은 절대 인덱스를 가진 동적 테이블 항목을 참조하는 것을 만나면, 이는 QPACK_DECOMPRESSION_FAILED 유형의 연결 오류로 처리되어야 합니다(MUST).
디코더가 인코더 명령에서 이미 축출된 동적 테이블 항목을 참조하는 것을 만나면, 이는 QPACK_ENCODER_STREAM_ERROR 유형의 연결 오류로 처리되어야 합니다(MUST).
3. 참조 테이블
HPACK과 달리 QPACK의 정적 및 동적 테이블 항목은 별도로 주소 지정됩니다. 다음 절에서는 각 테이블의 항목이 어떻게 주소 지정되는지 설명합니다.
3.1. 정적 테이블
정적 테이블은 미리 정의된 필드 라인 목록으로 구성되며, 각 항목은 시간이 지나도 고정된 인덱스를 가집니다. 그 항목들은 부록 A에 정의되어 있습니다.
정적 테이블의 모든 항목에는 이름과 값이 있습니다. 다만 값은 비어 있을 수 있습니다(즉, 길이가 0일 수 있음). 각 항목은 고유한 인덱스로 식별됩니다.
QPACK 정적 테이블의 인덱스는 0부터 시작한다는 점에 유의하십시오. 반면 HPACK 정적 테이블은 1부터 인덱싱됩니다.
디코더가 필드 라인 표현에서 잘못된 정적 테이블 인덱스를 만나면, 이를 QPACK_DECOMPRESSION_FAILED 유형의 연결 오류로 처리해야 합니다(MUST). 이 인덱스가 인코더 스트림에서 수신된 경우에는 이를 QPACK_ENCODER_STREAM_ERROR 유형의 연결 오류로 처리해야 합니다(MUST).
3.2. 동적 테이블
동적 테이블은 선입선출(FIFO) 순서로 유지되는 필드 라인 목록으로 구성됩니다. QPACK 인코더와 디코더는 처음에는 비어 있는 동적 테이블을 공유합니다. 인코더는 동적 테이블에 항목을 추가하고 이를 인코더 스트림의 명령을 통해 디코더에 전송합니다(자세한 내용은 섹션 4.3 참조).
동적 테이블은 중복 항목(즉, 동일한 이름과 동일한 값을 가진 항목)을 포함할 수 있습니다. 따라서 디코더는 중복 항목을 오류로 처리해서는 안 됩니다(MUST NOT).
동적 테이블 항목의 값은 비어 있을 수 있습니다.
3.2.1. 동적 테이블 크기
동적 테이블의 크기는 그 항목들의 크기의 합입니다.
항목의 크기는 이름의 바이트 길이, 값의 바이트 길이, 그리고 추가로 32바이트를 합한 값입니다. 항목의 크기는 이름과 값에 허프만 인코딩을 적용하지 않은 길이를 사용하여 계산됩니다.
3.2.2. 동적 테이블 용량 및 축출
인코더는 동적 테이블의 용량(capacity)을 설정하며, 이는 테이블 크기의 상한으로 작동합니다. 동적 테이블의 초기 용량은 0입니다. 인코더는 0이 아닌 용량으로 Set Dynamic Table Capacity 명령(섹션 4.3.1)을 전송하여 동적 테이블 사용을 시작합니다.
새 항목이 동적 테이블에 추가되기 전에, 동적 테이블의 끝에서부터 항목들을 축출하여 동적 테이블의 크기가 (테이블 용량 - 새 항목 크기) 이하가 될 때까지 진행됩니다. 인코더는 축출될 항목이 축출 가능하지 않은 경우에는 해당 항목을 축출하도록 해서는 안 됩니다(MUST NOT); 관련 내용은 섹션 2.1.1을 참조하십시오. 그런 다음 새 항목이 테이블에 추가됩니다. 만약 인코더가 동적 테이블 용량보다 큰 항목을 추가하려고 시도하면 이는 오류이며, 디코더는 이를 QPACK_ENCODER_STREAM_ERROR 유형의 연결 오류로 처리해야 합니다(MUST).
새 항목은 해당 항목을 추가할 때 축출될 동적 테이블의 항목을 참조할 수 있습니다. 구현체는 참조된 항목이 새 항목을 삽입하기 전에 동적 테이블에서 축출되는 경우 참조된 이름이나 값을 삭제하지 않도록 주의해야 합니다.
인코더가 동적 테이블 용량을 줄이면(섹션 4.3.1 참조), 동적 테이블의 끝에서부터 항목들이 축출되어 동적 테이블의 크기가 새로운 테이블 용량 이하가 될 때까지 진행됩니다. 이 메커니즘은 용량을 0으로 설정하여 동적 테이블의 항목을 완전히 지운 다음 이후에 복원하는 데 사용할 수 있습니다.
3.2.3. 동적 테이블 최대 용량
디코더의 메모리 요구를 제한하기 위해, 디코더는 인코더가 동적 테이블 용량으로 설정할 수 있는 최대값을 제한합니다. HTTP/3에서는 이 한도가 디코더가 전송하는 SETTINGS_QPACK_MAX_TABLE_CAPACITY 값으로 결정됩니다; 자세한 내용은 섹션 5을 참조하십시오. 인코더는 이 최대값을 초과하는 동적 테이블 용량을 설정해서는 안 되지만(MUST NOT), 더 낮은 용량을 선택할 수 있습니다(섹션 4.3.1 참조).
0-RTT 데이터를 사용하는 클라이언트의 경우, 서버의 최대 테이블 용량은 설정의 기억된 값이며, 이전에 전송되지 않았다면 0입니다. 클라이언트의 0-RTT 설정 값이 0인 경우 서버는 SETTINGS 프레임에서 이를 0이 아닌 값으로 설정할 수 있습니다(MAY). 기억된 값이 0이 아닌 경우 서버는 반드시 동일한 0이 아닌 값을 자신의 SETTINGS 프레임에 보내야 합니다(MUST). 만약 서버가 다른 값을 지정하거나 SETTINGS에서 SETTINGS_QPACK_MAX_TABLE_CAPACITY를 생략하면, 인코더는 이를 QPACK_DECODER_STREAM_ERROR 유형의 연결 오류로 처리해야 합니다.
0-RTT 데이터를 사용하지 않는 클라이언트(0-RTT가 시도되지 않았거나 거부된 경우) 및 모든 HTTP/3 서버의 경우, 인코더가 non-zero SETTINGS_QPACK_MAX_TABLE_CAPACITY 값을 처리할 때까지 최대 테이블 용량은 0입니다.
최대 테이블 용량이 0일 때 인코더는 동적 테이블에 항목을 삽입해서는 안 되며(MUST NOT), 인코더 스트림에서 어떤 인코더 명령도 보내서는 안 됩니다(MUST NOT).
3.2.4. 절대 인덱싱
각 항목은 그 항목의 생존 기간 동안 고정되는 절대 인덱스를 가집니다. 처음 삽입된 항목의 절대 인덱스는 0이며, 삽입이 진행될수록 인덱스는 하나씩 증가합니다.
3.2.5. 상대 인덱싱
상대 인덱스는 0에서 시작하며 절대 인덱스와 반대 방향으로 증가합니다. 상대 인덱스 0이 어떤 항목을 가리키는지는 참조의 문맥에 따라 달라집니다.
인코더 명령(섹션 4.3)에서 상대 인덱스 0은 동적 테이블에 가장 최근에 삽입된 값을 가리킵니다. 따라서 인코더 스트림에서 명령을 해석하는 동안 특정 상대 인덱스로 참조되는 항목은 변경될 수 있습니다.
+-----+---------------+-------+
| n-1 | ... | d | Absolute Index
+ - - +---------------+ - - - +
| 0 | ... | n-d-1 | Relative Index
+-----+---------------+-------+
^ |
| V
Insertion Point Dropping Point
n = count of entries inserted
d = count of entries dropped
그림 2: 예시 동적 테이블 인덱싱 - 인코더 스트림
인코더 명령과 달리, 필드 라인 표현에서의 상대 인덱스는 인코딩된 필드 섹션 시작 시의 Base를 기준으로 합니다(섹션 4.5.1 참조). 이는 인코딩된 필드 섹션과 동적 테이블 업데이트가 순서가 뒤바뀐 상태로 처리되더라도 참조가 안정적임을 보장합니다.
필드 라인 표현에서 상대 인덱스 0은 절대 인덱스가 Base - 1인 항목을 가리킵니다.
Base
|
V
+-----+-----+-----+-----+-------+
| n-1 | n-2 | n-3 | ... | d | Absolute Index
+-----+-----+ - +-----+ - +
| 0 | ... | n-d-3 | Relative Index
+-----+-----+-------+
n = count of entries inserted
d = count of entries dropped
In this example, Base = n - 2
그림 3: 예시 동적 테이블 인덱싱 - 표현에서의 상대 인덱스
3.2.6. 포스트-베이스 인덱싱
포스트-베이스 인덱스는 Base보다 크거나 같는 절대 인덱스를 가진 항목들에 대해 필드 라인 표현에서 사용됩니다. 이 인덱스는 절대 인덱스가 Base인 항목을 0으로 시작하여 절대 인덱스와 동일한 방향으로 증가합니다.
포스트-베이스 인덱스는 인코더가 단일 패스로 필드 섹션을 처리하면서 그 처리 도중(또는 다른 필드 섹션을 처리하는 동안)에 추가된 항목들을 참조할 수 있게 합니다.
Base
|
V
+-----+-----+-----+-----+-----+
| n-1 | n-2 | n-3 | ... | d | Absolute Index
+-----+-----+-----+-----+-----+
| 1 | 0 | Post-Base Index
+-----+-----+
n = count of entries inserted
d = count of entries dropped
In this example, Base = n - 2
그림 4: 예시 동적 테이블 인덱싱 - 표현에서의 포스트-베이스 인덱스
4. 와이어 형식
4.1. 원시 타입
4.1.1. 접두사 정수
문서 전반에서 RFC 7541 섹션 5.1에 정의된 접두사 정수 형식이 광범위하게 사용됩니다. RFC 7541의 형식은 수정 없이 사용됩니다. 다만 QPACK은 HPACK에서 실제로 사용되지 않은 일부 접두사 크기를 사용할 수 있습니다.
QPACK 구현체는 최대 62비트 길이의 정수를 디코드할 수 있어야 합니다(MUST).
4.1.2. 문자열 리터럴
문서 전반에서 RFC 7541 섹션 5.2에 정의된 문자열 리터럴 형식도 사용됩니다. 이 문자열 형식은 선택적 허프만 인코딩을 포함합니다.
HPACK은 문자열 리터럴이 바이트 경계에서 시작하도록 정의합니다. 문자열은 본 문서에서 'H'로 표기되는 허프만 플래그 1비트로 시작하고, 이어서 7비트 접두사 정수로 인코딩된 문자열 길이, 마지막으로 지정된 바이트 수의 데이터가 옵니다. 허프만 인코딩이 사용될 때는 RFC 7541의 부록 B의 허프만 테이블을 수정 없이 사용하며, 표시된 길이는 인코딩 후의 문자열 크기입니다.
본 문서는 문자열 리터럴이 바이트 경계가 아닌 위치에서 시작하는 것을 허용하도록 정의를 확장합니다. "N-비트 접두사 문자열 리터럴"은 바이트 중간에서 시작하며 처음 (8-N) 비트는 이전 필드에 할당됩니다. 문자열은 허프만 플래그 1비트를 사용하며, 이어서 (N-1)-비트 접두사 정수로 인코딩된 인코딩된 문자열의 길이가 옵니다. 접두사 크기 N은 2에서 8 사이의 값을 가질 수 있습니다. 문자열 리터럴의 나머지 부분은 변경되지 않습니다.
접두사 길이가 명시되지 않은 문자열 리터럴은 8비트 접두사 문자열 리터럴로 간주되며 RFC 7541의 정의를 수정 없이 따릅니다.
4.2. 인코더 및 디코더 스트림
QPACK은 두 가지 단방향 스트림 유형을 정의합니다:
- 인코더 스트림은 유형 0x02인 단방향 스트림입니다. 이는 인코더에서 디코더로 전송되는 프레임 없음의 일련의 인코더 명령을 담습니다.
- 디코더 스트림은 유형 0x03인 단방향 스트림입니다. 이는 디코더에서 인코더로 전송되는 프레임 없음의 일련의 디코더 명령을 담습니다.
HTTP/3 엔드포인트는 QPACK 인코더와 디코더를 포함합니다. 각 엔드포인트는 인코더 스트림과 디코더 스트림을 각각 최대 하나씩만 생성해야 합니다(MUST). 동일한 유형의 두 번째 스트림을 수신하면 H3_STREAM_CREATION_ERROR 유형의 연결 오류로 처리해야 합니다.
보낸이는 이들 스트림 중 어느 것도 닫아서는 안 되며(MUST NOT), 수신자는 보낸이가 이들 스트림을 닫도록 요청해서는 안 됩니다(MUST NOT). 이들 단방향 스트림 중 어느 하나의 닫힘은 H3_CLOSED_CRITICAL_STREAM 유형의 연결 오류로 처리되어야 합니다(MUST).
예를 들어 인코더가 동적 테이블을 사용하지 않거나 피어가 허용하는 동적 테이블 최대 크기가 0인 경우, 엔드포인트는 인코더 스트림을 생성하지 않을 수 있습니다(MAY).
동적 테이블의 최대 용량을 0으로 설정하는 디코더의 경우, 엔드포인트는 디코더 스트림을 생성하지 않을 수 있습니다(MAY).
설정 때문에 해당 스트림들이 사용 불가능하더라도, 엔드포인트는 피어가 인코더 스트림과 디코더 스트림을 생성하도록 허용해야 합니다(MUST).
4.3. 인코더 명령
인코더는 인코더 스트림에서 동적 테이블의 용량을 설정하고 동적 테이블 항목을 추가하는 인코더 명령을 전송합니다. 테이블 항목을 추가하는 명령은 기존 항목을 사용하여 중복 정보를 전송하지 않도록 할 수 있습니다. 이름은 정적 또는 동적 테이블의 기존 항목을 참조하여 전송되거나 문자열 리터럴로 전송될 수 있습니다. 이미 동적 테이블에 존재하는 항목의 경우, 전체 항목을 참조로 사용하여 복제 항목을 생성할 수도 있습니다.
4.3.1. 동적 테이블 용량 설정
인코더는 '001' 3비트 패턴으로 시작하는 명령을 사용하여 동적 테이블 용량 변경을 디코더에 알립니다. 그 뒤에는 5비트 접두사 정수로 표현된 새로운 동적 테이블 용량이 옵니다(섹션 4.1.1 참조).
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 1 | Capacity (5+) | +---+---+---+-------------------+
그림 5: 동적 테이블 용량 설정
4.3.2. 이름 참조로 삽입
인코더는 필드 이름이 정적 또는 동적 테이블의 항목 이름과 일치하는 항목을 동적 테이블에 추가할 때 '1' 1비트 패턴으로 시작하는 명령을 사용합니다. 두 번째 비트('T')는 참조가 정적 테이블인지 동적 테이블인지를 나타냅니다. 뒤따르는 6비트 접두사 정수(섹션 4.1.1)는 필드 이름에 대한 테이블 항목을 찾는 데 사용됩니다. T=1인 경우 숫자는 정적 테이블 인덱스를 나타내고, T=0인 경우 숫자는 동적 테이블의 상대 인덱스입니다.
필드 이름 참조 다음에는 문자열 리터럴로 표현된 필드 값이 옵니다(섹션 4.1.2 참조).
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | T | Name Index (6+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
그림 6: 필드 라인 삽입 — 인덱스된 이름
4.3.3. 리터럴 이름으로 삽입
인코더는 필드 이름과 필드 값이 모두 문자열 리터럴로 표현되는 항목을 동적 테이블에 추가할 때 '01' 2비트 패턴으로 시작하는 명령을 사용합니다.
그 다음에는 이름이 6비트 접두사 문자열 리터럴로, 값이 8비트 접두사 문자열 리터럴로 표현됩니다(섹션 4.1.2 참조).
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | H | Name Length (5+) |
+---+---+---+-------------------+
| Name String (Length bytes) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
그림 7: 필드 라인 삽입 — 새 이름
4.3.4. 중복
인코더는 '000' 3비트 패턴으로 시작하는 명령을 사용하여 동적 테이블의 기존 항목을 복제합니다. 그 뒤에는 5비트 접두사 정수로 표현된 기존 항목의 상대 인덱스가 옵니다(섹션 4.1.1 참조).
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | Index (5+) |
+---+---+---+-------------------+
그림 8: 중복
기존 항목은 이름이나 값을 다시 전송하지 않고 동적 테이블에 재삽입됩니다. 이는 오래된 항목을 참조하는 것을 피하여 새 항목 삽입을 차단하는 것을 방지하는 데 유용합니다.
4.4. 디코더 명령
디코더는 디코더 스트림에서 디코더 명령을 전송하여 필드 섹션 및 테이블 업데이트의 처리를 인코더에 알리고 동적 테이블의 일관성을 보장합니다.
4.4.1. 섹션 확인
선언된 Required Insert Count가 0이 아닌 인코딩된 필드 섹션을 처리한 후에, 디코더는 섹션 확인 명령을 발행합니다. 이 명령은 '1' 1비트 패턴으로 시작하며, 이어서 해당 필드 섹션과 연관된 스트림 ID가 7비트 접두사 정수로 인코딩되어 옵니다(참조 섹션 4.1.1).
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 1 | Stream ID (7+) | +---+---------------------------+
그림 9: 섹션 확인
인코더가 이미 비영(非零) Required Insert Count를 가진 모든 인코딩된 필드 섹션을 확인한 스트림을 참조하는 섹션 확인 명령을 수신한 경우, 이는 QPACK_DECODER_STREAM_ERROR 유형의 연결 오류로 처리되어야 합니다(MUST).
섹션 확인 명령은 Known Received Count를 증가시킬 수 있습니다; 관련 내용은 섹션 2.1.4를 참조하십시오.
4.4.2. 스트림 취소
스트림이 리셋되거나 읽기를 중단할 때, 디코더는 스트림 취소 명령을 발행합니다. 이 명령은 '01' 2비트 패턴으로 시작하며, 이어서 영향을 받는 스트림의 스트림 ID가 6비트 접두사 정수로 인코딩되어 옵니다.
이 명령은 섹션 2.2.2에서 설명된 대로 사용됩니다.
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | Stream ID (6+) | +---+---+-----------------------+
그림 10: 스트림 취소
4.4.3. 삽입 수 증가
삽입 수 증가 명령은 '00' 2비트 패턴으로 시작하며, 이어서 Increment가 6비트 접두사 정수로 인코딩되어 옵니다. 이 명령은 Known Received Count(섹션 2.1.4)를 Increment 매개변수 값만큼 증가시킵니다. 디코더는 지금까지 처리된 동적 테이블 삽입 및 복제의 총 수까지 Known Received Count를 증가시키는 Increment 값을 보내야 합니다.
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | Increment (6+) | +---+---+-----------------------+
그림 11: 삽입 수 증가
Increment 필드가 0이거나, 인코더가 보낸 것보다 Known Received Count를 초과하도록 증가시키는 Increment를 수신한 인코더는 이를 QPACK_DECODER_STREAM_ERROR 유형의 연결 오류로 처리해야 합니다(MUST).
4.5. 필드 라인 표현
인코딩된 필드 섹션은 접두사와 이 절에서 정의된 빈 칸일 수 있는 일련의 표현으로 구성됩니다. 각 표현은 단일 필드 라인에 해당합니다. 이러한 표현은 특정 상태의 정적 테이블 또는 동적 테이블을 참조하지만 그 상태를 변경하지는 않습니다.
인코딩된 필드 섹션은 포함 프로토콜이 정의한 프레임으로 전달됩니다.
4.5.1. 인코딩된 필드 섹션 접두사
각 인코딩된 필드 섹션은 두 정수로 접두사가 붙습니다. Required Insert Count는 8비트 접두사를 사용하는 정수로 인코딩됩니다(인코딩 방법은 섹션 4.5.1.1 참조). Base는 부호 비트('S')와 7비트 접두사를 사용하는 Delta Base 값으로 인코딩됩니다(참조 섹션 4.5.1.2).
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | Required Insert Count (8+) | +---+---------------------------+ | S | Delta Base (7+) | +---+---------------------------+ | Encoded Field Lines ... +-------------------------------+
그림 12: 인코딩된 필드 섹션
4.5.1.1. Required Insert Count
Required Insert Count는 인코딩된 필드 섹션을 처리하는 데 필요한 동적 테이블의 상태를 식별합니다. 차단되는 디코더는 Required Insert Count를 사용하여 필드 섹션의 나머지를 안전하게 처리할 시점을 결정합니다.
인코더는 인코딩하기 전에 Required Insert Count를 다음과 같이 변환합니다:
if ReqInsertCount == 0:
EncInsertCount = 0
else:
EncInsertCount = (ReqInsertCount mod (2 * MaxEntries)) + 1
여기서 MaxEntries는 동적 테이블이 가질 수 있는 항목의 최대 개수입니다. 가장 작은 항목은 이름과 값이 비어 있고 크기는 32입니다. 따라서 MaxEntries는 다음과 같이 계산됩니다:
MaxEntries = floor( MaxTableCapacity / 32 )
MaxTableCapacity는 디코더가 지정한 동적 테이블의 최대 용량입니다; 관련 내용은 섹션 3.2.3를 참조하십시오.
이 인코딩은 장수명 연결에서 접두사의 길이를 제한합니다.
디코더는 다음과 같은 알고리즘으로 Required Insert Count를 재구성할 수 있습니다. 디코더가 합치성 있는 인코더에 의해 생성될 수 없는 EncodedInsertCount 값을 만나면, 이는 QPACK_DECOMPRESSION_FAILED 유형의 연결 오류로 처리되어야 합니다(MUST).
TotalNumberOfInserts는 디코더의 동적 테이블에 대한 총 삽입 수입니다.
FullRange = 2 * MaxEntries
if EncodedInsertCount == 0:
ReqInsertCount = 0
else:
if EncodedInsertCount > FullRange:
Error
MaxValue = TotalNumberOfInserts + MaxEntries
# MaxWrapped is the largest possible value of
# ReqInsertCount that is 0 mod 2 * MaxEntries
MaxWrapped = floor(MaxValue / FullRange) * FullRange
ReqInsertCount = MaxWrapped + EncodedInsertCount - 1
# If ReqInsertCount exceeds MaxValue, the Encoder's value
# must have wrapped one fewer time
if ReqInsertCount > MaxValue:
if ReqInsertCount <= FullRange:
Error
ReqInsertCount -= FullRange
# Value of 0 must be encoded as 0.
if ReqInsertCount == 0:
Error
예를 들어, 동적 테이블이 100바이트인 경우 Required Insert Count는 6으로 모듈로 인코딩됩니다. 디코더가 10개의 삽입을 수신한 경우, 인코딩된 값 4는 필드 섹션의 Required Insert Count가 9임을 나타냅니다.
4.5.1.2. Base
Base는 섹션 3.2.5에서 설명한 대로 동적 테이블 참조를 해석하는 데 사용됩니다.
공간을 절약하기 위해, Base는 Required Insert Count에 상대적으로 인코딩됩니다. 이는 부호 비트('S' in 그림 12)와 Delta Base 값으로 표현됩니다. Sign 비트가 0이면 Base가 Required Insert Count보다 크거나 같음을 나타내며, 디코더는 Delta Base 값을 Required Insert Count에 더하여 Base 값을 결정합니다. Sign 비트가 1이면 Base가 Required Insert Count보다 작음을 나타내며, 디코더는 Delta Base 값을 Required Insert Count에서 빼고 추가로 1을 빼서 Base 값을 결정합니다. 즉:
if Sign == 0:
Base = ReqInsertCount + DeltaBase
else:
Base = ReqInsertCount - DeltaBase - 1
단일 패스 인코더는 필드 섹션을 인코딩하기 전에 Base를 결정합니다. 인코더가 필드 섹션을 인코딩하는 동안 동적 테이블에 항목을 삽입하고 이를 참조하는 경우 Required Insert Count는 Base보다 커지므로 인코딩된 차이는 음수가 되어 Sign 비트는 1로 설정됩니다. 필드 섹션이 테이블의 가장 최근 항목을 참조하지 않고 새 항목을 삽입하지 않은 경우, Base는 Required Insert Count보다 커지며 인코딩된 차이는 양수가 되어 Sign 비트는 0으로 설정됩니다.
Base 값은 음수가 되어서는 안 됩니다(MUST NOT). 프로토콜은 포스트-베이스 인덱싱을 사용하여 음수 Base로도 작동할 수 있지만, 이는 불필요하고 비효율적입니다. Endpoint는 Required Insert Count 값이 Delta Base 값보다 작거나 같을 때 Sign 비트가 1인 필드 블록을 잘못된 것으로 처리해야 합니다(MUST).
필드 섹션을 인코딩하기 전에 테이블 업데이트를 생성하는 인코더는 Base를 Required Insert Count 값으로 설정할 수 있습니다. 이 경우 Sign 비트와 Delta Base는 모두 0으로 설정됩니다.
동적 테이블 참조 없이 인코딩된 필드 섹션은 Base에 대해 아무 값이나 사용할 수 있습니다; Delta Base를 0으로 설정하는 것이 가장 효율적인 인코딩 중 하나입니다.
예를 들어 Required Insert Count가 9일 때, 디코더가 Sign 비트 1과 Delta Base 2를 수신하면 Base는 6이 되고 포스트-베이스 인덱싱으로 3개의 항목을 참조할 수 있게 됩니다. 이 예에서 상대 인덱스 1은 테이블에 추가된 다섯 번째 항목을 가리키고, 포스트-베이스 인덱스 1은 여덟 번째 항목을 가리킵니다.
4.5.2. 인덱스된 필드 라인
인덱스된 필드 라인 표현은 정적 테이블의 항목이나 Base 값보다 작은 절대 인덱스를 가진 동적 테이블의 항목을 식별합니다.
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 1 | T | Index (6+) | +---+---+-----------------------+
그림 13: 인덱스된 필드 라인
이 표현은 '1' 1비트 패턴으로 시작하며, 이어서 참조가 정적 테이블인지 동적 테이블인지를 나타내는 'T' 비트가 옵니다. 뒤따르는 6비트 접두사 정수(섹션 4.1.1)는 필드 라인에 대한 테이블 항목을 찾는 데 사용됩니다. T=1이면 숫자는 정적 테이블 인덱스를 나타내고, T=0이면 숫자는 동적 테이블에서의 상대 인덱스입니다.
4.5.3. 포스트-베이스 인덱스가 있는 인덱스된 필드 라인
포스트-베이스 인덱스를 가진 인덱스된 필드 라인 표현은 절대 인덱스가 Base보다 크거나 같은 동적 테이블의 항목을 식별합니다.
0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 1 | Index (4+) | +---+---+---+---+---------------+
그림 14: 포스트-베이스 인덱스가 있는 인덱스된 필드 라인
4.5.4. 이름 참조가 있는 리터럴 필드 라인
이 표현은 필드 이름이 정적 테이블의 항목 이름과 일치하거나 Base 값보다 작은 절대 인덱스를 가진 동적 테이블 항목의 필드 이름과 일치하는 필드 라인을 인코딩합니다.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | N | T |Name Index (4+)|
+---+---+---+---+---------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
그림 15: 이름 참조가 있는 리터럴 필드 라인
이 표현은 '01' 2비트 패턴으로 시작합니다. 다음 비트인 'N'은 중개자가 이후 홉에서 이 필드 라인을 동적 테이블에 추가할 수 있는지를 나타냅니다. 'N' 비트가 설정된 경우, 인코딩된 필드 라인은 항상 리터럴 표현으로 인코딩되어야 합니다(MUST). 특히, 피어가 'N' 비트가 설정된 리터럴로 표현된 필드 라인을 수신하고 이를 전달하는 경우에는, 해당 필드 라인을 전달할 때 반드시 리터럴 표현을 사용해야 합니다(MUST). 이 비트는 압축으로 인해 노출되어서는 안 되는 필드 값을 보호하기 위한 목적으로 사용됩니다; 자세한 내용은 섹션 7.1를 참조하십시오.
네 번째 비트인 'T' 비트는 참조가 정적 테이블인지 동적 테이블인지를 나타냅니다. 뒤따르는 4비트 접두사 정수(섹션 4.1.1)는 필드 이름에 대한 테이블 항목을 찾는 데 사용됩니다. T=1이면 숫자는 정적 테이블 인덱스를 나타내고, T=0이면 숫자는 동적 테이블의 상대 인덱스입니다.
동적 테이블 항목에서 가져오는 것은 필드 이름뿐이며, 필드 값은 8비트 접두사 문자열 리터럴로 인코딩됩니다(참조 섹션 4.1.2).
4.5.5. 포스트-베이스 이름 참조가 있는 리터럴 필드 라인
이 표현은 필드 이름이 Base 값보다 크거나 같은 절대 인덱스를 가진 동적 테이블 항목의 필드 이름과 일치하는 필드 라인을 인코딩합니다.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | N |NameIdx(3+)|
+---+---+---+---+---+-----------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
그림 16: 포스트-베이스 이름 참조가 있는 리터럴 필드 라인
이 표현은 '0000' 4비트 패턴으로 시작합니다. 다섯 번째 비트는 섹션 4.5.4에서 설명한 'N' 비트입니다. 이어서 동적 테이블 항목의 포스트-베이스 인덱스(섹션 3.2.6)가 3비트 접두사 정수로 인코딩되어 옵니다(참조 섹션 4.1.1).
동적 테이블 항목에서 가져오는 것은 필드 이름뿐이며, 필드 값은 8비트 접두사 문자열 리터럴로 인코딩됩니다(참조 섹션 4.1.2).
4.5.6. 리터럴 이름이 있는 리터럴 필드 라인
이 표현은 필드 이름과 필드 값을 문자열 리터럴로 인코딩합니다.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | N | H |NameLen(3+)|
+---+---+---+---+---+-----------+
| Name String (Length bytes) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
그림 17: 리터럴 이름이 있는 리터럴 필드 라인
5. 구성
QPACK은 HTTP/3 SETTINGS 프레임을 위한 두 가지 설정을 정의합니다:
6. 오류 처리
다음 오류 코드는 QPACK의 실패로 인해 스트림 또는 연결을 계속할 수 없음을 나타내기 위해 HTTP/3에서 정의됩니다:
- QPACK_DECOMPRESSION_FAILED (0x0200):
-
디코더가 인코딩된 필드 섹션을 해석하지 못하여 해당 필드 섹션의 디코딩을 계속할 수 없습니다.
- QPACK_ENCODER_STREAM_ERROR (0x0201):
-
디코더가 인코더 스트림에서 수신된 인코더 명령을 해석하지 못했습니다.
- QPACK_DECODER_STREAM_ERROR (0x0202):
-
인코더가 디코더 스트림에서 수신된 디코더 명령을 해석하지 못했습니다.
7. 보안 고려사항
이 절에서는 QPACK과 관련된 잠재적인 보안 우려 영역을 설명합니다:
- 공유된 압축 컨텍스트에 압축된 비밀에 대한 추측을 길이 기반 오라클로 확인하는 데 압축을 악용하는 것.
- 디코더의 처리 또는 메모리 용량을 소진시켜 발생하는 서비스 거부(DoS).
7.1. 동적 테이블 상태 탐지
QPACK은 HTTP와 같은 프로토콜에 내재된 중복성을 활용하여 필드 섹션의 인코딩된 크기를 줄입니다. 궁극적인 목표는 HTTP 요청 또는 응답을 전송하는 데 필요한 데이터량을 줄이는 것입니다.
헤더 및 트레일러 필드를 인코딩하는 데 사용되는 압축 컨텍스트는, 인코딩할 필드를 정의하여 전송할 수 있고 인코딩 후 그 필드의 길이를 관찰할 수 있는 공격자에 의해 탐지될 수 있습니다. 공격자가 이 둘을 모두 할 수 있을 때, 동적 테이블 상태에 대한 추측을 확인하기 위해 요청을 적응적으로 수정할 수 있습니다. 추측이 더 짧게 압축되면 공격자는 인코딩된 길이를 관찰하여 추측이 맞았음을 유추할 수 있습니다.
이것은 전송 계층 보안 프로토콜([TLS])과 QUIC 전송 프로토콜([QUIC-TRANSPORT]) 위에서도 가능하며, TLS와 QUIC는 내용에 대한 기밀성은 제공하지만 그 길이에 대해서는 제한적인 보호만 제공하기 때문입니다.
CRIME([CRIME])와 같은 공격은 이러한 일반적인 공격자 능력의 존재를 보여주었습니다. 특정 공격은 DEFLATE([RFC1951])가 접두사 매칭에 기반한 중복을 제거한다는 사실을 악용했습니다. 이는 공격자가 한 문자씩 추측을 확인할 수 있게 하여 지수 시간 공격을 선형 시간 공격으로 줄였습니다.
7.1.1. QPACK 및 HTTP에의 적용성
QPACK은 추측이 개별 문자가 아닌 전체 필드 라인과 일치하도록 강제함으로써 CRIME 기반 공격을 완화하지만 완전히 방지하지는 못합니다. 공격자는 추측이 맞는지 여부만 알 수 있으므로 해당 필드 이름에 연관된 필드 값에 대해 무차별 대입 공격을 수행해야 합니다.
따라서 특정 필드 값을 복구할 수 있는 가능성은 값의 엔트로피에 달려 있습니다. 엔트로피가 높은 값은 성공적으로 복구될 가능성이 낮습니다. 반면 엔트로피가 낮은 값은 여전히 취약합니다.
이러한 유형의 공격은 서로 신뢰하지 않는 두 주체가 단일 HTTP/3 연결에 요청 또는 응답을 올리는 경우 언제든지 가능할 수 있습니다. 공유된 QPACK 압축기가 한 주체에게 동적 테이블에 항목을 추가할 권한을 주고 다른 주체에게 선택된 필드 라인을 인코딩할 때 해당 항목을 참조할 권한을 준다면, 공격자(두 번째 주체)는 인코딩된 출력의 길이를 관찰함으로써 테이블 상태를 알 수 있습니다.
예를 들어, 서로 신뢰하지 않는 주체의 요청 또는 응답은 중개자가 다음과 같은 경우 발생할 수 있습니다:
- 원본 서버로 향하는 단일 연결에 여러 클라이언트의 요청을 전송하거나,
- 여러 원본 서버의 응답을 수집하여 클라이언트로 향하는 공유된 연결에 배치하는 경우.
웹 브라우저는 동일한 연결에서 다른 웹 오리진([RFC6454])이 만든 요청들이 서로 신뢰하지 않는 주체에 의해 만들어질 수 있음을 가정해야 합니다. 이외에도 서로 신뢰하지 않는 주체가 관련되는 다른 시나리오가 존재할 수 있습니다.
7.1.2. 완화책
헤더 또는 트레일러 필드에 대한 기밀성이 필요한 HTTP 사용자는 추측을 불가능하게 만들 정도의 엔트로피를 가진 값을 사용할 수 있습니다. 그러나 이는 모든 HTTP 사용자가 공격을 완화하기 위한 조치를 강제하므로 일반적인 해결책으로는 비현실적입니다. 또한 HTTP 사용 방식에 대한 새로운 제약을 부과합니다.
HTTP 사용자에게 제약을 가하기보다는 QPACK 구현이 압축 적용 방식을 제약하여 동적 테이블 탐지 가능성을 제한할 수 있습니다.
이상적인 해결책은 메시지를 구성하는 주체에 따라 동적 테이블에 대한 접근을 분리하는 것입니다. 테이블에 추가된 필드 값은 특정 주체에 귀속되며, 해당 값을 생성한 주체만 그 값을 추출할 수 있어야 합니다.
이 옵션의 압축 성능을 향상시키기 위해 특정 항목은 공개(public)로 태그될 수 있습니다. 예를 들어, 웹 브라우저는 Accept-Encoding 헤더 필드의 값을 모든 요청에서 사용 가능하도록 만들 수 있습니다.
필드 값의 출처에 대한 충분한 지식이 없는 인코더는 대신 동일한 필드 이름에 대해 서로 다른 값이 많은 경우 패널티를 도입할 수 있습니다. 이 패널티는 필드 값을 추측하려는 많은 시도가 이후 메시지에서 해당 필드가 동적 테이블 항목과 비교되지 않도록 만들 수 있어 추가 추측을 사실상 방지합니다.
이 대응은 필드 값의 길이에 반비례하여 적용될 수 있습니다. 즉, 짧은 값에 대해 더 빠르게 또는 더 높은 확률로 동적 테이블 접근을 비활성화할 수 있습니다.
이 완화책은 두 엔드포인트 간에서 가장 효과적입니다. 중개자가 주어진 메시지를 생성한 주체를 알지 못한 채로 재인코딩하는 경우, 중개자는 원래 인코더가 의도적으로 분리해 둔 압축 컨텍스트를 의도치 않게 병합할 수 있습니다.
7.1.3. 절대 인덱싱되지 않는 리터럴
구현체는 민감한 필드를 압축하지 않고 대신 그 값을 리터럴로 인코딩하여 보호할 수도 있습니다.
필드 라인을 동적 테이블에 삽입하지 않기로 거부하는 것은 모든 홉에서 그렇게 했을 때만 효과적입니다. never-indexed 리터럴 비트(섹션 4.5.4 참조)는 특정 값이 의도적으로 리터럴로 전송되었음을 중개자에게 알리는 데 사용할 수 있습니다.
중개자는 'N' 비트가 설정된 리터럴 표현을 다른 표현으로 재인코딩하여 인덱싱해서는 안 됩니다(MUST NOT). QPACK을 재인코딩에 사용하는 경우, 'N' 비트가 설정된 리터럴 표현을 사용해야 합니다(MUST). HPACK을 재인코딩에 사용하는 경우에는 HPACK의 never-indexed 리터럴 표현(참조 RFC 7541 섹션 6.2.3)을 사용해야 합니다(MUST).
필드 값을 절대 인덱싱하지 않도록 표시하는 선택은 여러 요인에 따라 달라집니다. QPACK은 전체 필드 값을 추측으로부터 보호하지 못하므로, 짧거나 엔트로피가 낮은 값은 공격자에 의해 더 쉽게 복구될 수 있습니다. 따라서 인코더는 엔트로피가 낮은 값을 인덱싱하지 않기로 선택할 수 있습니다.
인코더는 Cookie 또는 Authorization 헤더와 같이 노출될 경우 가치가 높거나 복구가 민감한 필드에 대해서는 값들을 인덱싱하지 않기로 선택할 수도 있습니다.
반대로, 인코더는 노출되더라도 거의 가치가 없는 필드에 대해서는 인덱싱을 선호할 수 있습니다. 예를 들어 User-Agent 헤더 필드는 요청 간에 일반적으로 거의 변하지 않으며 어떤 서버에도 전송됩니다. 이 경우 특정 User-Agent 값이 사용되었음을 확인하는 것은 거의 가치가 없습니다.
이러한 never-indexed 리터럴 표현을 사용할지 결정하는 기준은 시간이 지나면서 새로운 공격이 발견됨에 따라 진화할 것입니다.
7.2. 정적 허프만 인코딩
현재 알려진 정적 허프만 인코딩에 대한 공격은 없습니다. 한 연구는 정적 허프만 인코딩 테이블을 사용하는 것이 정보 유출을 만들 수 있음을 보여주었지만, 동일한 연구는 공격자가 이 정보 유출을 이용해 의미 있는 정보를 회수할 수 없다고 결론지었습니다(참조 [PETAL]).
7.3. 메모리 소비
공격자는 엔드포인트가 메모리를 소진하도록 시도할 수 있습니다. QPACK은 엔드포인트가 할당하는 최대 및 안정적 메모리 양을 제한하도록 설계되었습니다.
QPACK은 동적 테이블의 최대 크기와 차단된 스트림의 최대 수의 정의를 사용하여 인코더가 디코더에게 소비시키는 메모리 양을 제한합니다. HTTP/3에서는 이 값들이 디코더가 설정하는 SETTINGS_QPACK_MAX_TABLE_CAPACITY 및 SETTINGS_QPACK_BLOCKED_STREAMS 설정 매개변수로 제어됩니다(참조 섹션 3.2.3 및 섹션 2.1.2). 동적 테이블 크기 제한은 동적 테이블에 저장된 데이터 크기와 약간의 오버헤드 허용치를 고려합니다. 차단된 스트림 수 제한은 디코더가 필요로 하는 최대 메모리 양에 대한 대리자일 뿐입니다. 실제 최대 메모리는 각 차단된 스트림을 추적하는 데 디코더가 사용하는 메모리 양에 따라 달라집니다.
디코더는 동적 테이블의 최대 크기에 적절한 값을 설정하여 상태 메모리 사용량을 제한할 수 있습니다. HTTP/3에서는 이는 SETTINGS_QPACK_MAX_TABLE_CAPACITY 매개변수를 적절히 설정함으로써 실현됩니다. 인코더는 디코더가 허용하는 것보다 더 작은 동적 테이블 크기를 선택하고 이를 디코더에 신호하여 자신의 상태 메모리 사용량을 제한할 수 있습니다(섹션 4.3.1 참조).
디코더는 차단된 스트림에 사용되는 상태 메모리 양을 최대 차단 스트림 수에 적절한 값을 설정하여 제한할 수 있습니다. HTTP/3에서는 이는 SETTINGS_QPACK_BLOCKED_STREAMS 매개변수를 적절히 설정함으로써 실현됩니다. 차단될 위험이 있는 스트림은 인코더에서 추가적인 상태 메모리를 소비하지 않습니다.
인코더는 확인되지 않은 필드 섹션에서 모든 동적 테이블 참조를 추적하기 위해 메모리를 할당합니다. 구현체는 추적하기로 원하는 동적 테이블 참조 수만 사용함으로써 상태 메모리 양을 직접 제한할 수 있습니다; 디코더에 대한 신호는 필요하지 않습니다. 그러나 동적 테이블 참조 제한은 압축 효율을 떨어뜨립니다.
인코더나 디코더가 소비하는 일시적 메모리 양은 필드 라인을 순차적으로 처리함으로써 제한할 수 있습니다. 디코더 구현체는 필드 섹션을 디코딩하는 동안 필드 라인의 전체 목록을 유지할 필요가 없습니다. 단일 패스 알고리즘을 사용하는 인코더 구현체는 필드 섹션을 인코딩하는 동안 전체 목록을 유지할 필요가 없습니다. 다만 애플리케이션의 제약으로 인해 전체 목록을 유지해야 하는 경우가 있을 수 있습니다.
동적 테이블 크기에 대한 협상된 제한은 QPACK 구현체가 소비할 수 있는 대부분의 메모리를 고려하지만, 흐름 제어로 인해 즉시 전송될 수 없는 데이터는 이 제한의 영향을 받지 않습니다. 구현체는 전송되지 않은 데이터의 크기를 제한해야 합니다. 특히 디코더 스트림에서는 보낼 내용을 선택할 유연성이 제한되므로 주의해야 합니다. 전송되지 않은 데이터가 과도할 때 가능한 대응으로는 피어가 새 스트림을 열지 못하도록 제한하거나, 인코더 스트림만 읽거나, 연결을 닫는 것이 있습니다.
7.4. 구현 한계
QPACK 구현체는 정수에 대해 큰 값, 정수 인코딩의 긴 길이, 또는 긴 문자열 리터럴이 보안 약점을 만들지 않도록 보장해야 합니다.
구현체는 허용하는 정수 값과 인코딩된 길이에 대한 제한을 설정해야 하며(섹션 4.1.1 참조), 문자열 리터럴에 대해 허용하는 길이에도 제한을 설정해야 합니다(섹션 4.1.2 참조). 이러한 제한은 HTTP 구현체가 수용하도록 구성될 수 있는 가장 큰 개별 필드를 처리할 수 있을 만큼 충분히 커야 합니다(SHOULD).
구현체가 디코드할 수 있는 것보다 큰 값을 만나면, 이는 요청 스트림 상에서는 QPACK_DECOMPRESSION_FAILED 유형의 스트림 오류로 처리되어야 하며, 인코더 또는 디코더 스트림 상에서는 적절한 유형의 연결 오류로 처리되어야 합니다(MUST).
8. IANA 고려사항
이 문서는 [HTTP/3]에서 정의된 레지스트리에 여러 등록을 수행합니다. 이 문서에 의해 생성된 할당은 모두 영구 상태로 지정되며 변경 관리는 IETF가 수행하고 연락처는 HTTP 작업 그룹(ietf-http-wg@w3.org)으로 지정됩니다.
8.1. 설정 등록
이 문서는 두 가지 설정을 지정합니다. 다음 표의 항목들은 [HTTP/3]에 생성된 "HTTP/3 Settings" 레지스트리에 등록됩니다.
| Setting Name | Code | Specification | Default |
|---|---|---|---|
| QPACK_MAX_TABLE_CAPACITY | 0x01 | 섹션 5 | 0 |
| QPACK_BLOCKED_STREAMS | 0x07 | 섹션 5 | 0 |
서식상의 이유로, 여기의 설정 이름은 'SETTINGS_' 접두사를 제거하여 축약되어 있습니다.
8.2. 스트림 유형 등록
이 문서는 두 가지 스트림 유형을 지정합니다. 다음 표의 항목들은 [HTTP/3]에 생성된 "HTTP/3 Stream Types" 레지스트리에 등록됩니다.
8.3. 오류 코드 등록
이 문서는 세 가지 오류 코드를 지정합니다. 다음 표의 항목들은 [HTTP/3]에 생성된 "HTTP/3 Error Codes" 레지스트리에 등록됩니다.
9. 참조
9.1. 규범 참조
- [HTTP]
- Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, Ed., “HTTP Semantics”, STD 97, RFC 9110, DOI 10.17487/RFC9110, 2022년 6월, <https://www.rfc-editor.org/info/rfc9110>.
- [HTTP/3]
- Bishop, M., Ed., “HTTP/3”, RFC 9114, DOI 10.17487/RFC9114, 2022년 6월, <https://www.rfc-editor.org/info/rfc9114>.
- [QUIC-TRANSPORT]
- Iyengar, J., Ed. and M. Thomson, Ed., “QUIC: A UDP-Based Multiplexed and Secure Transport”, RFC 9000, DOI 10.17487/RFC9000, 2021년 5월, <https://www.rfc-editor.org/info/rfc9000>.
- [RFC2119]
- Bradner, S., “Key words for use in RFCs to Indicate Requirement Levels”, BCP 14, RFC 2119, DOI 10.17487/RFC2119, 1997년 3월, <https://www.rfc-editor.org/info/rfc2119>.
- [RFC2360]
- Scott, G., “Guide for Internet Standards Writers”, BCP 22, RFC 2360, DOI 10.17487/RFC2360, 1998년 6월, <https://www.rfc-editor.org/info/rfc2360>.
- [RFC7541]
- Peon, R. and H. Ruellan, “HPACK: Header Compression for HTTP/2”, RFC 7541, DOI 10.17487/RFC7541, 2015년 5월, <https://www.rfc-editor.org/info/rfc7541>.
- [RFC8174]
- Leiba, B., “Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words”, BCP 14, RFC 8174, DOI 10.17487/RFC8174, 2017년 5월, <https://www.rfc-editor.org/info/rfc8174>.
9.2. 참고 참조
- [CRIME]
- Wikipedia, “CRIME”, 2015년 5월, <http://en.wikipedia.org/w/index.php?title=CRIME&oldid=660948120>.
- [HTTP/2]
- Thomson, M., Ed. and C. Benfield, Ed., “HTTP/2”, RFC 9113, DOI 10.17487/RFC9113, 2022년 6월, <https://www.rfc-editor.org/info/rfc9113>.
- [PETAL]
- Tan, J. and J. Nahata, “PETAL: Preset Encoding Table Information Leakage”, 2013년 4월, <http://www.pdl.cmu.edu/PDL-FTP/associated/CMU-PDL-13-106.pdf>.
- [RFC1951]
- Deutsch, P., “DEFLATE Compressed Data Format Specification version 1.3”, RFC 1951, DOI 10.17487/RFC1951, 1996년 5월, <https://www.rfc-editor.org/info/rfc1951>.
- [RFC6454]
- Barth, A., “The Web Origin Concept”, RFC 6454, DOI 10.17487/RFC6454, 2011년 12월, <https://www.rfc-editor.org/info/rfc6454>.
- [TLS]
- Rescorla, E., “The Transport Layer Security (TLS) Protocol Version 1.3”, RFC 8446, DOI 10.17487/RFC8446, 2018년 8월, <https://www.rfc-editor.org/info/rfc8446>.
Appendix A. 정적 테이블
이 표는 2018년의 실제 인터넷 트래픽을 분석하여 가장 흔한 헤더 필드를 포함하도록 생성되었으며, 일부 지원되지 않거나 비표준 값을 필터링한 후에 구성되었습니다. 이러한 방법론 때문에 일부 항목은 일관성이 없거나 비슷하지만 동일하지 않은 값으로 여러 번 나타날 수 있습니다. 항목의 순서는 가장 일반적인 헤더 필드를 가장 적은 바이트로 인코딩하도록 최적화되어 있습니다.
| 인덱스 | 이름 | 값 |
|---|---|---|
| 0 | :authority | |
| 1 | :path | / |
| 2 | age | 0 |
| 3 | content-disposition | |
| 4 | content-length | 0 |
| 5 | cookie | |
| 6 | date | |
| 7 | etag | |
| 8 | if-modified-since | |
| 9 | if-none-match | |
| 10 | last-modified | |
| 11 | link | |
| 12 | location | |
| 13 | referer | |
| 14 | set-cookie | |
| 15 | :method | CONNECT |
| 16 | :method | DELETE |
| 17 | :method | GET |
| 18 | :method | HEAD |
| 19 | :method | OPTIONS |
| 20 | :method | POST |
| 21 | :method | PUT |
| 22 | :scheme | http |
| 23 | :scheme | https |
| 24 | :status | 103 |
| 25 | :status | 200 |
| 26 | :status | 304 |
| 27 | :status | 404 |
| 28 | :status | 503 |
| 29 | accept | */* |
| 30 | accept | application/dns-message |
| 31 | accept-encoding | gzip, deflate, br |
| 32 | accept-ranges | bytes |
| 33 | access-control-allow-headers | cache-control |
| 34 | access-control-allow-headers | content-type |
| 35 | access-control-allow-origin | * |
| 36 | cache-control | max-age=0 |
| 37 | cache-control | max-age=2592000 |
| 38 | cache-control | max-age=604800 |
| 39 | cache-control | no-cache |
| 40 | cache-control | no-store |
| 41 | cache-control | public, max-age=31536000 |
| 42 | content-encoding | br |
| 43 | content-encoding | gzip |
| 44 | content-type | application/dns-message |
| 45 | content-type | application/javascript |
| 46 | content-type | application/json |
| 47 | content-type | application/x-www-form-urlencoded |
| 48 | content-type | image/gif |
| 49 | content-type | image/jpeg |
| 50 | content-type | image/png |
| 51 | content-type | text/css |
| 52 | content-type | text/html; charset=utf-8 |
| 53 | content-type | text/plain |
| 54 | content-type | text/plain;charset=utf-8 |
| 55 | range | bytes=0- |
| 56 | strict-transport-security | max-age=31536000 |
| 57 | strict-transport-security | max-age=31536000; includesubdomains |
| 58 | strict-transport-security | max-age=31536000; includesubdomains; preload |
| 59 | vary | accept-encoding |
| 60 | vary | origin |
| 61 | x-content-type-options | nosniff |
| 62 | x-xss-protection | 1; mode=block |
| 63 | :status | 100 |
| 64 | :status | 204 |
| 65 | :status | 206 |
| 66 | :status | 302 |
| 67 | :status | 400 |
| 68 | :status | 403 |
| 69 | :status | 421 |
| 70 | :status | 425 |
| 71 | :status | 500 |
| 72 | accept-language | |
| 73 | access-control-allow-credentials | FALSE |
| 74 | access-control-allow-credentials | TRUE |
| 75 | access-control-allow-headers | * |
| 76 | access-control-allow-methods | get |
| 77 | access-control-allow-methods | get, post, options |
| 78 | access-control-allow-methods | options |
| 79 | access-control-expose-headers | content-length |
| 80 | access-control-request-headers | content-type |
| 81 | access-control-request-method | get |
| 82 | access-control-request-method | post |
| 83 | alt-svc | clear |
| 84 | authorization | |
| 85 | content-security-policy | script-src 'none'; object-src 'none'; base-uri 'none' |
| 86 | early-data | 1 |
| 87 | expect-ct | |
| 88 | forwarded | |
| 89 | if-range | |
| 90 | origin | |
| 91 | purpose | prefetch |
| 92 | server | |
| 93 | timing-allow-origin | * |
| 94 | upgrade-insecure-requests | 1 |
| 95 | user-agent | |
| 96 | x-forwarded-for | |
| 97 | x-frame-options | deny |
| 98 | x-frame-options | sameorigin |
필드 이름이나 값 안에 나타나는 모든 줄 바꿈은 서식상의 이유입니다.
부록 B. 인코딩 및 디코딩 예제
다음 예제들은 인코더와 디코더 간의 일련의 교환을 나타냅니다. 이 교환들은 대부분의 QPACK 명령을 실행하도록 설계되었으며, 일반적인 패턴과 동적 테이블 상태에 미치는 영향을 강조합니다. 인코더는 각각 하나의 필드 라인을 포함하는 세 개의 인코딩된 필드 섹션과 참조되지 않는 두 개의 추정적 삽입을 전송합니다.
인코더의 동적 테이블 상태와 현재 크기가 함께 표시됩니다. 각 항목은 항목의 절대 인덱스(Abs), 그 항목을 참조하는 미해결 인코딩된 필드 섹션의 현재 수(Ref), 그리고 이름과 값으로 표시됩니다. 'acknowledged' 선 위의 항목들은 디코더에 의해 확인(ack)되었습니다.
B.1. 이름 참조가 있는 리터럴 필드 라인
인코더는 정적 이름 참조를 사용한 필드의 리터럴 표현을 포함하는 인코딩된 필드 섹션을 전송합니다.
Data | Interpretation
| Encoder's Dynamic Table
Stream: 0
0000 | Required Insert Count = 0, Base = 0
510b 2f69 6e64 6578 | Literal Field Line with Name Reference
2e68 746d 6c | Static Table, Index=1
| (:path=/index.html)
Abs Ref Name Value
^-- acknowledged --^
Size=0
B.2. 동적 테이블
인코더는 동적 테이블 용량을 설정하고, 동적 이름 참조로 헤더를 삽입한 다음 이 새 항목을 참조하는 잠재적으로 블로킹될 수 있는 인코딩된 필드 섹션을 전송합니다. 디코더는 인코딩된 필드 섹션의 처리를 확인(ack)하며, 이는 Required Insert Count까지의 모든 동적 테이블 삽입을 암시적으로 확인합니다.
Stream: Encoder
3fbd01 | Set Dynamic Table Capacity=220
c00f 7777 772e 6578 | Insert With Name Reference
616d 706c 652e 636f | Static Table, Index=0
6d | (:authority=www.example.com)
c10c 2f73 616d 706c | Insert With Name Reference
652f 7061 7468 | Static Table, Index=1
| (:path=/sample/path)
Abs Ref Name Value
^-- acknowledged --^
0 0 :authority www.example.com
1 0 :path /sample/path
Size=106
Stream: 4
0381 | Required Insert Count = 2, Base = 0
10 | Indexed Field Line With Post-Base Index
| Absolute Index = Base(0) + Index(0) = 0
| (:authority=www.example.com)
11 | Indexed Field Line With Post-Base Index
| Absolute Index = Base(0) + Index(1) = 1
| (:path=/sample/path)
Abs Ref Name Value
^-- acknowledged --^
0 1 :authority www.example.com
1 1 :path /sample/path
Size=106
Stream: Decoder
84 | Section Acknowledgment (stream=4)
Abs Ref Name Value
0 0 :authority www.example.com
1 0 :path /sample/path
^-- acknowledged --^
Size=106
B.3. 추정적 삽입
인코더는 리터럴 이름으로 헤더를 동적 테이블에 삽입합니다. 디코더는 해당 항목의 수신을 확인(ack)합니다. 인코더는 어떤 인코딩된 필드 섹션도 전송하지 않습니다.
Stream: Encoder
4a63 7573 746f 6d2d | Insert With Literal Name
6b65 790c 6375 7374 | (custom-key=custom-value)
6f6d 2d76 616c 7565 |
Abs Ref Name Value
0 0 :authority www.example.com
1 0 :path /sample/path
^-- acknowledged --^
2 0 custom-key custom-value
Size=160
Stream: Decoder
01 | Insert Count Increment (1)
Abs Ref Name Value
0 0 :authority www.example.com
1 0 :path /sample/path
2 0 custom-key custom-value
^-- acknowledged --^
Size=160
B.4. 중복 명령, 스트림 취소
인코더는 동적 테이블의 기존 항목을 복제한 다음 복제된 항목을 포함하여 동적 테이블 항목을 참조하는 인코딩된 필드 섹션을 전송합니다. 인코더 스트림 데이터를 포함하는 패킷이 지연됩니다. 패킷이 도착하기 전에 디코더는 스트림을 취소하고 해당 인코딩된 필드 섹션이 처리되지 않았음을 인코더에 알립니다.
Stream: Encoder
02 | Duplicate (Relative Index = 2)
| Absolute Index =
| Insert Count(3) - Index(2) - 1 = 0
Abs Ref Name Value
0 0 :authority www.example.com
1 0 :path /sample/path
2 0 custom-key custom-value
^-- acknowledged --^
3 0 :authority www.example.com
Size=217
Stream: 8
0500 | Required Insert Count = 4, Base = 4
80 | Indexed Field Line, Dynamic Table
| Absolute Index = Base(4) - Index(0) - 1 = 3
| (:authority=www.example.com)
c1 | Indexed Field Line, Static Table Index = 1
| (:path=/)
81 | Indexed Field Line, Dynamic Table
| Absolute Index = Base(4) - Index(1) - 1 = 2
| (custom-key=custom-value)
Abs Ref Name Value
0 0 :authority www.example.com
1 0 :path /sample/path
2 1 custom-key custom-value
^-- acknowledged --^
3 1 :authority www.example.com
Size=217
Stream: Decoder
48 | Stream Cancellation (Stream=8)
Abs Ref Name Value
0 0 :authority www.example.com
1 0 :path /sample/path
2 0 custom-key custom-value
^-- acknowledged --^
3 0 :authority www.example.com
Size=217
B.5. 동적 테이블 삽입, 축출
인코더는 동적 테이블에 또 다른 헤더를 삽입하며, 이는 가장 오래된 항목을 축출합니다. 인코더는 어떤 인코딩된 필드 섹션도 전송하지 않습니다.
Stream: Encoder
810d 6375 7374 6f6d | Insert With Name Reference
2d76 616c 7565 32 | Dynamic Table, Relative Index = 1
| Absolute Index =
| Insert Count(4) - Index(1) - 1 = 2
| (custom-key=custom-value2)
Abs Ref Name Value
1 0 :path /sample/path
2 0 custom-key custom-value
^-- acknowledged --^
3 0 :authority www.example.com
4 0 custom-key custom-value2
Size=215
부록 C. 단일 패스 인코딩 샘플 알고리즘
중복 처리, 비차단 모드, 사용 가능한 인코더 스트림 흐름 제어 및 참조 추적 처리를 제외한 단일 패스 인코딩의 의사코드입니다.
# Helper functions:
# ====
# Encode an integer with the specified prefix and length
encodeInteger(buffer, prefix, value, prefixLength)
# Encode a dynamic table insert instruction with optional static
# or dynamic name index (but not both)
encodeInsert(buffer, staticNameIndex, dynamicNameIndex, fieldLine)
# Encode a static index reference
encodeStaticIndexReference(buffer, staticIndex)
# Encode a dynamic index reference relative to Base
encodeDynamicIndexReference(buffer, dynamicIndex, base)
# Encode a literal with an optional static name index
encodeLiteral(buffer, staticNameIndex, fieldLine)
# Encode a literal with a dynamic name index relative to Base
encodeDynamicLiteral(buffer, dynamicNameIndex, base, fieldLine)
# Encoding Algorithm
# ====
base = dynamicTable.getInsertCount()
requiredInsertCount = 0
for line in fieldLines:
staticIndex = staticTable.findIndex(line)
if staticIndex is not None:
encodeStaticIndexReference(streamBuffer, staticIndex)
continue
dynamicIndex = dynamicTable.findIndex(line)
if dynamicIndex is None:
# No matching entry. Either insert+index or encode literal
staticNameIndex = staticTable.findName(line.name)
if staticNameIndex is None:
dynamicNameIndex = dynamicTable.findName(line.name)
if shouldIndex(line) and dynamicTable.canIndex(line):
encodeInsert(encoderBuffer, staticNameIndex,
dynamicNameIndex, line)
dynamicIndex = dynamicTable.add(line)
if dynamicIndex is None:
# Could not index it, literal
if dynamicNameIndex is not None:
# Encode literal with dynamic name, possibly above Base
encodeDynamicLiteral(streamBuffer, dynamicNameIndex,
base, line)
requiredInsertCount = max(requiredInsertCount,
dynamicNameIndex)
else:
# Encodes a literal with a static name or literal name
encodeLiteral(streamBuffer, staticNameIndex, line)
else:
# Dynamic index reference
assert(dynamicIndex is not None)
requiredInsertCount = max(requiredInsertCount, dynamicIndex)
# Encode dynamicIndex, possibly above Base
encodeDynamicIndexReference(streamBuffer, dynamicIndex, base)
# encode the prefix
if requiredInsertCount == 0:
encodeInteger(prefixBuffer, 0x00, 0, 8)
encodeInteger(prefixBuffer, 0x00, 0, 7)
else:
wireRIC = (
requiredInsertCount
% (2 * getMaxEntries(maxTableCapacity))
) + 1;
encodeInteger(prefixBuffer, 0x00, wireRIC, 8)
if base >= requiredInsertCount:
encodeInteger(prefixBuffer, 0x00,
base - requiredInsertCount, 7)
else:
encodeInteger(prefixBuffer, 0x80,
requiredInsertCount - base - 1, 7)
return encoderBuffer, prefixBuffer + streamBuffer
감사의 글
IETF QUIC 작업 그룹은 많은 사람들로부터 엄청난 지원을 받았습니다.
압축 설계 팀은 문제 영역을 탐구하고 이 문서의 초기 초안 버전에 영향을 준 상당한 작업을 수행했습니다. 설계 팀 구성원 Roberto Peon, Martin Thomson 및 Dmitri Tikhonov의 기여에 감사드립니다.
다음 사람들도 이 문서에 상당한 기여를 했습니다:
-
Bence Beky
-
Alessandro Ghedini
-
Ryan Hamilton
-
Robin Marx
-
Patrick McManus
-
奥 一穂 (Kazuho Oku)
-
Lucas Pardue
-
Biren Roy
-
Ian Swett
이 문서는 [RFC7541]의 본문에 크게 의존합니다. 해당 저자들의 간접적인 입력에도 감사드립니다.
Buck Krasic의 기여는 그가 Google에 재직하는 동안 Google의 지원을 받았습니다.
Mike Bishop의 일부 기여는 그가 Microsoft에 재직하는 동안 지원을 받았습니다.