Browse Source

start of hpack decoding.

Brad Fitzpatrick 11 years ago
parent
commit
032cc7f6da
2 changed files with 118 additions and 39 deletions
  1. 68 14
      hpack/hpack.go
  2. 50 25
      hpack/hpack_test.go

+ 68 - 14
hpack/hpack.go

@@ -11,6 +11,23 @@ package hpack
 
 
 import "fmt"
 import "fmt"
 
 
+// A DecodingError is something the spec defines as a decoding error.
+type DecodingError struct {
+	Err error
+}
+
+func (de DecodingError) Error() string {
+	return fmt.Sprintf("decoding error: %v", de.Err)
+}
+
+// An InvalidIndexError is returned when an encoder references a table
+// entry before the static table or after the end of the dynamic table.
+type InvalidIndexError int
+
+func (e InvalidIndexError) Error() string {
+	return fmt.Sprintf("invalid indexed representation index %d", int(e))
+}
+
 // A HeaderField is a name-value pair. Both the name and value are
 // A HeaderField is a name-value pair. Both the name and value are
 // treated as opaque sequences of octets.
 // treated as opaque sequences of octets.
 type HeaderField struct {
 type HeaderField struct {
@@ -60,7 +77,7 @@ type dynamicTable struct {
 	// http://http2.github.io/http2-spec/compression.html#rfc.section.2.3.2
 	// http://http2.github.io/http2-spec/compression.html#rfc.section.2.3.2
 	// The newest (low index) is append at the end, and items are
 	// The newest (low index) is append at the end, and items are
 	// evicted from the front.
 	// evicted from the front.
-	s       []HeaderField
+	ents    []HeaderField
 	size    uint32
 	size    uint32
 	maxSize uint32
 	maxSize uint32
 }
 }
@@ -80,36 +97,73 @@ func (dt *dynamicTable) setMaxSize(v uint32) {
 // Later we'll need a remove operation on dynamicTable.
 // Later we'll need a remove operation on dynamicTable.
 
 
 func (dt *dynamicTable) add(f HeaderField) {
 func (dt *dynamicTable) add(f HeaderField) {
-	dt.s = append(dt.s, f)
+	dt.ents = append(dt.ents, f)
 	dt.size += f.size()
 	dt.size += f.size()
 	dt.evict()
 	dt.evict()
 }
 }
 
 
 // If we're too big, evict old stuff (front of the slice)
 // If we're too big, evict old stuff (front of the slice)
 func (dt *dynamicTable) evict() {
 func (dt *dynamicTable) evict() {
-	base := dt.s // keep base pointer of slice
+	base := dt.ents // keep base pointer of slice
 	for dt.size > dt.maxSize {
 	for dt.size > dt.maxSize {
-		dt.size -= dt.s[0].size()
-		dt.s = dt.s[1:]
+		dt.size -= dt.ents[0].size()
+		dt.ents = dt.ents[1:]
 	}
 	}
 
 
 	// Shift slice contents down if we evicted things.
 	// Shift slice contents down if we evicted things.
-	if len(dt.s) != len(base) {
-		copy(base, dt.s)
-		dt.s = base[:len(dt.s)]
+	if len(dt.ents) != len(base) {
+		copy(base, dt.ents)
+		dt.ents = base[:len(dt.ents)]
 	}
 	}
 }
 }
 
 
-func (dt *dynamicTable) at(i int) HeaderField {
+func (d *Decoder) at(i int) (hf HeaderField, ok bool) {
 	if i < 1 {
 	if i < 1 {
-		panic(fmt.Sprintf("header table index %d too small", i))
+		return
 	}
 	}
-	max := len(dt.s) + len(staticTable)
+	dents := d.dynTab.ents
+	max := len(dents) + len(staticTable)
 	if i > max {
 	if i > max {
-		panic(fmt.Sprintf("header table index %d too large (max = %d)", i, max))
+		return
 	}
 	}
 	if i <= len(staticTable) {
 	if i <= len(staticTable) {
-		return staticTable[i-1]
+		return staticTable[i-1], true
+	}
+	return dents[len(dents)-(i-len(staticTable))], true
+}
+
+// Decode decodes an entire block.
+//
+// TODO: remove this method and make it incremental later? This is
+// easier for debugging now.
+func (d *Decoder) Decode(p []byte) ([]HeaderField, error) {
+	var hf []HeaderField
+	// TODO: This is trashy. temporary development aid.
+	saveFunc := d.emit
+	defer func() { d.emit = saveFunc }()
+	d.emit = func(f HeaderField, sensitive bool) {
+		hf = append(hf, f)
+	}
+
+	for len(p) > 0 {
+		// Look at first byte to see what we're dealing with.
+		switch {
+		case p[0]&(1<<7) != 0:
+			// Indexed representation.
+			// http://http2.github.io/http2-spec/compression.html#rfc.section.6.1
+			idx := p[0] & ((1 << 7) - 1)
+			if idx == 127 {
+				panic("TODO: varuint decoding")
+			}
+			hf, ok := d.at(int(idx))
+			if !ok {
+				return nil, DecodingError{InvalidIndexError(idx)}
+			}
+			d.emit(hf, false /* TODO: sensitive ? */)
+			p = p[1:]
+		default:
+			panic("TODO")
+		}
 	}
 	}
-	return dt.s[len(dt.s)-(i-len(staticTable))]
+	return hf, nil
 }
 }

+ 50 - 25
hpack/hpack_test.go

@@ -9,6 +9,8 @@ import (
 	"bufio"
 	"bufio"
 	"bytes"
 	"bytes"
 	"encoding/hex"
 	"encoding/hex"
+	"fmt"
+	"reflect"
 	"regexp"
 	"regexp"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
@@ -113,49 +115,72 @@ func TestStaticTable(t *testing.T) {
 	}
 	}
 }
 }
 
 
