From 02f3d0fc4435f74e2f4c3ee0fff608e45a402397 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Tue, 25 Oct 2022 11:57:29 +0100 Subject: [PATCH] http3: add support for parsing and writing HTTP/3 capsules --- http3/capsule.go | 55 +++++++++++++++++++++++++++++++++++++++++ http3/capsule_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 http3/capsule.go create mode 100644 http3/capsule_test.go diff --git a/http3/capsule.go b/http3/capsule.go new file mode 100644 index 00000000000..6f7700f4328 --- /dev/null +++ b/http3/capsule.go @@ -0,0 +1,55 @@ +package http3 + +import ( + "io" + + "github.com/lucas-clemente/quic-go/quicvarint" +) + +// CapsuleType is the type of the capsule. +type CapsuleType uint64 + +type exactReader struct { + R *io.LimitedReader +} + +func (r *exactReader) Read(b []byte) (int, error) { + n, err := r.R.Read(b) + if r.R.N > 0 { + return n, io.ErrUnexpectedEOF + } + return n, err +} + +// ParseCapsule parses the header of a Capsule. +// It returns an io.LimitedReader that can be used to read the Capsule value. +// The Capsule value must be read entirely (i.e. until the io.EOF) before using r again. +func ParseCapsule(r quicvarint.Reader) (CapsuleType, io.Reader, error) { + ct, err := quicvarint.Read(r) + if err != nil { + if err == io.EOF { + return 0, nil, io.ErrUnexpectedEOF + } + return 0, nil, err + } + l, err := quicvarint.Read(r) + if err != nil { + if err == io.EOF { + return 0, nil, io.ErrUnexpectedEOF + } + return 0, nil, err + } + return CapsuleType(ct), &exactReader{R: io.LimitReader(r, int64(l)).(*io.LimitedReader)}, nil +} + +// WriteCapsule writes a capsule +func WriteCapsule(w quicvarint.Writer, ct CapsuleType, value []byte) error { + b := make([]byte, 0, 16) + b = quicvarint.Append(b, uint64(ct)) + b = quicvarint.Append(b, uint64(len(value))) + if _, err := w.Write(b); err != nil { + return err + } + _, err := w.Write(value) + return err +} diff --git a/http3/capsule_test.go b/http3/capsule_test.go new file mode 100644 index 00000000000..ffa93b742e7 --- /dev/null +++ b/http3/capsule_test.go @@ -0,0 +1,57 @@ +package http3 + +import ( + "bytes" + "io" + + "github.com/lucas-clemente/quic-go/quicvarint" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Capsule", func() { + It("parses Capsules", func() { + var buf bytes.Buffer + quicvarint.Write(&buf, 1337) + quicvarint.Write(&buf, 6) + buf.WriteString("foobar") + + ct, r, err := ParseCapsule(&buf) + Expect(err).ToNot(HaveOccurred()) + Expect(ct).To(BeEquivalentTo(1337)) + val, err := io.ReadAll(r) + Expect(err).ToNot(HaveOccurred()) + Expect(string(val)).To(Equal("foobar")) + }) + + It("writes capsules", func() { + var buf bytes.Buffer + WriteCapsule(&buf, 1337, []byte("foobar")) + + ct, r, err := ParseCapsule(&buf) + Expect(err).ToNot(HaveOccurred()) + Expect(ct).To(BeEquivalentTo(1337)) + val, err := io.ReadAll(r) + Expect(err).ToNot(HaveOccurred()) + Expect(string(val)).To(Equal("foobar")) + }) + + It("errors on EOF", func() { + var buf bytes.Buffer + quicvarint.Write(&buf, 1337) + quicvarint.Write(&buf, 6) + buf.WriteString("foobar") + data := buf.Bytes() + + for i := range data { + ct, r, err := ParseCapsule(bytes.NewReader(data[:i])) + if err != nil { + Expect(err).To(MatchError(io.ErrUnexpectedEOF)) + continue + } + Expect(ct).To(BeEquivalentTo(1337)) + _, err = io.ReadAll(r) + Expect(err).To(Equal(io.ErrUnexpectedEOF)) + } + }) +})