github.com/psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/quic/gquic-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  	Version39       VersionNumber = gquicVersion0 + 3*0x100 + 0x9
    22  	Version43       VersionNumber = gquicVersion0 + 4*0x100 + 0x3
    23  	Version44       VersionNumber = gquicVersion0 + 4*0x100 + 0x4
    24  	VersionTLS      VersionNumber = 101
    25  	VersionWhatever VersionNumber = 0 // for when the version doesn't matter
    26  	VersionUnknown  VersionNumber = math.MaxUint32
    27  )
    28  
    29  // SupportedVersions lists the versions that the server supports
    30  // must be in sorted descending order
    31  var SupportedVersions = []VersionNumber{
    32  	Version44,
    33  	Version43,
    34  	Version39,
    35  }
    36  
    37  // IsValidVersion says if the version is known to quic-go
    38  func IsValidVersion(v VersionNumber) bool {
    39  	return v == VersionTLS || IsSupportedVersion(SupportedVersions, v)
    40  }
    41  
    42  // UsesTLS says if this QUIC version uses TLS 1.3 for the handshake
    43  func (vn VersionNumber) UsesTLS() bool {
    44  	return !vn.isGQUIC()
    45  }
    46  
    47  func (vn VersionNumber) String() string {
    48  	switch vn {
    49  	case VersionWhatever:
    50  		return "whatever"
    51  	case VersionUnknown:
    52  		return "unknown"
    53  	case VersionTLS:
    54  		return "TLS dev version (WIP)"
    55  	default:
    56  		if vn.isGQUIC() {
    57  			return fmt.Sprintf("gQUIC %d", vn.toGQUICVersion())
    58  		}
    59  		return fmt.Sprintf("%#x", uint32(vn))
    60  	}
    61  }
    62  
    63  // ToAltSvc returns the representation of the version for the H2 Alt-Svc parameters
    64  func (vn VersionNumber) ToAltSvc() string {
    65  	if vn.isGQUIC() {
    66  		return fmt.Sprintf("%d", vn.toGQUICVersion())
    67  	}
    68  	return fmt.Sprintf("%d", vn)
    69  }
    70  
    71  // CryptoStreamID gets the Stream ID of the crypto stream
    72  func (vn VersionNumber) CryptoStreamID() StreamID {
    73  	if vn.isGQUIC() {
    74  		return 1
    75  	}
    76  	return 0
    77  }
    78  
    79  // UsesIETFFrameFormat tells if this version uses the IETF frame format
    80  func (vn VersionNumber) UsesIETFFrameFormat() bool {
    81  	return !vn.isGQUIC()
    82  }
    83  
    84  // UsesIETFHeaderFormat tells if this version uses the IETF header format
    85  func (vn VersionNumber) UsesIETFHeaderFormat() bool {
    86  	return !vn.isGQUIC() || vn >= Version44
    87  }
    88  
    89  // UsesLengthInHeader tells if this version uses the Length field in the IETF header
    90  func (vn VersionNumber) UsesLengthInHeader() bool {
    91  	return !vn.isGQUIC()
    92  }
    93  
    94  // UsesTokenInHeader tells if this version uses the Token field in the IETF header
    95  func (vn VersionNumber) UsesTokenInHeader() bool {
    96  	return !vn.isGQUIC()
    97  }
    98  
    99  // UsesStopWaitingFrames tells if this version uses STOP_WAITING frames
   100  func (vn VersionNumber) UsesStopWaitingFrames() bool {
   101  	return vn.isGQUIC() && vn <= Version43
   102  }
   103  
   104  // UsesVarintPacketNumbers tells if this version uses 7/14/30 bit packet numbers
   105  func (vn VersionNumber) UsesVarintPacketNumbers() bool {
   106  	return !vn.isGQUIC()
   107  }
   108  
   109  // StreamContributesToConnectionFlowControl says if a stream contributes to connection-level flow control
   110  func (vn VersionNumber) StreamContributesToConnectionFlowControl(id StreamID) bool {
   111  	if id == vn.CryptoStreamID() {
   112  		return false
   113  	}
   114  	if vn.isGQUIC() && id == 3 {
   115  		return false
   116  	}
   117  	return true
   118  }
   119  
   120  func (vn VersionNumber) isGQUIC() bool {
   121  	return vn > gquicVersion0 && vn <= maxGquicVersion
   122  }
   123  
   124  func (vn VersionNumber) toGQUICVersion() int {
   125  	return int(10*(vn-gquicVersion0)/0x100) + int(vn%0x10)
   126  }
   127  
   128  // IsSupportedVersion returns true if the server supports this version
   129  func IsSupportedVersion(supported []VersionNumber, v VersionNumber) bool {
   130  	for _, t := range supported {
   131  		if t == v {
   132  			return true
   133  		}
   134  	}
   135  	return false
   136  }
   137  
   138  // ChooseSupportedVersion finds the best version in the overlap of ours and theirs
   139  // ours is a slice of versions that we support, sorted by our preference (descending)
   140  // theirs is a slice of versions offered by the peer. The order does not matter.
   141  // The bool returned indicates if a matching version was found.
   142  func ChooseSupportedVersion(ours, theirs []VersionNumber) (VersionNumber, bool) {
   143  	for _, ourVer := range ours {
   144  		for _, theirVer := range theirs {
   145  			if ourVer == theirVer {
   146  				return ourVer, true
   147  			}
   148  		}
   149  	}
   150  	return 0, false
   151  }
   152  
   153  // generateReservedVersion generates a reserved version number (v & 0x0f0f0f0f == 0x0a0a0a0a)
   154  func generateReservedVersion() VersionNumber {
   155  	b := make([]byte, 4)
   156  	_, _ = rand.Read(b) // ignore the error here. Failure to read random data doesn't break anything
   157  	return VersionNumber((binary.BigEndian.Uint32(b) | 0x0a0a0a0a) & 0xfafafafa)
   158  }
   159  
   160  // GetGreasedVersions adds one reserved version number to a slice of version numbers, at a random position
   161  func GetGreasedVersions(supported []VersionNumber) []VersionNumber {
   162  	b := make([]byte, 1)
   163  	_, _ = rand.Read(b) // ignore the error here. Failure to read random data doesn't break anything
   164  	randPos := int(b[0]) % (len(supported) + 1)
   165  	greased := make([]VersionNumber, len(supported)+1)
   166  	copy(greased, supported[:randPos])
   167  	greased[randPos] = generateReservedVersion()
   168  	copy(greased[randPos+1:], supported[randPos:])
   169  	return greased
   170  }
   171  
   172  // StripGreasedVersions strips all greased versions from a slice of versions
   173  func StripGreasedVersions(versions []VersionNumber) []VersionNumber {
   174  	realVersions := make([]VersionNumber, 0, len(versions))
   175  	for _, v := range versions {
   176  		if v&0x0f0f0f0f != 0x0a0a0a0a {
   177  			realVersions = append(realVersions, v)
   178  		}
   179  	}
   180  	return realVersions
   181  }