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 }