github.com/ooni/psiphon/tunnel-core@v0.0.0-20230105123940-fe12a24c96ee/oovendor/quic-go/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  	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
    30  var SupportedVersions = []VersionNumber{Version1, VersionDraft29}
    31  
    32  // IsValidVersion says if the version is known to quic-go
    33  func IsValidVersion(v VersionNumber) bool {
    34  	return v == VersionTLS || IsSupportedVersion(SupportedVersions, v)
    35  }
    36  
    37  func (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  
    61  func (vn VersionNumber) isGQUIC() bool {
    62  	return vn > gquicVersion0 && vn <= maxGquicVersion
    63  }
    64  
    65  func (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
    70  func 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.
    83  func 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)
    95  func 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
   102  func 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  }