1package protocol
2
3import (
4	"crypto/rand"
5	"encoding/binary"
6	"fmt"
7	"math"
8)
9
10// VersionNumber is a version number as int
11type VersionNumber uint32
12
13// gQUIC version range as defined in the wiki: https://github.com/quicwg/base-drafts/wiki/QUIC-Versions
14const (
15	gquicVersion0   = 0x51303030
16	maxGquicVersion = 0x51303439
17)
18
19// The version numbers, making grepping easier
20const (
21	VersionTLS      VersionNumber = 0x1
22	VersionWhatever VersionNumber = math.MaxUint32 - 1 // for when the version doesn't matter
23	VersionUnknown  VersionNumber = math.MaxUint32
24	VersionDraft29  VersionNumber = 0xff00001d
25	Version1        VersionNumber = 0x1
26)
27
28// SupportedVersions lists the versions that the server supports
29// must be in sorted descending order
30var SupportedVersions = []VersionNumber{Version1, VersionDraft29}
31
32// IsValidVersion says if the version is known to quic-go
33func IsValidVersion(v VersionNumber) bool {
34	return v == VersionTLS || IsSupportedVersion(SupportedVersions, v)
35}
36
37func (vn VersionNumber) String() string {
38	// For releases, VersionTLS will be set to a draft version.
39	// A switch statement can't contain duplicate cases.
40	if vn == VersionTLS && VersionTLS != VersionDraft29 && VersionTLS != Version1 {
41		return "TLS dev version (WIP)"
42	}
43	//nolint:exhaustive
44	switch vn {
45	case VersionWhatever:
46		return "whatever"
47	case VersionUnknown:
48		return "unknown"
49	case VersionDraft29:
50		return "draft-29"
51	case Version1:
52		return "v1"
53	default:
54		if vn.isGQUIC() {
55			return fmt.Sprintf("gQUIC %d", vn.toGQUICVersion())
56		}
57		return fmt.Sprintf("%#x", uint32(vn))
58	}
59}
60
61func (vn VersionNumber) isGQUIC() bool {
62	return vn > gquicVersion0 && vn <= maxGquicVersion
63}
64
65func (vn VersionNumber) toGQUICVersion() int {
66	return int(10*(vn-gquicVersion0)/0x100) + int(vn%0x10)
67}
68
69// IsSupportedVersion returns true if the server supports this version
70func IsSupportedVersion(supported []VersionNumber, v VersionNumber) bool {
71	for _, t := range supported {
72		if t == v {
73			return true
74		}
75	}
76	return false
77}
78
79// ChooseSupportedVersion finds the best version in the overlap of ours and theirs
80// ours is a slice of versions that we support, sorted by our preference (descending)
81// theirs is a slice of versions offered by the peer. The order does not matter.
82// The bool returned indicates if a matching version was found.
83func ChooseSupportedVersion(ours, theirs []VersionNumber) (VersionNumber, bool) {
84	for _, ourVer := range ours {
85		for _, theirVer := range theirs {
86			if ourVer == theirVer {
87				return ourVer, true
88			}
89		}
90	}
91	return 0, false
92}
93
94// generateReservedVersion generates a reserved version number (v & 0x0f0f0f0f == 0x0a0a0a0a)
95func generateReservedVersion() VersionNumber {
96	b := make([]byte, 4)
97	_, _ = rand.Read(b) // ignore the error here. Failure to read random data doesn't break anything
98	return VersionNumber((binary.BigEndian.Uint32(b) | 0x0a0a0a0a) & 0xfafafafa)
99}
100
101// GetGreasedVersions adds one reserved version number to a slice of version numbers, at a random position
102func GetGreasedVersions(supported []VersionNumber) []VersionNumber {
103	b := make([]byte, 1)
104	_, _ = rand.Read(b) // ignore the error here. Failure to read random data doesn't break anything
105	randPos := int(b[0]) % (len(supported) + 1)
106	greased := make([]VersionNumber, len(supported)+1)
107	copy(greased, supported[:randPos])
108	greased[randPos] = generateReservedVersion()
109	copy(greased[randPos+1:], supported[randPos:])
110	return greased
111}
112