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