github.com/apernet/quic-go@v0.43.1-0.20240515053213-5e9e635fd9f0/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  }