// The application may modify the [SessionState] before storing it.
// This event only occurs on client connections.
QUICStoreSession
+
+ // QUICErrorEvent indicates that a fatal error has occurred.
+ // The handshake cannot proceed and the connection must be closed.
+ // QUICEvent.Err is set.
+ QUICErrorEvent
)
// A QUICEvent is an event occurring on a QUIC connection.
// Set for QUICResumeSession and QUICStoreSession.
SessionState *SessionState
+
+ // Set for QUICErrorEvent.
+ // The error will wrap AlertError.
+ Err error
}
type quicState struct {
cancel context.CancelFunc
waitingForDrain bool
+ errorReturned bool
// readbuf is shared between HandleData and the handshake goroutine.
// HandshakeCryptoData passes ownership to the handshake goroutine by
<-qs.signalc
<-qs.blockedc
}
+ if err := q.conn.handshakeErr; err != nil {
+ if qs.errorReturned {
+ return QUICEvent{Kind: QUICNoEvent}
+ }
+ qs.errorReturned = true
+ qs.events = nil
+ qs.nextEvent = 0
+ return QUICEvent{Kind: QUICErrorEvent, Err: q.conn.handshakeErr}
+ }
if qs.nextEvent >= len(qs.events) {
qs.events = qs.events[:0]
qs.nextEvent = 0
"bytes"
"context"
"errors"
+ "fmt"
"reflect"
"strings"
"testing"
ticketOpts QUICSessionTicketOptions
onResumeSession func(*SessionState)
gotParams []byte
+ gotError error
earlyDataRejected bool
complete bool
}
if onEvent != nil && onEvent(e, a, b) {
continue
}
+ if a.gotError != nil && e.Kind != QUICNoEvent {
+ return fmt.Errorf("unexpected event %v after QUICErrorEvent", e.Kind)
+ }
switch e.Kind {
case QUICNoEvent:
idleCount++
}
case QUICRejectedEarlyData:
a.earlyDataRejected = true
+ case QUICErrorEvent:
+ if e.Err == nil {
+ return errors.New("unexpected QUICErrorEvent with no Err")
+ }
+ a.gotError = e.Err
}
if e.Kind != QUICNoEvent {
idleCount = 0
if _, ok := errors.AsType[*CertificateVerificationError](err); !ok {
t.Errorf("connection handshake terminated with error %q, want CertificateVerificationError", err)
}
+
+ ev := cli.conn.NextEvent()
+ if ev.Kind != QUICErrorEvent {
+ t.Errorf("client.NextEvent: no QUICErrorEvent, want one")
+ }
+ if ev.Err != err {
+ t.Errorf("client.NextEvent: want same error returned by Start, got %v", ev.Err)
+ }
+}
+
+// Test that we can report an error produced by the GetEncryptedClientHelloKeys function.
+func TestQUICECHKeyError(t *testing.T) {
+ getECHKeysError := errors.New("error returned by GetEncryptedClientHelloKeys")
+ config := &QUICConfig{TLSConfig: testConfig.Clone()}
+ config.TLSConfig.MinVersion = VersionTLS13
+ config.TLSConfig.NextProtos = []string{"h3"}
+ config.TLSConfig.GetEncryptedClientHelloKeys = func(*ClientHelloInfo) ([]EncryptedClientHelloKey, error) {
+ return nil, getECHKeysError
+ }
+ cli := newTestQUICClient(t, config)
+ cli.conn.SetTransportParameters(nil)
+ srv := newTestQUICServer(t, config)
+
+ if err := runTestQUICConnection(context.Background(), cli, srv, nil); err != errTransportParametersRequired {
+ t.Fatalf("handshake with no client parameters: %v; want errTransportParametersRequired", err)
+ }
+ srv.conn.SetTransportParameters(nil)
+ if err := runTestQUICConnection(context.Background(), cli, srv, nil); err == nil {
+ t.Fatalf("handshake with GetEncryptedClientHelloKeys errors: nil, want error")
+ }
+ if srv.gotError == nil {
+ t.Fatalf("after GetEncryptedClientHelloKeys error, server did not see QUICErrorEvent")
+ }
+ if _, ok := errors.AsType[AlertError](srv.gotError); !ok {
+ t.Errorf("connection handshake terminated with error %T, want AlertError", srv.gotError)
+ }
+ if !errors.Is(srv.gotError, getECHKeysError) {
+ t.Errorf("connection handshake terminated with error %v, want error returned by GetEncryptedClientHelloKeys", srv.gotError)
+ }
}
// Test that QUICConn.ConnectionState can be used during the handshake,