| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | // Copyright 2014 The Go Authors. All rights reserved. | 
					
						
							|  |  |  | // Use of this source code is governed by a BSD-style | 
					
						
							|  |  |  | // license that can be found in the LICENSE file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Package http2 implements the HTTP/2 protocol. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This package is low-level and intended to be used directly by very | 
					
						
							|  |  |  | // few people. Most users will use it indirectly through the automatic | 
					
						
							|  |  |  | // use by the net/http package (from Go 1.6 and later). | 
					
						
							|  |  |  | // For use in earlier Go versions see ConfigureServer. (Transport support | 
					
						
							|  |  |  | // requires Go 1.6 or later) | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // See https://http2.github.io/ for more information on HTTP/2. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // See https://http2.golang.org/ for a test server running this code. | 
					
						
							|  |  |  | package http2 // import "golang.org/x/net/http2" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bufio" | 
					
						
							| 
									
										
										
										
											2024-06-10 07:40:53 +00:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | 	"crypto/tls" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2024-06-10 07:40:53 +00:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2022-09-28 18:30:40 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"golang.org/x/net/http/httpguts" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	VerboseLogs    bool | 
					
						
							|  |  |  | 	logFrameWrites bool | 
					
						
							|  |  |  | 	logFrameReads  bool | 
					
						
							|  |  |  | 	inTests        bool | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func init() { | 
					
						
							|  |  |  | 	e := os.Getenv("GODEBUG") | 
					
						
							|  |  |  | 	if strings.Contains(e, "http2debug=1") { | 
					
						
							|  |  |  | 		VerboseLogs = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if strings.Contains(e, "http2debug=2") { | 
					
						
							|  |  |  | 		VerboseLogs = true | 
					
						
							|  |  |  | 		logFrameWrites = true | 
					
						
							|  |  |  | 		logFrameReads = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	// ClientPreface is the string that must be sent by new | 
					
						
							|  |  |  | 	// connections from clients. | 
					
						
							|  |  |  | 	ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// SETTINGS_MAX_FRAME_SIZE default | 
					
						
							|  |  |  | 	// https://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2 | 
					
						
							|  |  |  | 	initialMaxFrameSize = 16384 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// NextProtoTLS is the NPN/ALPN protocol negotiated during | 
					
						
							|  |  |  | 	// HTTP/2's TLS setup. | 
					
						
							|  |  |  | 	NextProtoTLS = "h2" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// https://httpwg.org/specs/rfc7540.html#SettingValues | 
					
						
							|  |  |  | 	initialHeaderTableSize = 4096 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	initialWindowSize = 65535 // 6.9.2 Initial Flow Control Window Size | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defaultMaxReadFrameSize = 1 << 20 | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	clientPreface = []byte(ClientPreface) | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type streamState int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // HTTP/2 stream states. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // See http://tools.ietf.org/html/rfc7540#section-5.1. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // For simplicity, the server code merges "reserved (local)" into | 
					
						
							|  |  |  | // "half-closed (remote)". This is one less state transition to track. | 
					
						
							|  |  |  | // The only downside is that we send PUSH_PROMISEs slightly less | 
					
						
							|  |  |  | // liberally than allowable. More discussion here: | 
					
						
							|  |  |  | // https://lists.w3.org/Archives/Public/ietf-http-wg/2016JulSep/0599.html | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // "reserved (remote)" is omitted since the client code does not | 
					
						
							|  |  |  | // support server push. | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	stateIdle streamState = iota | 
					
						
							|  |  |  | 	stateOpen | 
					
						
							|  |  |  | 	stateHalfClosedLocal | 
					
						
							|  |  |  | 	stateHalfClosedRemote | 
					
						
							|  |  |  | 	stateClosed | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var stateName = [...]string{ | 
					
						
							|  |  |  | 	stateIdle:             "Idle", | 
					
						
							|  |  |  | 	stateOpen:             "Open", | 
					
						
							|  |  |  | 	stateHalfClosedLocal:  "HalfClosedLocal", | 
					
						
							|  |  |  | 	stateHalfClosedRemote: "HalfClosedRemote", | 
					
						
							|  |  |  | 	stateClosed:           "Closed", | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (st streamState) String() string { | 
					
						
							|  |  |  | 	return stateName[st] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Setting is a setting parameter: which setting it is, and its value. | 
					
						
							|  |  |  | type Setting struct { | 
					
						
							|  |  |  | 	// ID is which setting is being set. | 
					
						
							|  |  |  | 	// See https://httpwg.org/specs/rfc7540.html#SettingFormat | 
					
						
							|  |  |  | 	ID SettingID | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Val is the value. | 
					
						
							|  |  |  | 	Val uint32 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s Setting) String() string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("[%v = %d]", s.ID, s.Val) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Valid reports whether the setting is valid. | 
					
						
							|  |  |  | func (s Setting) Valid() error { | 
					
						
							|  |  |  | 	// Limits and error codes from 6.5.2 Defined SETTINGS Parameters | 
					
						
							|  |  |  | 	switch s.ID { | 
					
						
							|  |  |  | 	case SettingEnablePush: | 
					
						
							|  |  |  | 		if s.Val != 1 && s.Val != 0 { | 
					
						
							|  |  |  | 			return ConnectionError(ErrCodeProtocol) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case SettingInitialWindowSize: | 
					
						
							|  |  |  | 		if s.Val > 1<<31-1 { | 
					
						
							|  |  |  | 			return ConnectionError(ErrCodeFlowControl) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case SettingMaxFrameSize: | 
					
						
							|  |  |  | 		if s.Val < 16384 || s.Val > 1<<24-1 { | 
					
						
							|  |  |  | 			return ConnectionError(ErrCodeProtocol) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // A SettingID is an HTTP/2 setting as defined in | 
					
						
							|  |  |  | // https://httpwg.org/specs/rfc7540.html#iana-settings | 
					
						
							|  |  |  | type SettingID uint16 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	SettingHeaderTableSize      SettingID = 0x1 | 
					
						
							|  |  |  | 	SettingEnablePush           SettingID = 0x2 | 
					
						
							|  |  |  | 	SettingMaxConcurrentStreams SettingID = 0x3 | 
					
						
							|  |  |  | 	SettingInitialWindowSize    SettingID = 0x4 | 
					
						
							|  |  |  | 	SettingMaxFrameSize         SettingID = 0x5 | 
					
						
							|  |  |  | 	SettingMaxHeaderListSize    SettingID = 0x6 | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var settingName = map[SettingID]string{ | 
					
						
							|  |  |  | 	SettingHeaderTableSize:      "HEADER_TABLE_SIZE", | 
					
						
							|  |  |  | 	SettingEnablePush:           "ENABLE_PUSH", | 
					
						
							|  |  |  | 	SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS", | 
					
						
							|  |  |  | 	SettingInitialWindowSize:    "INITIAL_WINDOW_SIZE", | 
					
						
							|  |  |  | 	SettingMaxFrameSize:         "MAX_FRAME_SIZE", | 
					
						
							|  |  |  | 	SettingMaxHeaderListSize:    "MAX_HEADER_LIST_SIZE", | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s SettingID) String() string { | 
					
						
							|  |  |  | 	if v, ok := settingName[s]; ok { | 
					
						
							|  |  |  | 		return v | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return fmt.Sprintf("UNKNOWN_SETTING_%d", uint16(s)) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // validWireHeaderFieldName reports whether v is a valid header field | 
					
						
							|  |  |  | // name (key). See httpguts.ValidHeaderName for the base rules. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // Further, http2 says: | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //	"Just as in HTTP/1.x, header field names are strings of ASCII | 
					
						
							|  |  |  | //	characters that are compared in a case-insensitive | 
					
						
							|  |  |  | //	fashion. However, header field names MUST be converted to | 
					
						
							|  |  |  | //	lowercase prior to their encoding in HTTP/2. " | 
					
						
							|  |  |  | func validWireHeaderFieldName(v string) bool { | 
					
						
							|  |  |  | 	if len(v) == 0 { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, r := range v { | 
					
						
							|  |  |  | 		if !httpguts.IsTokenRune(r) { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if 'A' <= r && r <= 'Z' { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func httpCodeString(code int) string { | 
					
						
							|  |  |  | 	switch code { | 
					
						
							|  |  |  | 	case 200: | 
					
						
							|  |  |  | 		return "200" | 
					
						
							|  |  |  | 	case 404: | 
					
						
							|  |  |  | 		return "404" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return strconv.Itoa(code) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // from pkg io | 
					
						
							|  |  |  | type stringWriter interface { | 
					
						
							|  |  |  | 	WriteString(s string) (n int, err error) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed). | 
					
						
							|  |  |  | type closeWaiter chan struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Init makes a closeWaiter usable. | 
					
						
							|  |  |  | // It exists because so a closeWaiter value can be placed inside a | 
					
						
							|  |  |  | // larger struct and have the Mutex and Cond's memory in the same | 
					
						
							|  |  |  | // allocation. | 
					
						
							|  |  |  | func (cw *closeWaiter) Init() { | 
					
						
							|  |  |  | 	*cw = make(chan struct{}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Close marks the closeWaiter as closed and unblocks any waiters. | 
					
						
							|  |  |  | func (cw closeWaiter) Close() { | 
					
						
							|  |  |  | 	close(cw) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Wait waits for the closeWaiter to become closed. | 
					
						
							|  |  |  | func (cw closeWaiter) Wait() { | 
					
						
							|  |  |  | 	<-cw | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // bufferedWriter is a buffered writer that writes to w. | 
					
						
							|  |  |  | // Its buffered writer is lazily allocated as needed, to minimize | 
					
						
							|  |  |  | // idle memory usage with many connections. | 
					
						
							|  |  |  | type bufferedWriter struct { | 
					
						
							|  |  |  | 	_  incomparable | 
					
						
							|  |  |  | 	w  io.Writer     // immutable | 
					
						
							|  |  |  | 	bw *bufio.Writer // non-nil when data is buffered | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func newBufferedWriter(w io.Writer) *bufferedWriter { | 
					
						
							|  |  |  | 	return &bufferedWriter{w: w} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // bufWriterPoolBufferSize is the size of bufio.Writer's | 
					
						
							|  |  |  | // buffers created using bufWriterPool. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // TODO: pick a less arbitrary value? this is a bit under | 
					
						
							|  |  |  | // (3 x typical 1500 byte MTU) at least. Other than that, | 
					
						
							|  |  |  | // not much thought went into it. | 
					
						
							|  |  |  | const bufWriterPoolBufferSize = 4 << 10 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var bufWriterPool = sync.Pool{ | 
					
						
							|  |  |  | 	New: func() interface{} { | 
					
						
							|  |  |  | 		return bufio.NewWriterSize(nil, bufWriterPoolBufferSize) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (w *bufferedWriter) Available() int { | 
					
						
							|  |  |  | 	if w.bw == nil { | 
					
						
							|  |  |  | 		return bufWriterPoolBufferSize | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return w.bw.Available() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (w *bufferedWriter) Write(p []byte) (n int, err error) { | 
					
						
							|  |  |  | 	if w.bw == nil { | 
					
						
							|  |  |  | 		bw := bufWriterPool.Get().(*bufio.Writer) | 
					
						
							|  |  |  | 		bw.Reset(w.w) | 
					
						
							|  |  |  | 		w.bw = bw | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return w.bw.Write(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (w *bufferedWriter) Flush() error { | 
					
						
							|  |  |  | 	bw := w.bw | 
					
						
							|  |  |  | 	if bw == nil { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	err := bw.Flush() | 
					
						
							|  |  |  | 	bw.Reset(nil) | 
					
						
							|  |  |  | 	bufWriterPool.Put(bw) | 
					
						
							|  |  |  | 	w.bw = nil | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func mustUint31(v int32) uint32 { | 
					
						
							|  |  |  | 	if v < 0 || v > 2147483647 { | 
					
						
							|  |  |  | 		panic("out of range") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return uint32(v) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // bodyAllowedForStatus reports whether a given response status code | 
					
						
							|  |  |  | // permits a body. See RFC 7230, section 3.3. | 
					
						
							|  |  |  | func bodyAllowedForStatus(status int) bool { | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case status >= 100 && status <= 199: | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	case status == 204: | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	case status == 304: | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type httpError struct { | 
					
						
							|  |  |  | 	_       incomparable | 
					
						
							|  |  |  | 	msg     string | 
					
						
							|  |  |  | 	timeout bool | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (e *httpError) Error() string   { return e.msg } | 
					
						
							|  |  |  | func (e *httpError) Timeout() bool   { return e.timeout } | 
					
						
							|  |  |  | func (e *httpError) Temporary() bool { return true } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var errTimeout error = &httpError{msg: "http2: timeout awaiting response headers", timeout: true} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type connectionStater interface { | 
					
						
							|  |  |  | 	ConnectionState() tls.ConnectionState | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var sorterPool = sync.Pool{New: func() interface{} { return new(sorter) }} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type sorter struct { | 
					
						
							|  |  |  | 	v []string // owned by sorter | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *sorter) Len() int           { return len(s.v) } | 
					
						
							|  |  |  | func (s *sorter) Swap(i, j int)      { s.v[i], s.v[j] = s.v[j], s.v[i] } | 
					
						
							|  |  |  | func (s *sorter) Less(i, j int) bool { return s.v[i] < s.v[j] } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Keys returns the sorted keys of h. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The returned slice is only valid until s used again or returned to | 
					
						
							|  |  |  | // its pool. | 
					
						
							|  |  |  | func (s *sorter) Keys(h http.Header) []string { | 
					
						
							|  |  |  | 	keys := s.v[:0] | 
					
						
							|  |  |  | 	for k := range h { | 
					
						
							|  |  |  | 		keys = append(keys, k) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s.v = keys | 
					
						
							|  |  |  | 	sort.Sort(s) | 
					
						
							|  |  |  | 	return keys | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *sorter) SortStrings(ss []string) { | 
					
						
							|  |  |  | 	// Our sorter works on s.v, which sorter owns, so | 
					
						
							|  |  |  | 	// stash it away while we sort the user's buffer. | 
					
						
							|  |  |  | 	save := s.v | 
					
						
							|  |  |  | 	s.v = ss | 
					
						
							|  |  |  | 	sort.Sort(s) | 
					
						
							|  |  |  | 	s.v = save | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // validPseudoPath reports whether v is a valid :path pseudo-header | 
					
						
							|  |  |  | // value. It must be either: | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | //   - a non-empty string starting with '/' | 
					
						
							|  |  |  | //   - the string '*', for OPTIONS requests. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // For now this is only used a quick check for deciding when to clean | 
					
						
							|  |  |  | // up Opaque URLs before sending requests from the Transport. | 
					
						
							|  |  |  | // See golang.org/issue/16847 | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // We used to enforce that the path also didn't start with "//", but | 
					
						
							|  |  |  | // Google's GFE accepts such paths and Chrome sends them, so ignore | 
					
						
							|  |  |  | // that part of the spec. See golang.org/issue/19103. | 
					
						
							|  |  |  | func validPseudoPath(v string) bool { | 
					
						
							|  |  |  | 	return (len(v) > 0 && v[0] == '/') || v == "*" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // incomparable is a zero-width, non-comparable type. Adding it to a struct | 
					
						
							|  |  |  | // makes that struct also non-comparable, and generally doesn't add | 
					
						
							|  |  |  | // any size (as long as it's first). | 
					
						
							|  |  |  | type incomparable [0]func() | 
					
						
							| 
									
										
										
										
											2024-06-10 07:40:53 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | // synctestGroupInterface is the methods of synctestGroup used by Server and Transport. | 
					
						
							|  |  |  | // It's defined as an interface here to let us keep synctestGroup entirely test-only | 
					
						
							|  |  |  | // and not a part of non-test builds. | 
					
						
							|  |  |  | type synctestGroupInterface interface { | 
					
						
							|  |  |  | 	Join() | 
					
						
							|  |  |  | 	Now() time.Time | 
					
						
							|  |  |  | 	NewTimer(d time.Duration) timer | 
					
						
							|  |  |  | 	AfterFunc(d time.Duration, f func()) timer | 
					
						
							|  |  |  | 	ContextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) | 
					
						
							|  |  |  | } |