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