]> Cypherpunks repositories - gostls13.git/commitdiff
all: update to x/net@bff14c52567061031b9761881907c39e24792736
authorDamien Neil <dneil@google.com>
Mon, 24 Nov 2025 19:32:38 +0000 (11:32 -0800)
committerGopher Robot <gobot@golang.org>
Tue, 25 Nov 2025 01:25:21 +0000 (17:25 -0800)
This brings in CL 722200 which adds necessary HTTP/2 support for
net/http.Transport.NewClientConn.

For #75772

Change-Id: I5489232401096982ed21002f293dd0f87fe2fba6
Reviewed-on: https://go-review.googlesource.com/c/go/+/723901
Reviewed-by: Nicholas Husin <nsh@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
12 files changed:
src/cmd/go.mod
src/cmd/go.sum
src/cmd/vendor/modules.txt
src/go.mod
src/go.sum
src/net/http/h2_bundle.go
src/vendor/golang.org/x/crypto/chacha20/chacha_arm64.s
src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go
src/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go
src/vendor/golang.org/x/net/dns/dnsmessage/message.go
src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go [new file with mode: 0644]
src/vendor/modules.txt

index 2f13ca0dc9690ce6feffc16783ed190ca17c6078..3915c16da33a5528994c322fb6f09a1af6ac5e70 100644 (file)
@@ -16,6 +16,6 @@ require (
 
 require (
        github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect
-       golang.org/x/text v0.30.0 // indirect
+       golang.org/x/text v0.31.0 // indirect
        rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect
 )
index d75d9339fbf2301aae95e69d967f8721c5cfc72c..100ea28a7febe975372c06c84ec7a78c90b93269 100644 (file)
@@ -20,8 +20,8 @@ golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxU
 golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
 golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
 golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
 golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 h1:BGuEUnbWU1H+VhF4Z52lwCvzRT8Q/Z7kJC3okSME58w=
 golang.org/x/tools v0.39.1-0.20251120214200-68724afed209/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
 rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
index 1c7bf37a3b4818f646dc818921335e5ce19861f1..a3c2d2017745795e414b6df035ad1ef0fefe4b2a 100644 (file)
@@ -63,7 +63,7 @@ golang.org/x/telemetry/internal/upload
 # golang.org/x/term v0.34.0
 ## explicit; go 1.23.0
 golang.org/x/term
-# golang.org/x/text v0.30.0
+# golang.org/x/text v0.31.0
 ## explicit; go 1.24.0
 golang.org/x/text/cases
 golang.org/x/text/internal
index e6cb3d5b43dbd3612ca55537347ce670f1d618cf..77ca25331ae128e43728aed9fb80cc89e686eadc 100644 (file)
@@ -3,11 +3,11 @@ module std
 go 1.26
 
 require (
-       golang.org/x/crypto v0.43.0
-       golang.org/x/net v0.46.0
+       golang.org/x/crypto v0.44.0
+       golang.org/x/net v0.47.1-0.20251124223553-bff14c525670
 )
 
 require (
        golang.org/x/sys v0.38.0 // indirect
-       golang.org/x/text v0.30.0 // indirect
+       golang.org/x/text v0.31.0 // indirect
 )
index fe184a864718afd94848cb4baa87033647588a4f..b33582c594107f429711a06aa5b95d6849713a51 100644 (file)
@@ -1,8 +1,8 @@
-golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
-golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
-golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
-golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
+golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
+golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/net v0.47.1-0.20251124223553-bff14c525670 h1:6OE5meBQStq9OFgbyv9VH3wiSVw9HDJ7GBz2L5pkhuo=
+golang.org/x/net v0.47.1-0.20251124223553-bff14c525670/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
 golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
 golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
index 0df276321cf09e7d7bf0ab4ab6e6369c8e7a5728..5a7420bc7e12babe7647dc45505a08e4c930cac0 100644 (file)
@@ -19,6 +19,7 @@ package http
 import (
        "bufio"
        "bytes"
+       "compress/flate"
        "compress/gzip"
        "context"
        "crypto/rand"
@@ -1842,6 +1843,8 @@ type http2Framer struct {
        // lastHeaderStream is non-zero if the last frame was an
        // unfinished HEADERS/CONTINUATION.
        lastHeaderStream uint32
+       // lastFrameType holds the type of the last frame for verifying frame order.
+       lastFrameType http2FrameType
 
        maxReadSize uint32
        headerBuf   [http2frameHeaderLen]byte
@@ -2053,30 +2056,41 @@ func http2terminalReadFrameError(err error) bool {
        return err != nil
 }
 
-// ReadFrame reads a single frame. The returned Frame is only valid
-// until the next call to ReadFrame.
+// ReadFrameHeader reads the header of the next frame.
+// It reads the 9-byte fixed frame header, and does not read any portion of the
+// frame payload. The caller is responsible for consuming the payload, either
+// with ReadFrameForHeader or directly from the Framer's io.Reader.
 //
-// If the frame is larger than previously set with SetMaxReadFrameSize, the
-// returned error is ErrFrameTooLarge. Other errors may be of type
-// ConnectionError, StreamError, or anything else from the underlying
-// reader.
+// If the frame is larger than previously set with SetMaxReadFrameSize, it
+// returns the frame header and ErrFrameTooLarge.
 //
-// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID
-// indicates the stream responsible for the error.
-func (fr *http2Framer) ReadFrame() (http2Frame, error) {
+// If the returned FrameHeader.StreamID is non-zero, it indicates the stream
+// responsible for the error.
+func (fr *http2Framer) ReadFrameHeader() (http2FrameHeader, error) {
        fr.errDetail = nil
-       if fr.lastFrame != nil {
-               fr.lastFrame.invalidate()
-       }
        fh, err := http2readFrameHeader(fr.headerBuf[:], fr.r)
        if err != nil {
-               return nil, err
+               return fh, err
        }
        if fh.Length > fr.maxReadSize {
                if fh == http2invalidHTTP1LookingFrameHeader() {
-                       return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", http2ErrFrameTooLarge)
+                       return fh, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", http2ErrFrameTooLarge)
                }
-               return nil, http2ErrFrameTooLarge
+               return fh, http2ErrFrameTooLarge
+       }
+       if err := fr.checkFrameOrder(fh); err != nil {
+               return fh, err
+       }
+       return fh, nil
+}
+
+// ReadFrameForHeader reads the payload for the frame with the given FrameHeader.
+//
+// It behaves identically to ReadFrame, other than not checking the maximum
+// frame size.
+func (fr *http2Framer) ReadFrameForHeader(fh http2FrameHeader) (http2Frame, error) {
+       if fr.lastFrame != nil {
+               fr.lastFrame.invalidate()
        }
        payload := fr.getReadBuf(fh.Length)
        if _, err := io.ReadFull(fr.r, payload); err != nil {
@@ -2092,9 +2106,7 @@ func (fr *http2Framer) ReadFrame() (http2Frame, error) {
                }
                return nil, err
        }
-       if err := fr.checkFrameOrder(f); err != nil {
-               return nil, err
-       }
+       fr.lastFrame = f
        if fr.logReads {
                fr.debugReadLoggerf("http2: Framer %p: read %v", fr, http2summarizeFrame(f))
        }
@@ -2104,6 +2116,24 @@ func (fr *http2Framer) ReadFrame() (http2Frame, error) {
        return f, nil
 }
 
+// ReadFrame reads a single frame. The returned Frame is only valid
+// until the next call to ReadFrame or ReadFrameBodyForHeader.
+//
+// If the frame is larger than previously set with SetMaxReadFrameSize, the
+// returned error is ErrFrameTooLarge. Other errors may be of type
+// ConnectionError, StreamError, or anything else from the underlying
+// reader.
+//
+// If ReadFrame returns an error and a non-nil Frame, the Frame's StreamID
+// indicates the stream responsible for the error.
+func (fr *http2Framer) ReadFrame() (http2Frame, error) {
+       fh, err := fr.ReadFrameHeader()
+       if err != nil {
+               return nil, err
+       }
+       return fr.ReadFrameForHeader(fh)
+}
+
 // connError returns ConnectionError(code) but first
 // stashes away a public reason to the caller can optionally relay it
 // to the peer before hanging up on them. This might help others debug
@@ -2116,20 +2146,19 @@ func (fr *http2Framer) connError(code http2ErrCode, reason string) error {
 // checkFrameOrder reports an error if f is an invalid frame to return
 // next from ReadFrame. Mostly it checks whether HEADERS and
 // CONTINUATION frames are contiguous.
-func (fr *http2Framer) checkFrameOrder(f http2Frame) error {
-       last := fr.lastFrame
-       fr.lastFrame = f
+func (fr *http2Framer) checkFrameOrder(fh http2FrameHeader) error {
+       lastType := fr.lastFrameType
+       fr.lastFrameType = fh.Type
        if fr.AllowIllegalReads {
                return nil
        }
 
-       fh := f.Header()
        if fr.lastHeaderStream != 0 {
                if fh.Type != http2FrameContinuation {
                        return fr.connError(http2ErrCodeProtocol,
                                fmt.Sprintf("got %s for stream %d; expected CONTINUATION following %s for stream %d",
                                        fh.Type, fh.StreamID,
-                                       last.Header().Type, fr.lastHeaderStream))
+                                       lastType, fr.lastHeaderStream))
                }
                if fh.StreamID != fr.lastHeaderStream {
                        return fr.connError(http2ErrCodeProtocol,
@@ -2726,7 +2755,7 @@ var http2defaultRFC9218Priority = http2PriorityParam{
 // PriorityParam struct below is a superset of both schemes. The exported
 // symbols are from RFC 7540 and the non-exported ones are from RFC 9218.
 
-// PriorityParam are the stream prioritzation parameters.
+// PriorityParam are the stream prioritization parameters.
 type http2PriorityParam struct {
        // StreamDep is a 31-bit stream identifier for the
        // stream that this stream depends on. Zero means no
@@ -7637,11 +7666,24 @@ type http2ClientConn struct {
        // completely unresponsive connection.
        pendingResets int
 
+       // readBeforeStreamID is the smallest stream ID that has not been followed by
+       // a frame read from the peer. We use this to determine when a request may
+       // have been sent to a completely unresponsive connection:
+       // If the request ID is less than readBeforeStreamID, then we have had some
+       // indication of life on the connection since sending the request.
+       readBeforeStreamID uint32
+
        // reqHeaderMu is a 1-element semaphore channel controlling access to sending new requests.
        // Write to reqHeaderMu to lock it, read from it to unlock.
        // Lock reqmu BEFORE mu or wmu.
        reqHeaderMu chan struct{}
 
+       // internalStateHook reports state changes back to the net/http.ClientConn.
+       // Note that this is different from the user state hook registered by
+       // net/http.ClientConn.SetStateHook: The internal hook calls ClientConn,
+       // which calls the user hook.
+       internalStateHook func()
+
        // wmu is held while writing.
        // Acquire BEFORE mu when holding both, to avoid blocking mu on network writes.
        // Only acquire both at the same time when changing peer settings.
@@ -7972,7 +8014,7 @@ func http2canRetryError(err error) bool {
 
 func (t *http2Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*http2ClientConn, error) {
        if t.http2transportTestHooks != nil {
-               return t.newClientConn(nil, singleUse)
+               return t.newClientConn(nil, singleUse, nil)
        }
        host, _, err := net.SplitHostPort(addr)
        if err != nil {
@@ -7982,7 +8024,7 @@ func (t *http2Transport) dialClientConn(ctx context.Context, addr string, single
        if err != nil {
                return nil, err
        }
-       return t.newClientConn(tconn, singleUse)
+       return t.newClientConn(tconn, singleUse, nil)
 }
 
 func (t *http2Transport) newTLSConfig(host string) *tls.Config {
@@ -8034,10 +8076,10 @@ func (t *http2Transport) expectContinueTimeout() time.Duration {
 }
 
 func (t *http2Transport) NewClientConn(c net.Conn) (*http2ClientConn, error) {
-       return t.newClientConn(c, t.disableKeepAlives())
+       return t.newClientConn(c, t.disableKeepAlives(), nil)
 }
 
-func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2ClientConn, error) {
+func (t *http2Transport) newClientConn(c net.Conn, singleUse bool, internalStateHook func()) (*http2ClientConn, error) {
        conf := http2configFromTransport(t)
        cc := &http2ClientConn{
                t:                           t,
@@ -8059,6 +8101,7 @@ func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2Client
                pings:                       make(map[[8]byte]chan struct{}),
                reqHeaderMu:                 make(chan struct{}, 1),
                lastActive:                  time.Now(),
+               internalStateHook:           internalStateHook,
        }
        if t.http2transportTestHooks != nil {
                t.http2transportTestHooks.newclientconn(cc)
@@ -8299,10 +8342,7 @@ func (cc *http2ClientConn) idleStateLocked() (st http2clientConnIdleState) {
                maxConcurrentOkay = cc.currentRequestCountLocked() < int(cc.maxConcurrentStreams)
        }
 
-       st.canTakeNewRequest = cc.goAway == nil && !cc.closed && !cc.closing && maxConcurrentOkay &&
-               !cc.doNotReuse &&
-               int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
-               !cc.tooIdleLocked()
+       st.canTakeNewRequest = maxConcurrentOkay && cc.isUsableLocked()
 
        // If this connection has never been used for a request and is closed,
        // then let it take a request (which will fail).
@@ -8318,6 +8358,31 @@ func (cc *http2ClientConn) idleStateLocked() (st http2clientConnIdleState) {
        return
 }
 
+func (cc *http2ClientConn) isUsableLocked() bool {
+       return cc.goAway == nil &&
+               !cc.closed &&
+               !cc.closing &&
+               !cc.doNotReuse &&
+               int64(cc.nextStreamID)+2*int64(cc.pendingRequests) < math.MaxInt32 &&
+               !cc.tooIdleLocked()
+}
+
+// canReserveLocked reports whether a net/http.ClientConn can reserve a slot on this conn.
+//
+// This follows slightly different rules than clientConnIdleState.canTakeNewRequest.
+// We only permit reservations up to the conn's concurrency limit.
+// This differs from ClientConn.ReserveNewRequest, which permits reservations
+// past the limit when StrictMaxConcurrentStreams is set.
+func (cc *http2ClientConn) canReserveLocked() bool {
+       if cc.currentRequestCountLocked() >= int(cc.maxConcurrentStreams) {
+               return false
+       }
+       if !cc.isUsableLocked() {
+               return false
+       }
+       return true
+}
+
 // currentRequestCountLocked reports the number of concurrency slots currently in use,
 // including active streams, reserved slots, and reset streams waiting for acknowledgement.
 func (cc *http2ClientConn) currentRequestCountLocked() int {
@@ -8329,6 +8394,14 @@ func (cc *http2ClientConn) canTakeNewRequestLocked() bool {
        return st.canTakeNewRequest
 }
 
+// availableLocked reports the number of concurrency slots available.
+func (cc *http2ClientConn) availableLocked() int {
+       if !cc.canTakeNewRequestLocked() {
+               return 0
+       }
+       return max(0, int(cc.maxConcurrentStreams)-cc.currentRequestCountLocked())
+}
+
 // tooIdleLocked reports whether this connection has been been sitting idle
 // for too much wall time.
 func (cc *http2ClientConn) tooIdleLocked() bool {
@@ -8353,6 +8426,7 @@ func (cc *http2ClientConn) closeConn() {
        t := time.AfterFunc(250*time.Millisecond, cc.forceCloseConn)
        defer t.Stop()
        cc.tconn.Close()
+       cc.maybeCallStateHook()
 }
 
 // A tls.Conn.Close can hang for a long time if the peer is unresponsive.
@@ -8878,6 +8952,8 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) {
        }
        bodyClosed := cs.reqBodyClosed
        closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
+       // Have we read any frames from the connection since sending this request?
+       readSinceStream := cc.readBeforeStreamID > cs.ID
        cc.mu.Unlock()
        if mustCloseBody {
                cs.reqBody.Close()
@@ -8909,8 +8985,10 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) {
                                //
                                // This could be due to the server becoming unresponsive.
                                // To avoid sending too many requests on a dead connection,
-                               // we let the request continue to consume a concurrency slot
-                               // until we can confirm the server is still responding.
+                               // if we haven't read any frames from the connection since
+                               // sending this request, we let it continue to consume
+                               // a concurrency slot until we can confirm the server is
+                               // still responding.
                                // We do this by sending a PING frame along with the RST_STREAM
                                // (unless a ping is already in flight).
                                //
@@ -8921,7 +8999,7 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) {
                                // because it's short lived and will probably be closed before
                                // we get the ping response.
                                ping := false
-                               if !closeOnIdle {
+                               if !closeOnIdle && !readSinceStream {
                                        cc.mu.Lock()
                                        // rstStreamPingsBlocked works around a gRPC behavior:
                                        // see comment on the field for details.
@@ -8955,6 +9033,7 @@ func (cs *http2clientStream) cleanupWriteRequest(err error) {
        }
 
        close(cs.donec)
+       cc.maybeCallStateHook()
 }
 
 // awaitOpenSlotForStreamLocked waits until len(streams) < maxConcurrentStreams.
@@ -10008,6 +10087,7 @@ func (rl *http2clientConnReadLoop) streamByID(id uint32, headerOrData bool) *htt
                // See comment on ClientConn.rstStreamPingsBlocked for details.
                rl.cc.rstStreamPingsBlocked = false
        }
+       rl.cc.readBeforeStreamID = rl.cc.nextStreamID
        cs := rl.cc.streams[id]
        if cs != nil && !cs.readAborted {
                return cs
@@ -10058,6 +10138,7 @@ func (rl *http2clientConnReadLoop) processSettings(f *http2SettingsFrame) error
 
 func (rl *http2clientConnReadLoop) processSettingsNoWrite(f *http2SettingsFrame) error {
        cc := rl.cc
+       defer cc.maybeCallStateHook()
        cc.mu.Lock()
        defer cc.mu.Unlock()
 
@@ -10238,6 +10319,7 @@ func (cc *http2ClientConn) Ping(ctx context.Context) error {
 func (rl *http2clientConnReadLoop) processPing(f *http2PingFrame) error {
        if f.IsAck() {
                cc := rl.cc
+               defer cc.maybeCallStateHook()
                cc.mu.Lock()
                defer cc.mu.Unlock()
                // If ack, notify listener if any
@@ -10343,35 +10425,103 @@ func (rt http2erringRoundTripper) RoundTripErr() error { return rt.err }
 
 func (rt http2erringRoundTripper) RoundTrip(*Request) (*Response, error) { return nil, rt.err }
 
+var http2errConcurrentReadOnResBody = errors.New("http2: concurrent read on response body")
+
 // gzipReader wraps a response body so it can lazily
-// call gzip.NewReader on the first call to Read
+// get gzip.Reader from the pool on the first call to Read.
+// After Close is called it puts gzip.Reader to the pool immediately
+// if there is no Read in progress or later when Read completes.
 type http2gzipReader struct {
        _    http2incomparable
        body io.ReadCloser // underlying Response.Body
-       zr   *gzip.Reader  // lazily-initialized gzip reader
-       zerr error         // sticky error
+       mu   sync.Mutex    // guards zr and zerr
+       zr   *gzip.Reader  // stores gzip reader from the pool between reads
+       zerr error         // sticky gzip reader init error or sentinel value to detect concurrent read and read after close
 }
 
-func (gz *http2gzipReader) Read(p []byte) (n int, err error) {
+type http2eofReader struct{}
+
+func (http2eofReader) Read([]byte) (int, error) { return 0, io.EOF }
+
+func (http2eofReader) ReadByte() (byte, error) { return 0, io.EOF }
+
+var http2gzipPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
+
+// gzipPoolGet gets a gzip.Reader from the pool and resets it to read from r.
+func http2gzipPoolGet(r io.Reader) (*gzip.Reader, error) {
+       zr := http2gzipPool.Get().(*gzip.Reader)
+       if err := zr.Reset(r); err != nil {
+               http2gzipPoolPut(zr)
+               return nil, err
+       }
+       return zr, nil
+}
+
+// gzipPoolPut puts a gzip.Reader back into the pool.
+func http2gzipPoolPut(zr *gzip.Reader) {
+       // Reset will allocate bufio.Reader if we pass it anything
+       // other than a flate.Reader, so ensure that it's getting one.
+       var r flate.Reader = http2eofReader{}
+       zr.Reset(r)
+       http2gzipPool.Put(zr)
+}
+
+// acquire returns a gzip.Reader for reading response body.
+// The reader must be released after use.
+func (gz *http2gzipReader) acquire() (*gzip.Reader, error) {
+       gz.mu.Lock()
+       defer gz.mu.Unlock()
        if gz.zerr != nil {
-               return 0, gz.zerr
+               return nil, gz.zerr
        }
        if gz.zr == nil {
-               gz.zr, err = gzip.NewReader(gz.body)
-               if err != nil {
-                       gz.zerr = err
-                       return 0, err
+               gz.zr, gz.zerr = http2gzipPoolGet(gz.body)
+               if gz.zerr != nil {
+                       return nil, gz.zerr
                }
        }
-       return gz.zr.Read(p)
+       ret := gz.zr
+       gz.zr, gz.zerr = nil, http2errConcurrentReadOnResBody
+       return ret, nil
 }
 
-func (gz *http2gzipReader) Close() error {
-       if err := gz.body.Close(); err != nil {
-               return err
+// release returns the gzip.Reader to the pool if Close was called during Read.
+func (gz *http2gzipReader) release(zr *gzip.Reader) {
+       gz.mu.Lock()
+       defer gz.mu.Unlock()
+       if gz.zerr == http2errConcurrentReadOnResBody {
+               gz.zr, gz.zerr = zr, nil
+       } else { // fs.ErrClosed
+               http2gzipPoolPut(zr)
+       }
+}
+
+// close returns the gzip.Reader to the pool immediately or
+// signals release to do so after Read completes.
+func (gz *http2gzipReader) close() {
+       gz.mu.Lock()
+       defer gz.mu.Unlock()
+       if gz.zerr == nil && gz.zr != nil {
+               http2gzipPoolPut(gz.zr)
+               gz.zr = nil
        }
        gz.zerr = fs.ErrClosed
-       return nil
+}
+
+func (gz *http2gzipReader) Read(p []byte) (n int, err error) {
+       zr, err := gz.acquire()
+       if err != nil {
+               return 0, err
+       }
+       defer gz.release(zr)
+
+       return zr.Read(p)
+}
+
+func (gz *http2gzipReader) Close() error {
+       gz.close()
+
+       return gz.body.Close()
 }
 
 type http2errorReader struct{ err error }
@@ -10397,9 +10547,13 @@ func http2registerHTTPSProtocol(t *Transport, rt http2noDialH2RoundTripper) (err
 }
 
 // noDialH2RoundTripper is a RoundTripper which only tries to complete the request
-// if there's already has a cached connection to the host.
+// if there's already a cached connection to the host.
 // (The field is exported so it can be accessed via reflect from net/http; tested
 // by TestNoDialH2RoundTripperType)
+//
+// A noDialH2RoundTripper is registered with http1.Transport.RegisterProtocol,
+// and the http1.Transport can use type assertions to call non-RoundTrip methods on it.
+// This lets us expose, for example, NewClientConn to net/http.
 type http2noDialH2RoundTripper struct{ *http2Transport }
 
 func (rt http2noDialH2RoundTripper) RoundTrip(req *Request) (*Response, error) {
@@ -10410,6 +10564,85 @@ func (rt http2noDialH2RoundTripper) RoundTrip(req *Request) (*Response, error) {
        return res, err
 }
 
+func (rt http2noDialH2RoundTripper) NewClientConn(conn net.Conn, internalStateHook func()) (RoundTripper, error) {
+       tr := rt.http2Transport
+       cc, err := tr.newClientConn(conn, tr.disableKeepAlives(), internalStateHook)
+       if err != nil {
+               return nil, err
+       }
+
+       // RoundTrip should block when the conn is at its concurrency limit,
+       // not return an error. Setting strictMaxConcurrentStreams enables this.
+       cc.strictMaxConcurrentStreams = true
+
+       return http2netHTTPClientConn{cc}, nil
+}
+
+// netHTTPClientConn wraps ClientConn and implements the interface net/http expects from
+// the RoundTripper returned by NewClientConn.
+type http2netHTTPClientConn struct {
+       cc *http2ClientConn
+}
+
+func (cc http2netHTTPClientConn) RoundTrip(req *Request) (*Response, error) {
+       return cc.cc.RoundTrip(req)
+}
+
+func (cc http2netHTTPClientConn) Close() error {
+       return cc.cc.Close()
+}
+
+func (cc http2netHTTPClientConn) Err() error {
+       cc.cc.mu.Lock()
+       defer cc.cc.mu.Unlock()
+       if cc.cc.closed {
+               return errors.New("connection closed")
+       }
+       return nil
+}
+
+func (cc http2netHTTPClientConn) Reserve() error {
+       defer cc.cc.maybeCallStateHook()
+       cc.cc.mu.Lock()
+       defer cc.cc.mu.Unlock()
+       if !cc.cc.canReserveLocked() {
+               return errors.New("connection is unavailable")
+       }
+       cc.cc.streamsReserved++
+       return nil
+}
+
+func (cc http2netHTTPClientConn) Release() {
+       defer cc.cc.maybeCallStateHook()
+       cc.cc.mu.Lock()
+       defer cc.cc.mu.Unlock()
+       // We don't complain if streamsReserved is 0.
+       //
+       // This is consistent with RoundTrip: both Release and RoundTrip will
+       // consume a reservation iff one exists.
+       if cc.cc.streamsReserved > 0 {
+               cc.cc.streamsReserved--
+       }
+}
+
+func (cc http2netHTTPClientConn) Available() int {
+       cc.cc.mu.Lock()
+       defer cc.cc.mu.Unlock()
+       return cc.cc.availableLocked()
+}
+
+func (cc http2netHTTPClientConn) InFlight() int {
+       cc.cc.mu.Lock()
+       defer cc.cc.mu.Unlock()
+       return cc.cc.currentRequestCountLocked()
+}
+
+func (cc *http2ClientConn) maybeCallStateHook() {
+       if cc.internalStateHook != nil {
+               cc.internalStateHook()
+       }
+}
+
 func (t *http2Transport) idleConnTimeout() time.Duration {
        // to keep things backwards compatible, we use non-zero values of
        // IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
@@ -11069,45 +11302,75 @@ func (wr *http2FrameWriteRequest) replyToWriter(err error) {
 }
 
 // writeQueue is used by implementations of WriteScheduler.
+//
+// Each writeQueue contains a queue of FrameWriteRequests, meant to store all
+// FrameWriteRequests associated with a given stream. This is implemented as a
+// two-stage queue: currQueue[currPos:] and nextQueue. Removing an item is done
+// by incrementing currPos of currQueue. Adding an item is done by appending it
+// to the nextQueue. If currQueue is empty when trying to remove an item, we
+// can swap currQueue and nextQueue to remedy the situation.
+// This two-stage queue is analogous to the use of two lists in Okasaki's
+// purely functional queue but without the overhead of reversing the list when
+// swapping stages.
+//
+// writeQueue also contains prev and next, this can be used by implementations
+// of WriteScheduler to construct data structures that represent the order of
+// writing between different streams (e.g. circular linked list).
 type http2writeQueue struct {
-       s          []http2FrameWriteRequest
+       currQueue []http2FrameWriteRequest
+       nextQueue []http2FrameWriteRequest
+       currPos   int
+
        prev, next *http2writeQueue
 }
 
-func (q *http2writeQueue) empty() bool { return len(q.s) == 0 }
+func (q *http2writeQueue) empty() bool {
+       return (len(q.currQueue) - q.currPos + len(q.nextQueue)) == 0
+}
 
 func (q *http2writeQueue) push(wr http2FrameWriteRequest) {
-       q.s = append(q.s, wr)
+       q.nextQueue = append(q.nextQueue, wr)
 }
 
 func (q *http2writeQueue) shift() http2FrameWriteRequest {
-       if len(q.s) == 0 {
+       if q.empty() {
                panic("invalid use of queue")
        }
-       wr := q.s[0]
-       // TODO: less copy-happy queue.
-       copy(q.s, q.s[1:])
-       q.s[len(q.s)-1] = http2FrameWriteRequest{}
-       q.s = q.s[:len(q.s)-1]
+       if q.currPos >= len(q.currQueue) {
+               q.currQueue, q.currPos, q.nextQueue = q.nextQueue, 0, q.currQueue[:0]
+       }
+       wr := q.currQueue[q.currPos]
+       q.currQueue[q.currPos] = http2FrameWriteRequest{}
+       q.currPos++
        return wr
 }
 
+func (q *http2writeQueue) peek() *http2FrameWriteRequest {
+       if q.currPos < len(q.currQueue) {
+               return &q.currQueue[q.currPos]
+       }
+       if len(q.nextQueue) > 0 {
+               return &q.nextQueue[0]
+       }
+       return nil
+}
+
 // consume consumes up to n bytes from q.s[0]. If the frame is
 // entirely consumed, it is removed from the queue. If the frame
 // is partially consumed, the frame is kept with the consumed
 // bytes removed. Returns true iff any bytes were consumed.
 func (q *http2writeQueue) consume(n int32) (http2FrameWriteRequest, bool) {
-       if len(q.s) == 0 {
+       if q.empty() {
                return http2FrameWriteRequest{}, false
        }
-       consumed, rest, numresult := q.s[0].Consume(n)
+       consumed, rest, numresult := q.peek().Consume(n)
        switch numresult {
        case 0:
                return http2FrameWriteRequest{}, false
        case 1:
                q.shift()
        case 2:
-               q.s[0] = rest
+               *q.peek() = rest
        }
        return consumed, true
 }
@@ -11118,10 +11381,15 @@ type http2writeQueuePool []*http2writeQueue
 
 // put inserts an unused writeQueue into the pool.
 func (p *http2writeQueuePool) put(q *http2writeQueue) {
-       for i := range q.s {
-               q.s[i] = http2FrameWriteRequest{}
+       for i := range q.currQueue {
+               q.currQueue[i] = http2FrameWriteRequest{}
+       }
+       for i := range q.nextQueue {
+               q.nextQueue[i] = http2FrameWriteRequest{}
        }
-       q.s = q.s[:0]
+       q.currQueue = q.currQueue[:0]
+       q.nextQueue = q.nextQueue[:0]
+       q.currPos = 0
        *p = append(*p, q)
 }
 
@@ -11344,8 +11612,8 @@ func (z http2sortPriorityNodeSiblingsRFC7540) Swap(i, k int) { z[i], z[k] = z[k]
 func (z http2sortPriorityNodeSiblingsRFC7540) Less(i, k int) bool {
        // Prefer the subtree that has sent fewer bytes relative to its weight.
        // See sections 5.3.2 and 5.3.4.
-       wi, bi := float64(z[i].weight+1), float64(z[i].subtreeBytes)
-       wk, bk := float64(z[k].weight+1), float64(z[k].subtreeBytes)
+       wi, bi := float64(z[i].weight)+1, float64(z[i].subtreeBytes)
+       wk, bk := float64(z[k].weight)+1, float64(z[k].subtreeBytes)
        if bi == 0 && bk == 0 {
                return wi >= wk
        }
@@ -11432,7 +11700,6 @@ func (ws *http2priorityWriteSchedulerRFC7540) CloseStream(streamID uint32) {
 
        q := n.q
        ws.queuePool.put(&q)
-       n.q.s = nil
        if ws.maxClosedNodesInTree > 0 {
                ws.addClosedOrIdleNode(&ws.closedNodes, ws.maxClosedNodesInTree, n)
        } else {
@@ -11610,7 +11877,7 @@ type http2priorityWriteSchedulerRFC9218 struct {
        prioritizeIncremental bool
 }
 
-func http2newPriorityWriteSchedulerRFC9128() http2WriteScheduler {
+func http2newPriorityWriteSchedulerRFC9218() http2WriteScheduler {
        ws := &http2priorityWriteSchedulerRFC9218{
                streams: make(map[uint32]http2streamMetadata),
        }
index 7dd2638e88ada9e1251a40d5fae679275b62fe50..769af387e2e5f1f60aba40949a974e076337b837 100644 (file)
@@ -29,7 +29,7 @@ loop:
        MOVD    $NUM_ROUNDS, R21
        VLD1    (R11), [V30.S4, V31.S4]
 
-       // load contants
+       // load constants
        // VLD4R (R10), [V0.S4, V1.S4, V2.S4, V3.S4]
        WORD    $0x4D60E940
 
index 50695a14f62d871ecf1d40b7e32951e7ac7a7d49..b850e772e1656b16d8347ee3e5a5e3cfadd53daa 100644 (file)
@@ -56,7 +56,10 @@ func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []
 
        ret, out := sliceForAppend(dst, len(plaintext)+16)
        if alias.InexactOverlap(out, plaintext) {
-               panic("chacha20poly1305: invalid buffer overlap")
+               panic("chacha20poly1305: invalid buffer overlap of output and input")
+       }
+       if alias.AnyOverlap(out, additionalData) {
+               panic("chacha20poly1305: invalid buffer overlap of output and additional data")
        }
        chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData)
        return ret
@@ -73,7 +76,10 @@ func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) (
        ciphertext = ciphertext[:len(ciphertext)-16]
        ret, out := sliceForAppend(dst, len(ciphertext))
        if alias.InexactOverlap(out, ciphertext) {
-               panic("chacha20poly1305: invalid buffer overlap")
+               panic("chacha20poly1305: invalid buffer overlap of output and input")
+       }
+       if alias.AnyOverlap(out, additionalData) {
+               panic("chacha20poly1305: invalid buffer overlap of output and additional data")
        }
        if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) {
                for i := range out {
index 6313898f0a75d9e2e25f713b7e140715219fba5c..2ecc840fca2c73dc77d81fa00dd408e78eaa5f73 100644 (file)
@@ -31,7 +31,10 @@ func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []b
        ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize)
        ciphertext, tag := out[:len(plaintext)], out[len(plaintext):]
        if alias.InexactOverlap(out, plaintext) {
-               panic("chacha20poly1305: invalid buffer overlap")
+               panic("chacha20poly1305: invalid buffer overlap of output and input")
+       }
+       if alias.AnyOverlap(out, additionalData) {
+               panic("chacha20poly1305: invalid buffer overlap of output and additional data")
        }
 
        var polyKey [32]byte
@@ -67,7 +70,10 @@ func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []
 
        ret, out := sliceForAppend(dst, len(ciphertext))
        if alias.InexactOverlap(out, ciphertext) {
-               panic("chacha20poly1305: invalid buffer overlap")
+               panic("chacha20poly1305: invalid buffer overlap of output and input")
+       }
+       if alias.AnyOverlap(out, additionalData) {
+               panic("chacha20poly1305: invalid buffer overlap of output and additional data")
        }
        if !p.Verify(tag) {
                for i := range out {
index a656efc128a6f8848463403496fb1fb74d442933..7a978b47f666cf6105932d99361eb8d26cb54058 100644 (file)
@@ -17,8 +17,21 @@ import (
 )
 
 // Message formats
-
-// A Type is a type of DNS request and response.
+//
+// To add a new Resource Record type:
+// 1. Create Resource Record types
+//   1.1. Add a Type constant named "Type<name>"
+//   1.2. Add the corresponding entry to the typeNames map
+//   1.3. Add a [ResourceBody] implementation named "<name>Resource"
+// 2. Implement packing
+//   2.1. Implement Builder.<name>Resource()
+// 3. Implement unpacking
+//   3.1. Add the unpacking code to unpackResourceBody()
+//   3.2. Implement Parser.<name>Resource()
+
+// A Type is the type of a DNS Resource Record, as defined in the [IANA registry].
+//
+// [IANA registry]: https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4
 type Type uint16
 
 const (
@@ -33,6 +46,8 @@ const (
        TypeAAAA  Type = 28
        TypeSRV   Type = 33
        TypeOPT   Type = 41
+       TypeSVCB  Type = 64
+       TypeHTTPS Type = 65
 
        // Question.Type
        TypeWKS   Type = 11
@@ -53,6 +68,8 @@ var typeNames = map[Type]string{
        TypeAAAA:  "TypeAAAA",
        TypeSRV:   "TypeSRV",
        TypeOPT:   "TypeOPT",
+       TypeSVCB:  "TypeSVCB",
+       TypeHTTPS: "TypeHTTPS",
        TypeWKS:   "TypeWKS",
        TypeHINFO: "TypeHINFO",
        TypeMINFO: "TypeMINFO",
@@ -273,6 +290,8 @@ var (
        errTooManyAdditionals = errors.New("too many Additionals to pack (>65535)")
        errNonCanonicalName   = errors.New("name is not in canonical format (it must end with a .)")
        errStringTooLong      = errors.New("character string exceeds maximum length (255)")
+       errParamOutOfOrder    = errors.New("parameter out of order")
+       errTooLongSVCBValue   = errors.New("value too long (>65535 bytes)")
 )
 
 // Internal constants.
@@ -2220,6 +2239,16 @@ func unpackResourceBody(msg []byte, off int, hdr ResourceHeader) (ResourceBody,
                rb, err = unpackSRVResource(msg, off)
                r = &rb
                name = "SRV"
+       case TypeSVCB:
+               var rb SVCBResource
+               rb, err = unpackSVCBResource(msg, off, hdr.Length)
+               r = &rb
+               name = "SVCB"
+       case TypeHTTPS:
+               var rb HTTPSResource
+               rb.SVCBResource, err = unpackSVCBResource(msg, off, hdr.Length)
+               r = &rb
+               name = "HTTPS"
        case TypeOPT:
                var rb OPTResource
                rb, err = unpackOPTResource(msg, off, hdr.Length)
diff --git a/src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go b/src/vendor/golang.org/x/net/dns/dnsmessage/svcb.go
new file mode 100644 (file)
index 0000000..4840516
--- /dev/null
@@ -0,0 +1,326 @@
+// Copyright 2025 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 dnsmessage
+
+import (
+       "slices"
+)
+
+// An SVCBResource is an SVCB Resource record.
+type SVCBResource struct {
+       Priority uint16
+       Target   Name
+       Params   []SVCParam // Must be in strict increasing order by Key.
+}
+
+func (r *SVCBResource) realType() Type {
+       return TypeSVCB
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (r *SVCBResource) GoString() string {
+       b := []byte("dnsmessage.SVCBResource{" +
+               "Priority: " + printUint16(r.Priority) + ", " +
+               "Target: " + r.Target.GoString() + ", " +
+               "Params: []dnsmessage.SVCParam{")
+       if len(r.Params) > 0 {
+               b = append(b, r.Params[0].GoString()...)
+               for _, p := range r.Params[1:] {
+                       b = append(b, ", "+p.GoString()...)
+               }
+       }
+       b = append(b, "}}"...)
+       return string(b)
+}
+
+// An HTTPSResource is an HTTPS Resource record.
+// It has the same format as the SVCB record.
+type HTTPSResource struct {
+       // Alias for SVCB resource record.
+       SVCBResource
+}
+
+func (r *HTTPSResource) realType() Type {
+       return TypeHTTPS
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (r *HTTPSResource) GoString() string {
+       return "dnsmessage.HTTPSResource{SVCBResource: " + r.SVCBResource.GoString() + "}"
+}
+
+// GetParam returns a parameter value by key.
+func (r *SVCBResource) GetParam(key SVCParamKey) (value []byte, ok bool) {
+       for i := range r.Params {
+               if r.Params[i].Key == key {
+                       return r.Params[i].Value, true
+               }
+               if r.Params[i].Key > key {
+                       break
+               }
+       }
+       return nil, false
+}
+
+// SetParam sets a parameter value by key.
+// The Params list is kept sorted by key.
+func (r *SVCBResource) SetParam(key SVCParamKey, value []byte) {
+       i := 0
+       for i < len(r.Params) {
+               if r.Params[i].Key >= key {
+                       break
+               }
+               i++
+       }
+
+       if i < len(r.Params) && r.Params[i].Key == key {
+               r.Params[i].Value = value
+               return
+       }
+
+       r.Params = slices.Insert(r.Params, i, SVCParam{Key: key, Value: value})
+}
+
+// DeleteParam deletes a parameter by key.
+// It returns true if the parameter was present.
+func (r *SVCBResource) DeleteParam(key SVCParamKey) bool {
+       for i := range r.Params {
+               if r.Params[i].Key == key {
+                       r.Params = slices.Delete(r.Params, i, i+1)
+                       return true
+               }
+               if r.Params[i].Key > key {
+                       break
+               }
+       }
+       return false
+}
+
+// A SVCParam is a service parameter.
+type SVCParam struct {
+       Key   SVCParamKey
+       Value []byte
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (p SVCParam) GoString() string {
+       return "dnsmessage.SVCParam{" +
+               "Key: " + p.Key.GoString() + ", " +
+               "Value: []byte{" + printByteSlice(p.Value) + "}}"
+}
+
+// A SVCParamKey is a key for a service parameter.
+type SVCParamKey uint16
+
+// Values defined at https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys.
+const (
+       SVCParamMandatory          SVCParamKey = 0
+       SVCParamALPN               SVCParamKey = 1
+       SVCParamNoDefaultALPN      SVCParamKey = 2
+       SVCParamPort               SVCParamKey = 3
+       SVCParamIPv4Hint           SVCParamKey = 4
+       SVCParamECH                SVCParamKey = 5
+       SVCParamIPv6Hint           SVCParamKey = 6
+       SVCParamDOHPath            SVCParamKey = 7
+       SVCParamOHTTP              SVCParamKey = 8
+       SVCParamTLSSupportedGroups SVCParamKey = 9
+)
+
+var svcParamKeyNames = map[SVCParamKey]string{
+       SVCParamMandatory:          "Mandatory",
+       SVCParamALPN:               "ALPN",
+       SVCParamNoDefaultALPN:      "NoDefaultALPN",
+       SVCParamPort:               "Port",
+       SVCParamIPv4Hint:           "IPv4Hint",
+       SVCParamECH:                "ECH",
+       SVCParamIPv6Hint:           "IPv6Hint",
+       SVCParamDOHPath:            "DOHPath",
+       SVCParamOHTTP:              "OHTTP",
+       SVCParamTLSSupportedGroups: "TLSSupportedGroups",
+}
+
+// String implements fmt.Stringer.String.
+func (k SVCParamKey) String() string {
+       if n, ok := svcParamKeyNames[k]; ok {
+               return n
+       }
+       return printUint16(uint16(k))
+}
+
+// GoString implements fmt.GoStringer.GoString.
+func (k SVCParamKey) GoString() string {
+       if n, ok := svcParamKeyNames[k]; ok {
+               return "dnsmessage.SVCParam" + n
+       }
+       return printUint16(uint16(k))
+}
+
+func (r *SVCBResource) pack(msg []byte, _ map[string]uint16, _ int) ([]byte, error) {
+       oldMsg := msg
+       msg = packUint16(msg, r.Priority)
+       // https://datatracker.ietf.org/doc/html/rfc3597#section-4 prohibits name
+       // compression for RR types that are not "well-known".
+       // https://datatracker.ietf.org/doc/html/rfc9460#section-2.2 explicitly states that
+       // compression of the Target is prohibited, following RFC 3597.
+       msg, err := r.Target.pack(msg, nil, 0)
+       if err != nil {
+               return oldMsg, &nestedError{"SVCBResource.Target", err}
+       }
+       var previousKey SVCParamKey
+       for i, param := range r.Params {
+               if i > 0 && param.Key <= previousKey {
+                       return oldMsg, &nestedError{"SVCBResource.Params", errParamOutOfOrder}
+               }
+               if len(param.Value) > (1<<16)-1 {
+                       return oldMsg, &nestedError{"SVCBResource.Params", errTooLongSVCBValue}
+               }
+               msg = packUint16(msg, uint16(param.Key))
+               msg = packUint16(msg, uint16(len(param.Value)))
+               msg = append(msg, param.Value...)
+       }
+       return msg, nil
+}
+
+func unpackSVCBResource(msg []byte, off int, length uint16) (SVCBResource, error) {
+       // Wire format reference: https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2.
+       r := SVCBResource{}
+       paramsOff := off
+       bodyEnd := off + int(length)
+
+       var err error
+       if r.Priority, paramsOff, err = unpackUint16(msg, paramsOff); err != nil {
+               return SVCBResource{}, &nestedError{"Priority", err}
+       }
+
+       if paramsOff, err = r.Target.unpack(msg, paramsOff); err != nil {
+               return SVCBResource{}, &nestedError{"Target", err}
+       }
+
+       // Two-pass parsing to avoid allocations.
+       // First, count the number of params.
+       n := 0
+       var totalValueLen uint16
+       off = paramsOff
+       var previousKey uint16
+       for off < bodyEnd {
+               var key, len uint16
+               if key, off, err = unpackUint16(msg, off); err != nil {
+                       return SVCBResource{}, &nestedError{"Params key", err}
+               }
+               if n > 0 && key <= previousKey {
+                       // As per https://www.rfc-editor.org/rfc/rfc9460.html#section-2.2, clients MUST
+                       // consider the RR malformed if the SvcParamKeys are not in strictly increasing numeric order
+                       return SVCBResource{}, &nestedError{"Params", errParamOutOfOrder}
+               }
+               if len, off, err = unpackUint16(msg, off); err != nil {
+                       return SVCBResource{}, &nestedError{"Params value length", err}
+               }
+               if off+int(len) > bodyEnd {
+                       return SVCBResource{}, errResourceLen
+               }
+               totalValueLen += len
+               off += int(len)
+               n++
+       }
+       if off != bodyEnd {
+               return SVCBResource{}, errResourceLen
+       }
+
+       // Second, fill in the params.
+       r.Params = make([]SVCParam, n)
+       // valuesBuf is used to hold all param values to reduce allocations.
+       // Each param's Value slice will point into this buffer.
+       valuesBuf := make([]byte, totalValueLen)
+       off = paramsOff
+       for i := 0; i < n; i++ {
+               p := &r.Params[i]
+               var key, len uint16
+               if key, off, err = unpackUint16(msg, off); err != nil {
+                       return SVCBResource{}, &nestedError{"param key", err}
+               }
+               p.Key = SVCParamKey(key)
+               if len, off, err = unpackUint16(msg, off); err != nil {
+                       return SVCBResource{}, &nestedError{"param length", err}
+               }
+               if copy(valuesBuf, msg[off:off+int(len)]) != int(len) {
+                       return SVCBResource{}, &nestedError{"param value", errCalcLen}
+               }
+               p.Value = valuesBuf[:len:len]
+               valuesBuf = valuesBuf[len:]
+               off += int(len)
+       }
+
+       return r, nil
+}
+
+// genericSVCBResource parses a single Resource Record compatible with SVCB.
+func (p *Parser) genericSVCBResource(svcbType Type) (SVCBResource, error) {
+       if !p.resHeaderValid || p.resHeaderType != svcbType {
+               return SVCBResource{}, ErrNotStarted
+       }
+       r, err := unpackSVCBResource(p.msg, p.off, p.resHeaderLength)
+       if err != nil {
+               return SVCBResource{}, err
+       }
+       p.off += int(p.resHeaderLength)
+       p.resHeaderValid = false
+       p.index++
+       return r, nil
+}
+
+// SVCBResource parses a single SVCBResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) SVCBResource() (SVCBResource, error) {
+       return p.genericSVCBResource(TypeSVCB)
+}
+
+// HTTPSResource parses a single HTTPSResource.
+//
+// One of the XXXHeader methods must have been called before calling this
+// method.
+func (p *Parser) HTTPSResource() (HTTPSResource, error) {
+       svcb, err := p.genericSVCBResource(TypeHTTPS)
+       if err != nil {
+               return HTTPSResource{}, err
+       }
+       return HTTPSResource{svcb}, nil
+}
+
+// genericSVCBResource is the generic implementation for adding SVCB-like resources.
+func (b *Builder) genericSVCBResource(h ResourceHeader, r SVCBResource) error {
+       if err := b.checkResourceSection(); err != nil {
+               return err
+       }
+       msg, lenOff, err := h.pack(b.msg, b.compression, b.start)
+       if err != nil {
+               return &nestedError{"ResourceHeader", err}
+       }
+       preLen := len(msg)
+       if msg, err = r.pack(msg, b.compression, b.start); err != nil {
+               return &nestedError{"ResourceBody", err}
+       }
+       if err := h.fixLen(msg, lenOff, preLen); err != nil {
+               return err
+       }
+       if err := b.incrementSectionCount(); err != nil {
+               return err
+       }
+       b.msg = msg
+       return nil
+}
+
+// SVCBResource adds a single SVCBResource.
+func (b *Builder) SVCBResource(h ResourceHeader, r SVCBResource) error {
+       h.Type = r.realType()
+       return b.genericSVCBResource(h, r)
+}
+
+// HTTPSResource adds a single HTTPSResource.
+func (b *Builder) HTTPSResource(h ResourceHeader, r HTTPSResource) error {
+       h.Type = r.realType()
+       return b.genericSVCBResource(h, r.SVCBResource)
+}
index bf7a797966088fd407b40dd5ac8426f99e5ef83b..27f1ea7edf1f6093e48a991512c241eff2665873 100644 (file)
@@ -1,4 +1,4 @@
-# golang.org/x/crypto v0.43.0
+# golang.org/x/crypto v0.44.0
 ## explicit; go 1.24.0
 golang.org/x/crypto/chacha20
 golang.org/x/crypto/chacha20poly1305
@@ -6,7 +6,7 @@ golang.org/x/crypto/cryptobyte
 golang.org/x/crypto/cryptobyte/asn1
 golang.org/x/crypto/internal/alias
 golang.org/x/crypto/internal/poly1305
-# golang.org/x/net v0.46.0
+# golang.org/x/net v0.47.1-0.20251124223553-bff14c525670
 ## explicit; go 1.24.0
 golang.org/x/net/dns/dnsmessage
 golang.org/x/net/http/httpguts
@@ -18,7 +18,7 @@ golang.org/x/net/nettest
 # golang.org/x/sys v0.38.0
 ## explicit; go 1.24.0
 golang.org/x/sys/cpu
-# golang.org/x/text v0.30.0
+# golang.org/x/text v0.31.0
 ## explicit; go 1.24.0
 golang.org/x/text/secure/bidirule
 golang.org/x/text/transform