+func (d *Decoder) mustAt(idx int) HeaderField {
+	if hf, ok := d.at(idx); !ok {
+		panic(fmt.Sprintf("bogus index %d", idx))
+	} else {
+		return hf
+	}
+}
+
 func TestDynamicTableAt(t *testing.T) {
 func TestDynamicTableAt(t *testing.T) {
-	var dt dynamicTable
-	dt.setMaxSize(4 << 10)
-	if got, want := dt.at(2), (HeaderField{":method", "GET"}); got != want {
+	d := NewDecoder(4096, nil)
+	at := d.mustAt
+	if got, want := at(2), (HeaderField{":method", "GET"}); got != want {
 		t.Errorf("at(2) = %q; want %q", got, want)
 		t.Errorf("at(2) = %q; want %q", got, want)
 	}
 	}
-	dt.add(HeaderField{"foo", "bar"})
-	dt.add(HeaderField{"blake", "miz"})
-	if got, want := dt.at(len(staticTable)+1), (HeaderField{"blake", "miz"}); got != want {
+	d.dynTab.add(HeaderField{"foo", "bar"})
+	d.dynTab.add(HeaderField{"blake", "miz"})
+	if got, want := at(len(staticTable)+1), (HeaderField{"blake", "miz"}); got != want {
 		t.Errorf("at(dyn 1) = %q; want %q", got, want)
 		t.Errorf("at(dyn 1) = %q; want %q", got, want)
 	}
 	}
-	if got, want := dt.at(len(staticTable)+2), (HeaderField{"foo", "bar"}); got != want {
+	if got, want := at(len(staticTable)+2), (HeaderField{"foo", "bar"}); got != want {
 		t.Errorf("at(dyn 2) = %q; want %q", got, want)
 		t.Errorf("at(dyn 2) = %q; want %q", got, want)
 	}
 	}
-	if got, want := dt.at(3), (HeaderField{":method", "POST"}); got != want {
+	if got, want := at(3), (HeaderField{":method", "POST"}); got != want {
 		t.Errorf("at(3) = %q; want %q", got, want)
 		t.Errorf("at(3) = %q; want %q", got, want)
 	}
 	}
 }
 }
 
 
 func TestDynamicTableSizeEvict(t *testing.T) {
 func TestDynamicTableSizeEvict(t *testing.T) {
-	var dt dynamicTable
-	dt.setMaxSize(4 << 10)
-	if want := uint32(0); dt.size != want {
-		t.Fatalf("size = %d; want %d", dt.size, want)
+	d := NewDecoder(4096, nil)
+	if want := uint32(0); d.dynTab.size != want {
+		t.Fatalf("size = %d; want %d", d.dynTab.size, want)
 	}
 	}
-	dt.add(HeaderField{"blake", "eats pizza"})
-	if want := uint32(15 + 32); dt.size != want {
-		t.Fatalf("after pizza, size = %d; want %d", dt.size, want)
+	add := d.dynTab.add
+	add(HeaderField{"blake", "eats pizza"})
+	if want := uint32(15 + 32); d.dynTab.size != want {
+		t.Fatalf("after pizza, size = %d; want %d", d.dynTab.size, want)
 	}
 	}
-	dt.add(HeaderField{"foo", "bar"})
-	if want := uint32(15 + 32 + 6 + 32); dt.size != want {
-		t.Fatalf("after foo bar, size = %d; want %d", dt.size, want)
+	add(HeaderField{"foo", "bar"})
+	if want := uint32(15 + 32 + 6 + 32); d.dynTab.size != want {
+		t.Fatalf("after foo bar, size = %d; want %d", d.dynTab.size, want)
 	}
 	}
-	dt.setMaxSize(15 + 32 + 1 /* slop */)
-	if want := uint32(6 + 32); dt.size != want {
-		t.Fatalf("after setMaxSize, size = %d; want %d", dt.size, want)
+	d.dynTab.setMaxSize(15 + 32 + 1 /* slop */)
+	if want := uint32(6 + 32); d.dynTab.size != want {
+		t.Fatalf("after setMaxSize, size = %d; want %d", d.dynTab.size, want)
 	}
 	}
-	if got, want := dt.at(len(staticTable)+1), (HeaderField{"foo", "bar"}); got != want {
+	if got, want := d.mustAt(len(staticTable)+1), (HeaderField{"foo", "bar"}); got != want {
 		t.Errorf("at(dyn 1) = %q; want %q", got, want)
 		t.Errorf("at(dyn 1) = %q; want %q", got, want)
 	}
 	}
-	dt.add(HeaderField{"long", strings.Repeat("x", 500)})
-	if want := uint32(0); dt.size != want {
-		t.Fatalf("after big one, size = %d; want %d", dt.size, want)
+	add(HeaderField{"long", strings.Repeat("x", 500)})
+	if want := uint32(0); d.dynTab.size != want {
+		t.Fatalf("after big one, size = %d; want %d", d.dynTab.size, want)
+	}
+}
+
+func TestDecoderDecode(t *testing.T) {
+	d := NewDecoder(4096, nil)
+
+	// Indexed Header Field
+	// http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.4
+	hf, err := d.Decode([]byte("\x82"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	want := []HeaderField{{":method", "GET"}}
+	if !reflect.DeepEqual(hf, want) {
+		t.Errorf("Got %v; want %v", hf, want)
 	}
 	}
 }
 }