Repository: quic-go/quic-go
Pull request #4341 (2024-03-03 06:49:22)

http3: send SETTINGS_ENABLE_CONNECT_PROTOCOL (for Extended CONNECT)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/quic-go/quic-go/pull/4341?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 84.78%. Comparing base [(`0405634`)](https://app.codecov.io/gh/quic-go/quic-go/commit/040563410820aba7d3a443b0038e638acd6fe838?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) to head [(`a957398`)](https://app.codecov.io/gh/quic-go/quic-go/pull/4341?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4341      +/-   ##
==========================================
- Coverage   84.79%   84.78%   -0.01%     
==========================================
  Files         150      147       -3     
  Lines       14254    14020     -234     
==========================================
- Hits        12086    11886     -200     
+ Misses       1670     1651      -19     
+ Partials      498      483      -15     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/quic-go/quic-go/pull/4341?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).


Patch:
diff --git a/http3/frames.go b/http3/frames.go
index 454e5f94..90b22f6b 100644
--- a/http3/frames.go
+++ b/http3/frames.go
@@ -88,11 +88,18 @@ func (f *headersFrame) Append(b []byte) []byte {
 	return quicvarint.Append(b, f.Length)
 }
 
-const settingDatagram = 0x33
+const (
+	// Extended CONNECT, RFC 9220
+	settingExtendedConnect = 0x8
+	// HTTP Datagrams, RFC 9297
+	settingDatagram = 0x33
+)
 
 type settingsFrame struct {
-	Datagram bool
-	Other    map[uint64]uint64 // all settings that we don't explicitly recognize
+	Datagram        bool // HTTP Datagrams, RFC 9297
+	ExtendedConnect bool // Extended CONNECT, RFC 9220
+
+	Other map[uint64]uint64 // all settings that we don't explicitly recognize
 }
 
 func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
@@ -108,7 +115,7 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
 	}
 	frame := &settingsFrame{}
 	b := bytes.NewReader(buf)
