Browse Source

work on errors, frame parsing registry, settings

Brad Fitzpatrick 11 years ago
parent
commit
a476fa8086
3 changed files with 193 additions and 20 deletions
  1. 58 0
      errors.go
  2. 122 18
      frame.go
  3. 13 2
      http2.go

+ 58 - 0
errors.go

@@ -0,0 +1,58 @@
+package http2
+
+import "fmt"
+
+type ErrCode uint32
+
+const (
+	ErrCodeNo                 ErrCode = 0x0
+	ErrCodeProtocol           ErrCode = 0x1
+	ErrCodeInternal           ErrCode = 0x2
+	ErrCodeFlowControl        ErrCode = 0x3
+	ErrCodeSettingsTimeout    ErrCode = 0x4
+	ErrCodeStreamClosed       ErrCode = 0x5
+	ErrCodeFrameSize          ErrCode = 0x6
+	ErrCodeRefusedStream      ErrCode = 0x7
+	ErrCodeCancel             ErrCode = 0x8
+	ErrCodeCompression        ErrCode = 0x9
+	ErrCodeConnect            ErrCode = 0xa
+	ErrCodeEnhanceYourCalm    ErrCode = 0xb
+	ErrCodeInadequateSecurity ErrCode = 0xc
+)
+
+var ErrCodeName = map[ErrCode]string{
+	ErrCodeNo:                 "NO_ERROR",
+	ErrCodeProtocol:           "PROTOCOL_ERROR",
+	ErrCodeInternal:           "INTERNAL_ERROR",
+	ErrCodeFlowControl:        "FLOW_CONTROL_ERROR",
+	ErrCodeSettingsTimeout:    "SETTINGS_TIMEOUT",
+	ErrCodeStreamClosed:       "STREAM_CLOSED",
+	ErrCodeFrameSize:          "FRAME_SIZE_ERROR",
+	ErrCodeRefusedStream:      "REFUSED_STREAM",
+	ErrCodeCancel:             "CANCEL",
+	ErrCodeCompression:        "COMPRESSION_ERROR",
+	ErrCodeConnect:            "CONNECT_ERROR",
+	ErrCodeEnhanceYourCalm:    "ENHANCE_YOUR_CALM",
+	ErrCodeInadequateSecurity: "INADEQUATE_SECURITY",
+}
+
+func (e ErrCode) String() string {
+	if s, ok := ErrCodeName[e]; ok {
+		return s
+	}
+	return fmt.Sprintf("unknown error code %x", e)
+}
+
+type Error interface {
+	IsStreamError() bool
+	IsConnectionError() bool
+	error
+}
+
+type ConnectionError ErrCode
+
+var _ Error = ConnectionError(0)
+
+func (e ConnectionError) IsStreamError() bool     { return false }
+func (e ConnectionError) IsConnectionError() bool { return true }
+func (e ConnectionError) Error() string           { return fmt.Sprintf("connection error: %s", ErrCode(e)) }

+ 122 - 18
frame.go

@@ -1,7 +1,10 @@
 package http2
 
 import (
+	"encoding/binary"
 	"io"
+	"io/ioutil"
+	"log"
 	"sync"
 )
 
@@ -11,27 +14,66 @@ type FrameType uint8
 
 // Defined in http://http2.github.io/http2-spec/#rfc.section.11.2
 const (
-	FrameData         FrameType = 0
-	FrameHeaders      FrameType = 1
-	FramePriority     FrameType = 2
-	FrameRSTStream    FrameType = 3
-	FrameSettings     FrameType = 4
-	FramePushPromise  FrameType = 5
-	FramePing         FrameType = 6
-	FrameGoAway       FrameType = 7
-	FrameWindowUpdate FrameType = 8
-	FrameContinuation FrameType = 9
+	FrameData         FrameType = 0x0
+	FrameHeaders      FrameType = 0x1
+	FramePriority     FrameType = 0x2
+	FrameRSTStream    FrameType = 0x3
+	FrameSettings     FrameType = 0x4
+	FramePushPromise  FrameType = 0x5
+	FramePing         FrameType = 0x6
+	FrameGoAway       FrameType = 0x7
+	FrameWindowUpdate FrameType = 0x8
+	FrameContinuation FrameType = 0x9
+
+	FlagSettingsAck Flags = 0x1
+)
+
+type SettingID uint16
+
+const (
+	SettingHeaderTableSize      SettingID = 0x1
+	SettingEnablePush           SettingID = 0x2
+	SettingMaxConcurrentStreams SettingID = 0x3
+	SettingInitialWindowSize    SettingID = 0x4
 )
 
