varint_test.mx raw
1 package marmot
2
3 // QUIC varint codec tests (RFC 9000 ยง16, Appendix A).
4 //
5 // The critical test in this file is TestDecodeVarint8ByteAbove2p31 - an
6 // 8-byte-encoded value above 2^31 must survive decoding as uint64 without
7 // silent truncation. This would regress if anyone typed the API as uint
8 // or int, since Moxie's uint/int are 32-bit.
9
10 import "testing"
11
12 // roundTrip encodes v, decodes it back, and checks size class + value.
13 func roundTrip(t *testing.T, v uint64, wantN int32) {
14 var buf [8]byte
15 n, err := EncodeVarint(v, buf[:])
16 if err != nil {
17 t.Fatalf("EncodeVarint(%d): %s", int64(v), err.Error())
18 return
19 }
20 if n != wantN {
21 t.Errorf("EncodeVarint(%d): wrote %d bytes, want %d", int64(v), n, wantN)
22 }
23 got, gotN, err := DecodeVarint(buf[:n])
24 if err != nil {
25 t.Fatalf("DecodeVarint(%d bytes): %s", n, err.Error())
26 return
27 }
28 if gotN != n {
29 t.Errorf("DecodeVarint(%d): consumed %d bytes, want %d", int64(v), gotN, n)
30 }
31 if got != v {
32 t.Errorf("DecodeVarint round-trip: got %d, want %d", int64(got), int64(v))
33 }
34 }
35
36 func TestVarintSizeClasses(t *testing.T) {
37 // 1-byte class: 0..63
38 roundTrip(t, 0, 1)
39 roundTrip(t, 63, 1)
40 // 2-byte class: 64..16383
41 roundTrip(t, 64, 2)
42 roundTrip(t, 16383, 2)
43 // 4-byte class: 16384..1073741823 (2^30-1)
44 roundTrip(t, 16384, 4)
45 roundTrip(t, 1073741823, 4)
46 // 8-byte class: 1073741824..VarintMax
47 roundTrip(t, 1073741824, 8)
48 }
49
50 // TestDecodeVarint8ByteAbove2p31 is the regression guard for the whole
51 // uint64-in-the-API rule. An 8-byte varint encoding a value > 2^31 must
52 // decode to the correct uint64. If someone refactored this to return uint
53 // (32-bit in Moxie), the value would silently truncate to its low 32 bits
54 // and every caller would get wrong data with no error.
55 func TestDecodeVarint8ByteAbove2p31(t *testing.T) {
56 // 2^31 exactly (first value that overflows signed int32).
57 v := uint64(0x80000000)
58 roundTrip(t, v, 8)
59
60 // 2^31 + 1 (first value that overflows unsigned int32 if signed is used).
61 roundTrip(t, uint64(0x80000001), 8)
62
63 // 2^32 (first value above uint32 range - critical test).
64 roundTrip(t, uint64(0x100000000), 8)
65
66 // 2^40 - solidly in the upper range.
67 roundTrip(t, uint64(0x10000000000), 8)
68
69 // VarintMax (2^62-1) - largest legal QUIC varint.
70 roundTrip(t, VarintMax, 8)
71 }
72
73 func TestEncodeVarintOverflow(t *testing.T) {
74 // Values above VarintMax must be rejected.
75 var buf [8]byte
76 _, err := EncodeVarint(VarintMax+1, buf[:])
77 if err == nil {
78 t.Error("EncodeVarint(VarintMax+1) should error")
79 }
80 _, err = EncodeVarint(uint64(0xffffffffffffffff), buf[:])
81 if err == nil {
82 t.Error("EncodeVarint(max uint64) should error")
83 }
84 }
85
86 func TestEncodeVarintBufferTooSmall(t *testing.T) {
87 // 8-byte value into 4-byte buffer.
88 var small [4]byte
89 _, err := EncodeVarint(uint64(0x100000000), small[:])
90 if err == nil {
91 t.Error("EncodeVarint into small buffer should error")
92 }
93 // 4-byte value into 2-byte buffer.
94 _, err = EncodeVarint(16384, small[:1])
95 if err == nil {
96 t.Error("EncodeVarint into 1-byte buffer should error")
97 }
98 }
99
100 func TestDecodeVarintTruncated(t *testing.T) {
101 // Advertise 8-byte class but supply 3 bytes.
102 _, _, err := DecodeVarint([]byte{0xc0, 0x00, 0x00})
103 if err == nil {
104 t.Error("DecodeVarint(truncated 8-byte) should error")
105 }
106 // Advertise 4-byte class but supply 2 bytes.
107 _, _, err = DecodeVarint([]byte{0x80, 0x00})
108 if err == nil {
109 t.Error("DecodeVarint(truncated 4-byte) should error")
110 }
111 // Advertise 2-byte class but supply 1 byte.
112 _, _, err = DecodeVarint([]byte{0x40})
113 if err == nil {
114 t.Error("DecodeVarint(truncated 2-byte) should error")
115 }
116 // Empty.
117 _, _, err = DecodeVarint(nil)
118 if err == nil {
119 t.Error("DecodeVarint(nil) should error")
120 }
121 }
122
123 // readQuicVec narrows uint64 to int at the call site. A varint length above
124 // int32 range must be rejected with an error, not silently truncated.
125 func TestReadQuicVecRejectsOversizedLength(t *testing.T) {
126 // Build a QUIC varint encoding 2^32 (well above int32 max).
127 var buf [8]byte
128 n, _ := EncodeVarint(uint64(0x100000000), buf[:])
129 _, _, err := readQuicVec(buf[:n])
130 if err == nil {
131 t.Error("readQuicVec(length=2^32) should error - cannot fit in int32")
132 }
133 }
134