-	var readDatagram bool
+	var readDatagram, readExtendedConnect bool
 	for b.Len() > 0 {
 		id, err := quicvarint.Read(b)
 		if err != nil { // should not happen. We allocated the whole frame already.
@@ -120,13 +127,22 @@ func parseSettingsFrame(r io.Reader, l uint64) (*settingsFrame, error) {
 		}
 
 		switch id {
+		case settingExtendedConnect:
+			if readExtendedConnect {
+				return nil, fmt.Errorf("duplicate setting: %d", id)
+			}
+			readExtendedConnect = true
+			if val != 0 && val != 1 {
+				return nil, fmt.Errorf("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: %d", val)
+			}
+			frame.ExtendedConnect = val == 1
 		case settingDatagram:
 			if readDatagram {
 				return nil, fmt.Errorf("duplicate setting: %d", id)
 			}
 			readDatagram = true
 			if val != 0 && val != 1 {
-				return nil, fmt.Errorf("invalid value for H3_DATAGRAM: %d", val)
+				return nil, fmt.Errorf("invalid value for SETTINGS_H3_DATAGRAM: %d", val)
 			}
 			frame.Datagram = val == 1
 		default:
@@ -151,11 +167,18 @@ func (f *settingsFrame) Append(b []byte) []byte {
 	if f.Datagram {
 		l += quicvarint.Len(settingDatagram) + quicvarint.Len(1)
 	}
+	if f.ExtendedConnect {
+		l += quicvarint.Len(settingExtendedConnect) + quicvarint.Len(1)
+	}
 	b = quicvarint.Append(b, uint64(l))
 	if f.Datagram {
 		b = quicvarint.Append(b, settingDatagram)
 		b = quicvarint.Append(b, 1)
 	}
+	if f.ExtendedConnect {
+		b = quicvarint.Append(b, settingExtendedConnect)
+		b = quicvarint.Append(b, 1)
+	}
 	for id, val := range f.Other {
 		b = quicvarint.Append(b, id)
 		b = quicvarint.Append(b, val)
diff --git a/http3/server.go b/http3/server.go
index 2d5437c9..c17b16b5 100644
--- a/http3/server.go
+++ b/http3/server.go
@@ -451,7 +451,11 @@ func (s *Server) handleConn(conn quic.Connection) error {
 	}
 	b := make([]byte, 0, 64)
 	b = quicvarint.Append(b, streamTypeControlStream) // stream type
-	b = (&settingsFrame{Datagram: s.EnableDatagrams, Other: s.AdditionalSettings}).Append(b)
+	b = (&settingsFrame{
+		Datagram:        s.EnableDatagrams,
+		ExtendedConnect: true,
+		Other:           s.AdditionalSettings,
+	}).Append(b)
 	str.Write(b)
 
 	go s.handleUnidirectionalStreams(conn)


Test patch:
diff --git a/http3/frames_test.go b/http3/frames_test.go
index 04e583d2..fbb1d752 100644
--- a/http3/frames_test.go
+++ b/http3/frames_test.go
@@ -127,8 +127,8 @@ var _ = Describe("Frames", func() {
 			}
 		})
 
-		Context("H3_DATAGRAM", func() {
-			It("reads the H3_DATAGRAM value", func() {
+		Context("HTTP Datagrams", func() {
+			It("reads the SETTINGS_H3_DATAGRAM value", func() {
 				settings := quicvarint.Append(nil, settingDatagram)
 				settings = quicvarint.Append(settings, 1)
 				data := quicvarint.Append(nil, 4) // type byte
@@ -141,7 +141,7 @@ var _ = Describe("Frames", func() {
 				Expect(sf.Datagram).To(BeTrue())
 			})
 
-			It("rejects duplicate H3_DATAGRAM entries", func() {
+			It("rejects duplicate SETTINGS_H3_DATAGRAM entries", func() {
 				settings := quicvarint.Append(nil, settingDatagram)
 				settings = quicvarint.Append(settings, 1)
 				settings = quicvarint.Append(settings, settingDatagram)
@@ -153,23 +153,67 @@ var _ = Describe("Frames", func() {
 				Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingDatagram)))
 			})
 
-			It("rejects invalid values for the H3_DATAGRAM entry", func() {
+			It("rejects invalid values for the SETTINGS_H3_DATAGRAM entry", func() {
 				settings := quicvarint.Append(nil, settingDatagram)
 				settings = quicvarint.Append(settings, 1337)
 				data := quicvarint.Append(nil, 4) // type byte
 				data = quicvarint.Append(data, uint64(len(settings)))
 				data = append(data, settings...)
 				_, err := parseNextFrame(bytes.NewReader(data), nil)
-				Expect(err).To(MatchError("invalid value for H3_DATAGRAM: 1337"))
+				Expect(err).To(MatchError("invalid value for SETTINGS_H3_DATAGRAM: 1337"))
 			})
 
-			It("writes the H3_DATAGRAM setting", func() {
+			It("writes the SETTINGS_H3_DATAGRAM setting", func() {
 				sf := &settingsFrame{Datagram: true}
 				frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil)
 				Expect(err).ToNot(HaveOccurred())
 				Expect(frame).To(Equal(sf))
 			})
 		})
+
+		Context("Extended Connect", func() {
+			It("reads the SETTINGS_ENABLE_CONNECT_PROTOCOL value", func() {
+				settings := quicvarint.Append(nil, settingExtendedConnect)
+				settings = quicvarint.Append(settings, 1)
+				data := quicvarint.Append(nil, 4) // type byte
+				data = quicvarint.Append(data, uint64(len(settings)))
+				data = append(data, settings...)
+				f, err := parseNextFrame(bytes.NewReader(data), nil)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(f).To(BeAssignableToTypeOf(&settingsFrame{}))
+				sf := f.(*settingsFrame)
+				Expect(sf.ExtendedConnect).To(BeTrue())
+			})
+
+			It("rejects duplicate SETTINGS_ENABLE_CONNECT_PROTOCOL entries", func() {
+				settings := quicvarint.Append(nil, settingExtendedConnect)
+				settings = quicvarint.Append(settings, 1)
+				settings = quicvarint.Append(settings, settingExtendedConnect)
+				settings = quicvarint.Append(settings, 1)
+				data := quicvarint.Append(nil, 4) // type byte
+				data = quicvarint.Append(data, uint64(len(settings)))
+				data = append(data, settings...)
+				_, err := parseNextFrame(bytes.NewReader(data), nil)
+				Expect(err).To(MatchError(fmt.Sprintf("duplicate setting: %d", settingExtendedConnect)))
+			})
+
+			It("rejects invalid values for the SETTINGS_ENABLE_CONNECT_PROTOCOL entry", func() {
+				settings := quicvarint.Append(nil, settingExtendedConnect)
+				settings = quicvarint.Append(settings, 1337)
+				data := quicvarint.Append(nil, 4) // type byte
+				data = quicvarint.Append(data, uint64(len(settings)))
+				data = append(data, settings...)
+				_, err := parseNextFrame(bytes.NewReader(data), nil)
+				Expect(err).To(MatchError("invalid value for SETTINGS_ENABLE_CONNECT_PROTOCOL: 1337"))
+			})
+
+			It("writes the SETTINGS_ENABLE_CONNECT_PROTOCOL setting", func() {
+				sf := &settingsFrame{ExtendedConnect: true}
+				frame, err := parseNextFrame(bytes.NewReader(sf.Append(nil)), nil)
+				Expect(err).ToNot(HaveOccurred())
+				Expect(frame).To(Equal(sf))
+			})
+		})
 	})
 
 	Context("hijacking", func() {