package tls
import (
"context"
"errors"
"fmt"
)
type QUICEncryptionLevel int
const (
QUICEncryptionLevelInitial = QUICEncryptionLevel (iota )
QUICEncryptionLevelEarly
QUICEncryptionLevelHandshake
QUICEncryptionLevelApplication
)
func (l QUICEncryptionLevel ) String () string {
switch l {
case QUICEncryptionLevelInitial :
return "Initial"
case QUICEncryptionLevelEarly :
return "Early"
case QUICEncryptionLevelHandshake :
return "Handshake"
case QUICEncryptionLevelApplication :
return "Application"
default :
return fmt .Sprintf ("QUICEncryptionLevel(%v)" , int (l ))
}
}
type QUICConn struct {
conn *Conn
sessionTicketSent bool
}
type QUICConfig struct {
TLSConfig *Config
EnableSessionEvents bool
}
type QUICEventKind int
const (
QUICNoEvent QUICEventKind = iota
QUICSetReadSecret
QUICSetWriteSecret
QUICWriteData
QUICTransportParameters
QUICTransportParametersRequired
QUICRejectedEarlyData
QUICHandshakeDone
QUICResumeSession
QUICStoreSession
)
type QUICEvent struct {
Kind QUICEventKind
Level QUICEncryptionLevel
Data []byte
Suite uint16
SessionState *SessionState
}
type quicState struct {
events []QUICEvent
nextEvent int
eventArr [8 ]QUICEvent
started bool
signalc chan struct {}
blockedc chan struct {}
cancelc <-chan struct {}
cancel context .CancelFunc
waitingForDrain bool
readbuf []byte
transportParams []byte
enableSessionEvents bool
}
func QUICClient (config *QUICConfig ) *QUICConn {
return newQUICConn (Client (nil , config .TLSConfig ), config )
}
func QUICServer (config *QUICConfig ) *QUICConn {
return newQUICConn (Server (nil , config .TLSConfig ), config )
}
func newQUICConn(conn *Conn , config *QUICConfig ) *QUICConn {
conn .quic = &quicState {
signalc : make (chan struct {}),
blockedc : make (chan struct {}),
enableSessionEvents : config .EnableSessionEvents ,
}
conn .quic .events = conn .quic .eventArr [:0 ]
return &QUICConn {
conn : conn ,
}
}
func (q *QUICConn ) Start (ctx context .Context ) error {
if q .conn .quic .started {
return quicError (errors .New ("tls: Start called more than once" ))
}
q .conn .quic .started = true
if q .conn .config .MinVersion < VersionTLS13 {
return quicError (errors .New ("tls: Config MinVersion must be at least TLS 1.13" ))
}
go q .conn .HandshakeContext (ctx )
if _ , ok := <-q .conn .quic .blockedc ; !ok {
return q .conn .handshakeErr
}
return nil
}
func (q *QUICConn ) NextEvent () QUICEvent {
qs := q .conn .quic
if last := qs .nextEvent - 1 ; last >= 0 && len (qs .events [last ].Data ) > 0 {
qs .events [last ].Data [0 ] = 0
}
if qs .nextEvent >= len (qs .events ) && qs .waitingForDrain {
qs .waitingForDrain = false
<-qs .signalc
<-qs .blockedc
}
if qs .nextEvent >= len (qs .events ) {
qs .events = qs .events [:0 ]
qs .nextEvent = 0
return QUICEvent {Kind : QUICNoEvent }
}
e := qs .events [qs .nextEvent ]
qs .events [qs .nextEvent ] = QUICEvent {}
qs .nextEvent ++
return e
}
func (q *QUICConn ) Close () error {
if q .conn .quic .cancel == nil {
return nil
}
q .conn .quic .cancel ()
for range q .conn .quic .blockedc {
}
return q .conn .handshakeErr
}
func (q *QUICConn ) HandleData (level QUICEncryptionLevel , data []byte ) error {
c := q .conn
if c .in .level != level {
return quicError (c .in .setErrorLocked (errors .New ("tls: handshake data received at wrong level" )))
}
c .quic .readbuf = data
<-c .quic .signalc
_ , ok := <-c .quic .blockedc
if ok {
return nil
}
c .handshakeMutex .Lock ()
defer c .handshakeMutex .Unlock ()
c .hand .Write (c .quic .readbuf )
c .quic .readbuf = nil
for q .conn .hand .Len () >= 4 && q .conn .handshakeErr == nil {
b := q .conn .hand .Bytes ()
n := int (b [1 ])<<16 | int (b [2 ])<<8 | int (b [3 ])
if n > maxHandshake {
q .conn .handshakeErr = fmt .Errorf ("tls: handshake message of length %d bytes exceeds maximum of %d bytes" , n , maxHandshake )
break
}
if len (b ) < 4 +n {
return nil
}
if err := q .conn .handlePostHandshakeMessage (); err != nil {
q .conn .handshakeErr = err
}
}
if q .conn .handshakeErr != nil {
return quicError (q .conn .handshakeErr )
}
return nil
}
type QUICSessionTicketOptions struct {
EarlyData bool
Extra [][]byte
}
func (q *QUICConn ) SendSessionTicket (opts QUICSessionTicketOptions ) error {
c := q .conn
if !c .isHandshakeComplete .Load () {
return quicError (errors .New ("tls: SendSessionTicket called before handshake completed" ))
}
if c .isClient {
return quicError (errors .New ("tls: SendSessionTicket called on the client" ))
}
if q .sessionTicketSent {
return quicError (errors .New ("tls: SendSessionTicket called multiple times" ))
}
q .sessionTicketSent = true
return quicError (c .sendSessionTicket (opts .EarlyData , opts .Extra ))
}
func (q *QUICConn ) StoreSession (session *SessionState ) error {
c := q .conn
if !c .isClient {
return quicError (errors .New ("tls: StoreSessionTicket called on the server" ))
}
cacheKey := c .clientSessionCacheKey ()
if cacheKey == "" {
return nil
}
cs := &ClientSessionState {session : session }
c .config .ClientSessionCache .Put (cacheKey , cs )
return nil
}
func (q *QUICConn ) ConnectionState () ConnectionState {
return q .conn .ConnectionState ()
}
func (q *QUICConn ) SetTransportParameters (params []byte ) {
if params == nil {
params = []byte {}
}
q .conn .quic .transportParams = params
if q .conn .quic .started {
<-q .conn .quic .signalc
<-q .conn .quic .blockedc
}
}
func quicError(err error ) error {
if err == nil {
return nil
}
var ae AlertError
if errors .As (err , &ae ) {
return err
}
var a alert
if !errors .As (err , &a ) {
a = alertInternalError
}
return fmt .Errorf ("%w%.0w" , err , AlertError (a ))
}
func (c *Conn ) quicReadHandshakeBytes (n int ) error {
for c .hand .Len () < n {
if err := c .quicWaitForSignal (); err != nil {
return err
}
}
return nil
}
func (c *Conn ) quicSetReadSecret (level QUICEncryptionLevel , suite uint16 , secret []byte ) {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICSetReadSecret ,
Level : level ,
Suite : suite ,
Data : secret ,
})
}
func (c *Conn ) quicSetWriteSecret (level QUICEncryptionLevel , suite uint16 , secret []byte ) {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICSetWriteSecret ,
Level : level ,
Suite : suite ,
Data : secret ,
})
}
func (c *Conn ) quicWriteCryptoData (level QUICEncryptionLevel , data []byte ) {
var last *QUICEvent
if len (c .quic .events ) > 0 {
last = &c .quic .events [len (c .quic .events )-1 ]
}
if last == nil || last .Kind != QUICWriteData || last .Level != level {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICWriteData ,
Level : level ,
})
last = &c .quic .events [len (c .quic .events )-1 ]
}
last .Data = append (last .Data , data ...)
}
func (c *Conn ) quicResumeSession (session *SessionState ) error {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICResumeSession ,
SessionState : session ,
})
c .quic .waitingForDrain = true
for c .quic .waitingForDrain {
if err := c .quicWaitForSignal (); err != nil {
return err
}
}
return nil
}
func (c *Conn ) quicStoreSession (session *SessionState ) {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICStoreSession ,
SessionState : session ,
})
}
func (c *Conn ) quicSetTransportParameters (params []byte ) {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICTransportParameters ,
Data : params ,
})
}
func (c *Conn ) quicGetTransportParameters () ([]byte , error ) {
if c .quic .transportParams == nil {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICTransportParametersRequired ,
})
}
for c .quic .transportParams == nil {
if err := c .quicWaitForSignal (); err != nil {
return nil , err
}
}
return c .quic .transportParams , nil
}
func (c *Conn ) quicHandshakeComplete () {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICHandshakeDone ,
})
}
func (c *Conn ) quicRejectedEarlyData () {
c .quic .events = append (c .quic .events , QUICEvent {
Kind : QUICRejectedEarlyData ,
})
}
func (c *Conn ) quicWaitForSignal () error {
c .handshakeMutex .Unlock ()
defer c .handshakeMutex .Lock ()
select {
case c .quic .blockedc <- struct {}{}:
case <- c .quic .cancelc :
return c .sendAlertLocked (alertCloseNotify )
}
select {
case c .quic .signalc <- struct {}{}:
c .hand .Write (c .quic .readbuf )
c .quic .readbuf = nil
case <- c .quic .cancelc :
return c .sendAlertLocked (alertCloseNotify )
}
return nil
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .