diff --git a/internal/protocol/version.go b/internal/protocol/version.go index 5ad04f00..600b508b 100644 --- a/internal/protocol/version.go +++ b/internal/protocol/version.go @@ -1,11 +1,13 @@ package protocol import ( + "crypto/rand" + "encoding/binary" "fmt" ) // VersionNumber is a version number as int -type VersionNumber int +type VersionNumber int32 // gQUIC version range as defined in the wiki: https://github.com/quicwg/base-drafts/wiki/QUIC-Versions const ( @@ -112,3 +114,22 @@ func ChooseSupportedVersion(ours, theirs []VersionNumber) (VersionNumber, bool) } return 0, false } + +// generateReservedVersion generates a reserved version number (v & 0x0f0f0f0f == 0x0a0a0a0a) +func generateReservedVersion() VersionNumber { + b := make([]byte, 4) + _, _ = rand.Read(b) // ignore the error here. Failure to read random data doesn't break anything + return VersionNumber((binary.BigEndian.Uint32(b) | 0x0a0a0a0a) & 0xfafafafa) +} + +// GetGreasedVersions adds one reserved version number to a slice of version numbers, at a random position +func GetGreasedVersions(supported []VersionNumber) []VersionNumber { + b := make([]byte, 1) + _, _ = rand.Read(b) // ignore the error here. Failure to read random data doesn't break anything + randPos := int(b[0]) % (len(supported) + 1) + greased := make([]VersionNumber, len(supported)+1) + copy(greased, supported[:randPos]) + greased[randPos] = generateReservedVersion() + copy(greased[randPos+1:], supported[randPos:]) + return greased +} diff --git a/internal/protocol/version_test.go b/internal/protocol/version_test.go index c69888dd..67d600ab 100644 --- a/internal/protocol/version_test.go +++ b/internal/protocol/version_test.go @@ -6,6 +6,10 @@ import ( ) var _ = Describe("Version", func() { + isReservedVersion := func(v VersionNumber) bool { + return v&0x0f0f0f0f == 0x0a0a0a0a + } + // version numbers taken from the wiki: https://github.com/quicwg/base-drafts/wiki/QUIC-Versions It("has the right gQUIC version number", func() { Expect(Version39).To(BeEquivalentTo(0x51303339)) @@ -16,6 +20,11 @@ var _ = Describe("Version", func() { Expect(VersionTLS.UsesTLS()).To(BeTrue()) }) + It("versions don't have reserved version numbers", func() { + Expect(isReservedVersion(Version39)).To(BeFalse()) + Expect(isReservedVersion(VersionTLS)).To(BeFalse()) + }) + It("has the right string representation", func() { Expect(Version39.String()).To(Equal("gQUIC 39")) Expect(VersionTLS.String()).To(ContainSubstring("TLS")) @@ -105,4 +114,47 @@ var _ = Describe("Version", func() { Expect(ok).To(BeFalse()) }) }) + + Context("reserved versions", func() { + It("adds a greased version if passed an empty slice", func() { + greased := GetGreasedVersions([]VersionNumber{}) + Expect(greased).To(HaveLen(1)) + Expect(isReservedVersion(greased[0])).To(BeTrue()) + }) + + It("creates greased lists of version numbers", func() { + supported := []VersionNumber{10, 18, 29} + for _, v := range supported { + Expect(isReservedVersion(v)).To(BeFalse()) + } + var greasedVersionFirst, greasedVersionLast, greasedVersionMiddle int + // check that + // 1. the greased version sometimes appears first + // 2. the greased version sometimes appears in the middle + // 3. the greased version sometimes appears last + // 4. the supported versions are kept in order + for i := 0; i < 100; i++ { + greased := GetGreasedVersions(supported) + Expect(greased).To(HaveLen(4)) + var j int + for i, v := range greased { + if isReservedVersion(v) { + if i == 0 { + greasedVersionFirst++ + } + if i == len(greased)-1 { + greasedVersionLast++ + } + greasedVersionMiddle++ + continue + } + Expect(supported[j]).To(Equal(v)) + j++ + } + } + Expect(greasedVersionFirst).ToNot(BeZero()) + Expect(greasedVersionLast).ToNot(BeZero()) + Expect(greasedVersionMiddle).ToNot(BeZero()) + }) + }) })