Source file src/net/http/internal/http3/qpack_decode_test.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package http3
     6  
     7  import (
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  )
    12  
    13  func TestQPACKDecode(t *testing.T) {
    14  	type header struct {
    15  		itype       indexType
    16  		name, value string
    17  	}
    18  	// Many test cases here taken from Google QUICHE,
    19  	// quiche/quic/core/qpack/qpack_encoder_test.cc.
    20  	for _, test := range []struct {
    21  		name string
    22  		enc  []byte
    23  		want []header
    24  	}{{
    25  		name: "empty",
    26  		enc:  unhex("0000"),
    27  		want: []header{},
    28  	}, {
    29  		name: "literal entry empty value",
    30  		enc:  unhex("000023666f6f00"),
    31  		want: []header{
    32  			{mayIndex, "foo", ""},
    33  		},
    34  	}, {
    35  		name: "simple literal entry",
    36  		enc:  unhex("000023666f6f03626172"),
    37  		want: []header{
    38  			{mayIndex, "foo", "bar"},
    39  		},
    40  	}, {
    41  		name: "multiple literal entries",
    42  		enc: unhex("0000" + // prefix
    43  			// foo: bar
    44  			"23666f6f03626172" +
    45  			// 7 octet long header name, the smallest number
    46  			// that does not fit on a 3-bit prefix.
    47  			"2700666f6f62616172" +
    48  			// 127 octet long header value, the smallest number
    49  			// that does not fit on a 7-bit prefix.
    50  			"7f00616161616161616161616161616161616161616161616161616161616161616161" +
    51  			"6161616161616161616161616161616161616161616161616161616161616161616161" +
    52  			"6161616161616161616161616161616161616161616161616161616161616161616161" +
    53  			"616161616161616161616161616161616161616161616161",
    54  		),
    55  		want: []header{
    56  			{mayIndex, "foo", "bar"},
    57  			{mayIndex, "foobaar", strings.Repeat("a", 127)},
    58  		},
    59  	}, {
    60  		name: "line feed in value",
    61  		enc:  unhex("000023666f6f0462610a72"),
    62  		want: []header{
    63  			{mayIndex, "foo", "ba\nr"},
    64  		},
    65  	}, {
    66  		name: "huffman simple",
    67  		enc:  unhex("00002f0125a849e95ba97d7f8925a849e95bb8e8b4bf"),
    68  		want: []header{
    69  			{mayIndex, "custom-key", "custom-value"},
    70  		},
    71  	}, {
    72  		name: "alternating huffman nonhuffman",
    73  		enc: unhex("0000" + // Prefix.
    74  			"2f0125a849e95ba97d7f" + // Huffman-encoded name.
    75  			"8925a849e95bb8e8b4bf" + // Huffman-encoded value.
    76  			"2703637573746f6d2d6b6579" + // Non-Huffman encoded name.
    77  			"0c637573746f6d2d76616c7565" + // Non-Huffman encoded value.
    78  			"2f0125a849e95ba97d7f" + // Huffman-encoded name.
    79  			"0c637573746f6d2d76616c7565" + // Non-Huffman encoded value.
    80  			"2703637573746f6d2d6b6579" + // Non-Huffman encoded name.
    81  			"8925a849e95bb8e8b4bf", // Huffman-encoded value.
    82  		),
    83  		want: []header{
    84  			{mayIndex, "custom-key", "custom-value"},
    85  			{mayIndex, "custom-key", "custom-value"},
    86  			{mayIndex, "custom-key", "custom-value"},
    87  			{mayIndex, "custom-key", "custom-value"},
    88  		},
    89  	}, {
    90  		name: "static table",
    91  		enc:  unhex("0000d1d45f00055452414345dfcc5f108621e9aec2a11f5c8294e75f1000"),
    92  		want: []header{
    93  			{mayIndex, ":method", "GET"},
    94  			{mayIndex, ":method", "POST"},
    95  			{mayIndex, ":method", "TRACE"},
    96  			{mayIndex, "accept-encoding", "gzip, deflate, br"},
    97  			{mayIndex, "location", ""},
    98  			{mayIndex, "accept-encoding", "compress"},
    99  			{mayIndex, "location", "foo"},
   100  			{mayIndex, "accept-encoding", ""},
   101  		},
   102  	}} {
   103  		synctestSubtest(t, test.name, func(t *testing.T) {
   104  			st1, st2 := newStreamPair(t)
   105  			st1.Write(test.enc)
   106  			st1.Flush()
   107  
   108  			st2.lim = int64(len(test.enc))
   109  
   110  			var dec qpackDecoder
   111  			got := []header{}
   112  			err := dec.decode(st2, func(itype indexType, name, value string) error {
   113  				got = append(got, header{itype, name, value})
   114  				return nil
   115  			})
   116  			if err != nil {
   117  				t.Fatalf("decode: %v", err)
   118  			}
   119  			if !reflect.DeepEqual(got, test.want) {
   120  				t.Errorf("encoded: %x", test.enc)
   121  				t.Errorf("got headers:")
   122  				for _, h := range got {
   123  					t.Errorf("  %v: %q", h.name, h.value)
   124  				}
   125  				t.Errorf("want headers:")
   126  				for _, h := range test.want {
   127  					t.Errorf("  %v: %q", h.name, h.value)
   128  				}
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  func TestQPACKDecodeErrors(t *testing.T) {
   135  	// Many test cases here taken from Google QUICHE,
   136  	// quiche/quic/core/qpack/qpack_encoder_test.cc.
   137  	for _, test := range []struct {
   138  		name string
   139  		enc  []byte
   140  	}{{
   141  		name: "literal entry empty name",
   142  		enc:  unhex("00002003666f6f"),
   143  	}, {
   144  		name: "literal entry empty name and value",
   145  		enc:  unhex("00002000"),
   146  	}, {
   147  		name: "name length too large for varint",
   148  		enc:  unhex("000027ffffffffffffffffffff"),
   149  	}, {
   150  		name: "string literal too long",
   151  		enc:  unhex("000027ffff7f"),
   152  	}, {
   153  		name: "value length too large for varint",
   154  		enc:  unhex("000023666f6f7fffffffffffffffffffff"),
   155  	}, {
   156  		name: "value length too long",
   157  		enc:  unhex("000023666f6f7fffff7f"),
   158  	}, {
   159  		name: "incomplete header block",
   160  		enc:  unhex("00002366"),
   161  	}, {
   162  		name: "huffman name does not have eos prefix",
   163  		enc:  unhex("00002f0125a849e95ba97d7e8925a849e95bb8e8b4bf"),
   164  	}, {
   165  		name: "huffman value does not have eos prefix",
   166  		enc:  unhex("00002f0125a849e95ba97d7f8925a849e95bb8e8b4be"),
   167  	}, {
   168  		name: "huffman name eos prefix too long",
   169  		enc:  unhex("00002f0225a849e95ba97d7fff8925a849e95bb8e8b4bf"),
   170  	}, {
   171  		name: "huffman value eos prefix too long",
   172  		enc:  unhex("00002f0125a849e95ba97d7f8a25a849e95bb8e8b4bfff"),
   173  	}, {
   174  		name: "too high static table index",
   175  		enc:  unhex("0000ff23ff24"),
   176  	}, {
   177  		name: "prefixed string length overflow",
   178  		enc:  unhex("000027ffffffffffffffff7f"),
   179  	}, {
   180  		name: "prefixed static index overflow",
   181  		enc:  unhex("0000ffffffffffffffffff7f"),
   182  	}} {
   183  		synctestSubtest(t, test.name, func(t *testing.T) {
   184  			st1, st2 := newStreamPair(t)
   185  			st1.Write(test.enc)
   186  			st1.Flush()
   187  
   188  			st2.lim = int64(len(test.enc))
   189  
   190  			var dec qpackDecoder
   191  			err := dec.decode(st2, func(itype indexType, name, value string) error {
   192  				return nil
   193  			})
   194  			if err == nil {
   195  				t.Errorf("encoded: %x", test.enc)
   196  				t.Fatalf("decode succeeded; want error")
   197  			}
   198  		})
   199  	}
   200  }
   201  

View as plain text