github.com/MerlinKodo/quic-go@v0.39.2/internal/protocol/version.go (about)

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