uquic/internal/handshake/client_session_cache.go

118 lines
3.4 KiB
Go

package handshake
import (
"bytes"
"crypto/tls"
"io"
"time"
"unsafe"
"github.com/marten-seemann/qtls"
"github.com/lucas-clemente/quic-go/internal/congestion"
"github.com/lucas-clemente/quic-go/internal/utils"
)
const clientSessionStateRevision = 1
type clientSessionCache struct {
tls.ClientSessionCache
rttStats *congestion.RTTStats
getAppData func() []byte
setAppData func([]byte)
}
func newClientSessionCache(
cache tls.ClientSessionCache,
rttStats *congestion.RTTStats,
get func() []byte,
set func([]byte),
) *clientSessionCache {
return &clientSessionCache{
ClientSessionCache: cache,
rttStats: rttStats,
getAppData: get,
setAppData: set,
}
}
var _ qtls.ClientSessionCache = &clientSessionCache{}
func (c *clientSessionCache) Get(sessionKey string) (*qtls.ClientSessionState, bool) {
sess, ok := c.ClientSessionCache.Get(sessionKey)
if sess == nil {
return nil, ok
}
// qtls.ClientSessionState is identical to the tls.ClientSessionState.
// In order to allow users of quic-go to use a tls.Config,
// we need this workaround to use the ClientSessionCache.
// In unsafe.go we check that the two structs are actually identical.
tlsSessBytes := (*[unsafe.Sizeof(*sess)]byte)(unsafe.Pointer(sess))[:]
var session clientSessionState
sessBytes := (*[unsafe.Sizeof(session)]byte)(unsafe.Pointer(&session))[:]
copy(sessBytes, tlsSessBytes)
r := bytes.NewReader(session.nonce)
rev, err := utils.ReadVarInt(r)
if err != nil {
return nil, false
}
if rev != clientSessionStateRevision {
return nil, false
}
rtt, err := utils.ReadVarInt(r)
if err != nil {
return nil, false
}
appDataLen, err := utils.ReadVarInt(r)
if err != nil {
return nil, false
}
appData := make([]byte, appDataLen)
if _, err := io.ReadFull(r, appData); err != nil {
return nil, false
}
nonceLen, err := utils.ReadVarInt(r)
if err != nil {
return nil, false
}
nonce := make([]byte, nonceLen)
if _, err := io.ReadFull(r, nonce); err != nil {
return nil, false
}
c.setAppData(appData)
session.nonce = nonce
c.rttStats.SetInitialRTT(time.Duration(rtt) * time.Microsecond)
var qtlsSession qtls.ClientSessionState
qtlsSessBytes := (*[unsafe.Sizeof(qtlsSession)]byte)(unsafe.Pointer(&qtlsSession))[:]
copy(qtlsSessBytes, sessBytes)
return &qtlsSession, ok
}
func (c *clientSessionCache) Put(sessionKey string, cs *qtls.ClientSessionState) {
if cs == nil {
c.ClientSessionCache.Put(sessionKey, nil)
return
}
// qtls.ClientSessionState is identical to the tls.ClientSessionState.
// In order to allow users of quic-go to use a tls.Config,
// we need this workaround to use the ClientSessionCache.
// In unsafe.go we check that the two structs are actually identical.
qtlsSessBytes := (*[unsafe.Sizeof(*cs)]byte)(unsafe.Pointer(cs))[:]
var session clientSessionState
sessBytes := (*[unsafe.Sizeof(session)]byte)(unsafe.Pointer(&session))[:]
copy(sessBytes, qtlsSessBytes)
appData := c.getAppData()
buf := &bytes.Buffer{}
utils.WriteVarInt(buf, clientSessionStateRevision)
utils.WriteVarInt(buf, uint64(c.rttStats.SmoothedRTT().Microseconds()))
utils.WriteVarInt(buf, uint64(len(appData)))
buf.Write(appData)
utils.WriteVarInt(buf, uint64(len(session.nonce)))
buf.Write(session.nonce)
session.nonce = buf.Bytes()
var tlsSession tls.ClientSessionState
tlsSessBytes := (*[unsafe.Sizeof(tlsSession)]byte)(unsafe.Pointer(&tlsSession))[:]
copy(tlsSessBytes, sessBytes)
c.ClientSessionCache.Put(sessionKey, &tlsSession)
}