+func knownSetting(id SettingID) bool {
+	// TODO: permit registration of custom settings values?
+	// Per server type?
+	return id >= 1 && id <= 4
+}
+
+type frameParser func(FrameHeader, io.Reader) (Frame, error)
+
+var FrameParsers = map[FrameType]frameParser{
+	FrameSettings: parseFrameSettings,
+}
+
+func typeFrameParser(t FrameType) frameParser {
+	if f, ok := FrameParsers[t]; ok {
+		return f
+	}
+	return parseUnknownFrame
+}
+
+type Flags uint8
+
+func (f Flags) Has(v Flags) bool {
+	return (f & v) == v
+}
+
 // A FrameHeader is the 8 byte header of all HTTP/2 frames.
 //
 // See http://http2.github.io/http2-spec/#FrameHeader
 type FrameHeader struct {
-	Type   FrameType
-	Flags  uint8
-	Length uint16
+	Type     FrameType
+	Flags    Flags
+	Length   uint16
+	StreamID uint32
 }
 
+func (h FrameHeader) Header() FrameHeader { return h }
+
 // frame header bytes
 var fhBytes = sync.Pool{
 	New: func() interface{} {
@@ -49,13 +91,75 @@ func ReadFrameHeader(r io.Reader) (FrameHeader, error) {
 		return FrameHeader{}, err
 	}
 	return FrameHeader{
-		Length: (uint16(buf[0])<<8 + uint16(buf[1])) & (1<<14 - 1),
-		Flags:  buf[3],
-		Type:   FrameType(buf[2]),
+		Length:   (uint16(buf[0])<<8 + uint16(buf[1])) & (1<<14 - 1),
+		Flags:    Flags(buf[3]),
+		Type:     FrameType(buf[2]),
+		StreamID: binary.BigEndian.Uint32(buf[4:]) & (1<<31 - 1),
+	}, nil
+}
+
+type Frame interface {
+	Header() FrameHeader
+}
+
+type SettingsFrame struct {
+	FrameHeader
+	Settings map[SettingID]uint32
+}
+
+func parseFrameSettings(fh FrameHeader, r io.Reader) (Frame, error) {
+	if fh.Flags.Has(FlagSettingsAck) && fh.Length > 0 {
+		// When this (ACK 0x1) bit is set, the payload of the
+		// SETTINGS frame MUST be empty.  Receipt of a
+		// SETTINGS frame with the ACK flag set and a length
+		// field value other than 0 MUST be treated as a
+		// connection error (Section 5.4.1) of type
+		// FRAME_SIZE_ERROR.
+		return nil, ConnectionError(ErrCodeFrameSize)
+	}
+	if fh.StreamID != 0 {
+		// SETTINGS frames always apply to a connection,
+		// never a single stream.  The stream identifier for a
+		// SETTINGS frame MUST be zero (0x0).  If an endpoint
+		// receives a SETTINGS frame whose stream identifier
+		// field is anything other than 0x0, the endpoint MUST
+		// respond with a connection error (Section 5.4.1) of
+		// type PROTOCOL_ERROR.
+		log.Printf("Bogus StreamID in settings: %+v", fh)
+		return nil, ConnectionError(ErrCodeProtocol)
+	}
+	if fh.Length%6 != 0 {
+		// Expecting even number of 6 byte settings.
+		return nil, ConnectionError(ErrCodeFrameSize)
+	}
+	s := make(map[SettingID]uint32)
+	nSettings := int(fh.Length / 6)
+	var buf [4]byte
+	for i := 0; i < nSettings; i++ {
+		if _, err := io.ReadFull(r, buf[:2]); err != nil {
+			return nil, err
+		}
+		settingID := SettingID(binary.BigEndian.Uint16(buf[:2]))
+		if _, err := io.ReadFull(r, buf[:4]); err != nil {
+			return nil, err
+		}
+		value := binary.BigEndian.Uint32(buf[:4])
+		if knownSetting(settingID) {
+			s[settingID] = value
+		}
+	}
+
+	return &SettingsFrame{
+		FrameHeader: fh,
+		Settings:    s,
 	}, nil
 }
 
-type Frame struct {
+type UnknownFrame struct {
 	FrameHeader
-	data, unread []byte
+}
+
+func parseUnknownFrame(fh FrameHeader, r io.Reader) (Frame, error) {
+	_, err := io.CopyN(ioutil.Discard, r, int64(fh.Length))
+	return UnknownFrame{fh}, err
 }

+ 13 - 2
http2.go

@@ -74,8 +74,19 @@ func (cc *clientConn) serve() {
 			}
 			return
 		}
-		log.Printf("read frame: %v", fh)
-		break
+		f, err := typeFrameParser(fh.Type)(fh, cc.c)
+		if h2e, ok := err.(Error); ok {
+			if h2e.IsConnectionError() {
+				log.Printf("Disconnection; connection error: %v", err)
+				return
+			}
+			// TODO: stream errors, etc
+		}
+		if err != nil {
+			log.Printf("Disconnection to other error: %v", err)
+			return
+		}
+		log.Printf("read frame: %#v", f)
 	}
 }