Browse Source

hpack varint decoding

Brad Fitzpatrick 11 years ago
parent
commit
3b07fb76f9
2 changed files with 105 additions and 1 deletions
  1. 51 1
      hpack/hpack.go
  2. 54 0
      hpack/hpack_test.go

+ 51 - 1
hpack/hpack.go

@@ -9,7 +9,10 @@
 // See http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09
 package hpack
 
-import "fmt"
+import (
+	"errors"
+	"fmt"
+)
 
 // A DecodingError is something the spec defines as a decoding error.
 type DecodingError struct {
@@ -167,3 +170,50 @@ func (d *Decoder) Decode(p []byte) ([]HeaderField, error) {
 	}
 	return hf, nil
 }
+
+var errVarintOverflow = DecodingError{errors.New("varint integer overflow")}
+
+// readVarInt reads an unsigned variable length integer off the
+// beginning of p. n is the parameter as described in
+// http://http2.github.io/http2-spec/compression.html#rfc.section.5.1.
+//
+// n must always be between 1 and 8.
+//
+// The returned consumed parameter is the number of bytes that were
+// consumed from the beginning of p. It is zero if the end of the
+// integer's representation wasn't included in p. (In this case,
+// callers should wait for more data to arrive and try again with a
+// larger p buffer).
+func readVarInt(n byte, p []byte) (i uint64, consumed int, err error) {
+	if n < 1 || n > 8 {
+		panic("bad n")
+	}
+	if len(p) == 0 {
+		return
+	}
+	i = uint64(p[0])
+	if n < 8 {
+		i &= (1 << uint64(n)) - 1
+	}
+	if i < (1<<uint64(n))-1 {
+		return i, 1, nil
+	}
+
+	p = p[1:]
+	consumed++
+	var m uint64
+	for len(p) > 0 {
+		b := p[0]
+		consumed++
+		i += uint64(b&127) << m
+		if b&128 == 0 {
+			return
+		}
+		p = p[1:]
+		m += 7
+		if m >= 63 { // TODO: proper overflow check. making this up.
+			return 0, 0, errVarintOverflow
+		}
+	}
+	return 0, 0, nil
+}

+ 54 - 0
hpack/hpack_test.go

@@ -216,3 +216,57 @@ func TestHuffmanDecode(t *testing.T) {
 		}
 	}
 }
+
+func TestReadVarInt(t *testing.T) {
+	type res struct {
+		i        uint64
+		consumed int
+		err      error
+	}
+	tests := []struct {
+		n    byte
+		p    []byte
+		want res
+	}{
+		// Fits in a byte:
+		{1, []byte{0}, res{0, 1, nil}},
+		{2, []byte{2}, res{2, 1, nil}},
+		{3, []byte{6}, res{6, 1, nil}},
+		{4, []byte{14}, res{14, 1, nil}},
+		{5, []byte{30}, res{30, 1, nil}},
+		{6, []byte{62}, res{62, 1, nil}},
+		{7, []byte{126}, res{126, 1, nil}},
+		{8, []byte{254}, res{254, 1, nil}},
+
+		// Doesn't fit in a byte:
+		{1, []byte{1}, res{0, 0, nil}},
+		{2, []byte{3}, res{0, 0, nil}},
+		{3, []byte{7}, res{0, 0, nil}},
+		{4, []byte{15}, res{0, 0, nil}},
+		{5, []byte{31}, res{0, 0, nil}},
+		{6, []byte{63}, res{0, 0, nil}},
+		{7, []byte{127}, res{0, 0, nil}},
+		{8, []byte{255}, res{0, 0, nil}},
+
+		// Ignoring top bits:
+		{5, []byte{255, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 111
+		{5, []byte{159, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 100
+		{5, []byte{191, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 101
+
+		// Extra byte:
+		{5, []byte{191, 154, 10, 2}, res{1337, 3, nil}}, // extra byte
+
+		// Short a byte:
+		{5, []byte{191, 154}, res{0, 0, nil}},
+
+		// integer overflow:
+		{1, []byte{255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, res{0, 0, errVarintOverflow}},
+	}
+	for _, tt := range tests {
+		i, consumed, err := readVarInt(tt.n, tt.p)
+		got := res{i, consumed, err}
+		if got != tt.want {
+			t.Errorf("readVarInt(%d, %v ~ %x) = %+v; want %+v", tt.n, tt.p, tt.p, got, tt.want)
+		}
+	}
+}