|
@@ -5,95 +5,99 @@ import (
|
|
"context"
|
|
"context"
|
|
"crypto"
|
|
"crypto"
|
|
"crypto/aes"
|
|
"crypto/aes"
|
|
|
|
+ "crypto/tls"
|
|
"encoding/binary"
|
|
"encoding/binary"
|
|
"io"
|
|
"io"
|
|
"os"
|
|
"os"
|
|
|
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
"github.com/sagernet/sing-box/adapter"
|
|
|
|
+ "github.com/sagernet/sing-box/common/ja3"
|
|
"github.com/sagernet/sing-box/common/sniff/internal/qtls"
|
|
"github.com/sagernet/sing-box/common/sniff/internal/qtls"
|
|
C "github.com/sagernet/sing-box/constant"
|
|
C "github.com/sagernet/sing-box/constant"
|
|
|
|
+ "github.com/sagernet/sing/common/buf"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
|
|
|
|
"golang.org/x/crypto/hkdf"
|
|
"golang.org/x/crypto/hkdf"
|
|
)
|
|
)
|
|
|
|
|
|
-func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContext, error) {
|
|
|
|
- reader := bytes.NewReader(packet)
|
|
|
|
|
|
+var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection")
|
|
|
|
|
|
|
|
+func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
|
|
|
+ reader := bytes.NewReader(packet)
|
|
typeByte, err := reader.ReadByte()
|
|
typeByte, err := reader.ReadByte()
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
if typeByte&0x40 == 0 {
|
|
if typeByte&0x40 == 0 {
|
|
- return nil, E.New("bad type byte")
|
|
|
|
|
|
+ return E.New("bad type byte")
|
|
}
|
|
}
|
|
var versionNumber uint32
|
|
var versionNumber uint32
|
|
err = binary.Read(reader, binary.BigEndian, &versionNumber)
|
|
err = binary.Read(reader, binary.BigEndian, &versionNumber)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
if versionNumber != qtls.VersionDraft29 && versionNumber != qtls.Version1 && versionNumber != qtls.Version2 {
|
|
if versionNumber != qtls.VersionDraft29 && versionNumber != qtls.Version1 && versionNumber != qtls.Version2 {
|
|
- return nil, E.New("bad version")
|
|
|
|
|
|
+ return E.New("bad version")
|
|
}
|
|
}
|
|
packetType := (typeByte & 0x30) >> 4
|
|
packetType := (typeByte & 0x30) >> 4
|
|
if packetType == 0 && versionNumber == qtls.Version2 || packetType == 2 && versionNumber != qtls.Version2 || packetType > 2 {
|
|
if packetType == 0 && versionNumber == qtls.Version2 || packetType == 2 && versionNumber != qtls.Version2 || packetType > 2 {
|
|
- return nil, E.New("bad packet type")
|
|
|
|
|
|
+ return E.New("bad packet type")
|
|
}
|
|
}
|
|
|
|
|
|
destConnIDLen, err := reader.ReadByte()
|
|
destConnIDLen, err := reader.ReadByte()
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
if destConnIDLen == 0 || destConnIDLen > 20 {
|
|
if destConnIDLen == 0 || destConnIDLen > 20 {
|
|
- return nil, E.New("bad destination connection id length")
|
|
|
|
|
|
+ return E.New("bad destination connection id length")
|
|
}
|
|
}
|
|
|
|
|
|
destConnID := make([]byte, destConnIDLen)
|
|
destConnID := make([]byte, destConnIDLen)
|
|
_, err = io.ReadFull(reader, destConnID)
|
|
_, err = io.ReadFull(reader, destConnID)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
srcConnIDLen, err := reader.ReadByte()
|
|
srcConnIDLen, err := reader.ReadByte()
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
_, err = io.CopyN(io.Discard, reader, int64(srcConnIDLen))
|
|
_, err = io.CopyN(io.Discard, reader, int64(srcConnIDLen))
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
tokenLen, err := qtls.ReadUvarint(reader)
|
|
tokenLen, err := qtls.ReadUvarint(reader)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
_, err = io.CopyN(io.Discard, reader, int64(tokenLen))
|
|
_, err = io.CopyN(io.Discard, reader, int64(tokenLen))
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
packetLen, err := qtls.ReadUvarint(reader)
|
|
packetLen, err := qtls.ReadUvarint(reader)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
hdrLen := int(reader.Size()) - reader.Len()
|
|
hdrLen := int(reader.Size()) - reader.Len()
|
|
if hdrLen+int(packetLen) > len(packet) {
|
|
if hdrLen+int(packetLen) > len(packet) {
|
|
- return nil, os.ErrInvalid
|
|
|
|
|
|
+ return os.ErrInvalid
|
|
}
|
|
}
|
|
|
|
|
|
_, err = io.CopyN(io.Discard, reader, 4)
|
|
_, err = io.CopyN(io.Discard, reader, 4)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
pnBytes := make([]byte, aes.BlockSize)
|
|
pnBytes := make([]byte, aes.BlockSize)
|
|
_, err = io.ReadFull(reader, pnBytes)
|
|
_, err = io.ReadFull(reader, pnBytes)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
var salt []byte
|
|
var salt []byte
|
|
@@ -117,7 +121,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|
hpKey := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, hkdfHeaderProtectionLabel, 16)
|
|
hpKey := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, hkdfHeaderProtectionLabel, 16)
|
|
block, err := aes.NewCipher(hpKey)
|
|
block, err := aes.NewCipher(hpKey)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
mask := make([]byte, aes.BlockSize)
|
|
mask := make([]byte, aes.BlockSize)
|
|
block.Encrypt(mask, pnBytes)
|
|
block.Encrypt(mask, pnBytes)
|
|
@@ -129,7 +133,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|
}
|
|
}
|
|
packetNumberLength := newPacket[0]&0x3 + 1
|
|
packetNumberLength := newPacket[0]&0x3 + 1
|
|
if hdrLen+int(packetNumberLength) > int(packetLen)+hdrLen {
|
|
if hdrLen+int(packetNumberLength) > int(packetLen)+hdrLen {
|
|
- return nil, os.ErrInvalid
|
|
|
|
|
|
+ return os.ErrInvalid
|
|
}
|
|
}
|
|
var packetNumber uint32
|
|
var packetNumber uint32
|
|
switch packetNumberLength {
|
|
switch packetNumberLength {
|
|
@@ -142,7 +146,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|
case 4:
|
|
case 4:
|
|
packetNumber = binary.BigEndian.Uint32(newPacket[hdrLen:])
|
|
packetNumber = binary.BigEndian.Uint32(newPacket[hdrLen:])
|
|
default:
|
|
default:
|
|
- return nil, E.New("bad packet number length")
|
|
|
|
|
|
+ return E.New("bad packet number length")
|
|
}
|
|
}
|
|
extHdrLen := hdrLen + int(packetNumberLength)
|
|
extHdrLen := hdrLen + int(packetNumberLength)
|
|
copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:])
|
|
copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:])
|
|
@@ -166,138 +170,208 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex
|
|
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))
|
|
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))
|
|
decrypted, err := cipher.Open(newPacket[extHdrLen:extHdrLen], nonce, data, newPacket[:extHdrLen])
|
|
decrypted, err := cipher.Open(newPacket[extHdrLen:extHdrLen], nonce, data, newPacket[:extHdrLen])
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
var frameType byte
|
|
var frameType byte
|
|
- var frameLen uint64
|
|
|
|
- var fragments []struct {
|
|
|
|
- offset uint64
|
|
|
|
- length uint64
|
|
|
|
- payload []byte
|
|
|
|
- }
|
|
|
|
|
|
+ var fragments []qCryptoFragment
|
|
decryptedReader := bytes.NewReader(decrypted)
|
|
decryptedReader := bytes.NewReader(decrypted)
|
|
|
|
+ const (
|
|
|
|
+ frameTypePadding = 0x00
|
|
|
|
+ frameTypePing = 0x01
|
|
|
|
+ frameTypeAck = 0x02
|
|
|
|
+ frameTypeAck2 = 0x03
|
|
|
|
+ frameTypeCrypto = 0x06
|
|
|
|
+ frameTypeConnectionClose = 0x1c
|
|
|
|
+ )
|
|
|
|
+ var frameTypeList []uint8
|
|
for {
|
|
for {
|
|
frameType, err = decryptedReader.ReadByte()
|
|
frameType, err = decryptedReader.ReadByte()
|
|
if err == io.EOF {
|
|
if err == io.EOF {
|
|
break
|
|
break
|
|
}
|
|
}
|
|
|
|
+ frameTypeList = append(frameTypeList, frameType)
|
|
switch frameType {
|
|
switch frameType {
|
|
- case 0x00: // PADDING
|
|
|
|
|
|
+ case frameTypePadding:
|
|
continue
|
|
continue
|
|
- case 0x01: // PING
|
|
|
|
|
|
+ case frameTypePing:
|
|
continue
|
|
continue
|
|
- case 0x02, 0x03: // ACK
|
|
|
|
|
|
+ case frameTypeAck, frameTypeAck2:
|
|
_, err = qtls.ReadUvarint(decryptedReader) // Largest Acknowledged
|
|
_, err = qtls.ReadUvarint(decryptedReader) // Largest Acknowledged
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ACK Delay
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ACK Delay
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
ackRangeCount, err := qtls.ReadUvarint(decryptedReader) // ACK Range Count
|
|
ackRangeCount, err := qtls.ReadUvarint(decryptedReader) // ACK Range Count
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
_, err = qtls.ReadUvarint(decryptedReader) // First ACK Range
|
|
_, err = qtls.ReadUvarint(decryptedReader) // First ACK Range
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
for i := 0; i < int(ackRangeCount); i++ {
|
|
for i := 0; i < int(ackRangeCount); i++ {
|
|
_, err = qtls.ReadUvarint(decryptedReader) // Gap
|
|
_, err = qtls.ReadUvarint(decryptedReader) // Gap
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ACK Range Length
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ACK Range Length
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if frameType == 0x03 {
|
|
if frameType == 0x03 {
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ECT0 Count
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ECT0 Count
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ECT1 Count
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ECT1 Count
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ECN-CE Count
|
|
_, err = qtls.ReadUvarint(decryptedReader) // ECN-CE Count
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- case 0x06: // CRYPTO
|
|
|
|
|
|
+ case frameTypeCrypto:
|
|
var offset uint64
|
|
var offset uint64
|
|
offset, err = qtls.ReadUvarint(decryptedReader)
|
|
offset, err = qtls.ReadUvarint(decryptedReader)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
var length uint64
|
|
var length uint64
|
|
length, err = qtls.ReadUvarint(decryptedReader)
|
|
length, err = qtls.ReadUvarint(decryptedReader)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
index := len(decrypted) - decryptedReader.Len()
|
|
index := len(decrypted) - decryptedReader.Len()
|
|
- fragments = append(fragments, struct {
|
|
|
|
- offset uint64
|
|
|
|
- length uint64
|
|
|
|
- payload []byte
|
|
|
|
- }{offset, length, decrypted[index : index+int(length)]})
|
|
|
|
- frameLen += length
|
|
|
|
|
|
+ fragments = append(fragments, qCryptoFragment{offset, length, decrypted[index : index+int(length)]})
|
|
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent)
|
|
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
- case 0x1c: // CONNECTION_CLOSE
|
|
|
|
|
|
+ case frameTypeConnectionClose:
|
|
_, err = qtls.ReadUvarint(decryptedReader) // Error Code
|
|
_, err = qtls.ReadUvarint(decryptedReader) // Error Code
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
_, err = qtls.ReadUvarint(decryptedReader) // Frame Type
|
|
_, err = qtls.ReadUvarint(decryptedReader) // Frame Type
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
var length uint64
|
|
var length uint64
|
|
length, err = qtls.ReadUvarint(decryptedReader) // Reason Phrase Length
|
|
length, err = qtls.ReadUvarint(decryptedReader) // Reason Phrase Length
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent) // Reason Phrase
|
|
_, err = decryptedReader.Seek(int64(length), io.SeekCurrent) // Reason Phrase
|
|
if err != nil {
|
|
if err != nil {
|
|
- return nil, err
|
|
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
default:
|
|
default:
|
|
- return nil, os.ErrInvalid
|
|
|
|
|
|
+ return os.ErrInvalid
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- tlsHdr := make([]byte, 5)
|
|
|
|
- tlsHdr[0] = 0x16
|
|
|
|
- binary.BigEndian.PutUint16(tlsHdr[1:], uint16(0x0303))
|
|
|
|
- binary.BigEndian.PutUint16(tlsHdr[3:], uint16(frameLen))
|
|
|
|
|
|
+ if metadata.SniffContext != nil {
|
|
|
|
+ fragments = append(fragments, metadata.SniffContext.([]qCryptoFragment)...)
|
|
|
|
+ metadata.SniffContext = nil
|
|
|
|
+ }
|
|
|
|
+ var frameLen uint64
|
|
|
|
+ for _, fragment := range fragments {
|
|
|
|
+ frameLen += fragment.length
|
|
|
|
+ }
|
|
|
|
+ buffer := buf.NewSize(5 + int(frameLen))
|
|
|
|
+ defer buffer.Release()
|
|
|
|
+ buffer.WriteByte(0x16)
|
|
|
|
+ binary.Write(buffer, binary.BigEndian, uint16(0x0303))
|
|
|
|
+ binary.Write(buffer, binary.BigEndian, uint16(frameLen))
|
|
var index uint64
|
|
var index uint64
|
|
var length int
|
|
var length int
|
|
- var readers []io.Reader
|
|
|
|
- readers = append(readers, bytes.NewReader(tlsHdr))
|
|
|
|
find:
|
|
find:
|
|
for {
|
|
for {
|
|
for _, fragment := range fragments {
|
|
for _, fragment := range fragments {
|
|
if fragment.offset == index {
|
|
if fragment.offset == index {
|
|
- readers = append(readers, bytes.NewReader(fragment.payload))
|
|
|
|
|
|
+ buffer.Write(fragment.payload)
|
|
index = fragment.offset + fragment.length
|
|
index = fragment.offset + fragment.length
|
|
length++
|
|
length++
|
|
continue find
|
|
continue find
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- if length == len(fragments) {
|
|
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ metadata.Protocol = C.ProtocolQUIC
|
|
|
|
+ fingerprint, err := ja3.Compute(buffer.Bytes())
|
|
|
|
+ if err != nil {
|
|
|
|
+ metadata.Protocol = C.ProtocolQUIC
|
|
|
|
+ metadata.Client = C.ClientChromium
|
|
|
|
+ metadata.SniffContext = fragments
|
|
|
|
+ return ErrClientHelloFragmented
|
|
|
|
+ }
|
|
|
|
+ metadata.Domain = fingerprint.ServerName
|
|
|
|
+ for metadata.Client == "" {
|
|
|
|
+ if len(frameTypeList) == 1 {
|
|
|
|
+ metadata.Client = C.ClientFirefox
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ if frameTypeList[0] == frameTypeCrypto && isZero(frameTypeList[1:]) {
|
|
|
|
+ if len(fingerprint.Versions) == 2 && fingerprint.Versions[0]&ja3.GreaseBitmask == 0x0A0A &&
|
|
|
|
+ len(fingerprint.EllipticCurves) == 5 && fingerprint.EllipticCurves[0]&ja3.GreaseBitmask == 0x0A0A {
|
|
|
|
+ metadata.Client = C.ClientSafari
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ if len(fingerprint.CipherSuites) == 1 && fingerprint.CipherSuites[0] == tls.TLS_AES_256_GCM_SHA384 &&
|
|
|
|
+ len(fingerprint.EllipticCurves) == 1 && fingerprint.EllipticCurves[0] == uint16(tls.X25519) &&
|
|
|
|
+ len(fingerprint.SignatureAlgorithms) == 1 && fingerprint.SignatureAlgorithms[0] == uint16(tls.ECDSAWithP256AndSHA256) {
|
|
|
|
+ metadata.Client = C.ClientSafari
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if frameTypeList[len(frameTypeList)-1] == frameTypeCrypto && isZero(frameTypeList[:len(frameTypeList)-1]) {
|
|
|
|
+ metadata.Client = C.ClientQUICGo
|
|
break
|
|
break
|
|
}
|
|
}
|
|
- return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, E.New("bad fragments")
|
|
|
|
|
|
+
|
|
|
|
+ if count(frameTypeList, frameTypeCrypto) > 1 || count(frameTypeList, frameTypePing) > 0 {
|
|
|
|
+ if maybeUQUIC(fingerprint) {
|
|
|
|
+ metadata.Client = C.ClientQUICGo
|
|
|
|
+ } else {
|
|
|
|
+ metadata.Client = C.ClientChromium
|
|
|
|
+ }
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ metadata.Client = C.ClientUnknown
|
|
|
|
+ //nolint:staticcheck
|
|
|
|
+ break
|
|
}
|
|
}
|
|
- metadata, err := TLSClientHello(ctx, io.MultiReader(readers...))
|
|
|
|
- if err != nil {
|
|
|
|
- return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err
|
|
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func isZero(slices []uint8) bool {
|
|
|
|
+ for _, slice := range slices {
|
|
|
|
+ if slice != 0 {
|
|
|
|
+ return false
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- metadata.Protocol = C.ProtocolQUIC
|
|
|
|
- return metadata, nil
|
|
|
|
|
|
+ return true
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func count(slices []uint8, value uint8) int {
|
|
|
|
+ var times int
|
|
|
|
+ for _, slice := range slices {
|
|
|
|
+ if slice == value {
|
|
|
|
+ times++
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return times
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type qCryptoFragment struct {
|
|
|
|
+ offset uint64
|
|
|
|
+ length uint64
|
|
|
|
+ payload []byte
|
|
}
|
|
}
|