// Copyright 2024 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.

//go:build go1.21

package quic

import "time"

type pathState struct {
	// Response to a peer's PATH_CHALLENGE.
	// This is not a sentVal, because we don't resend lost PATH_RESPONSE frames.
	// We only track the most recent PATH_CHALLENGE.
	// If the peer sends a second PATH_CHALLENGE before we respond to the first,
	// we'll drop the first response.
	sendPathResponse pathResponseType
	data             pathChallengeData
}

// pathChallengeData is data carried in a PATH_CHALLENGE or PATH_RESPONSE frame.
type pathChallengeData [64 / 8]byte

type pathResponseType uint8

const (
	pathResponseNotNeeded = pathResponseType(iota)
	pathResponseSmall     // send PATH_RESPONSE, do not expand datagram
	pathResponseExpanded  // send PATH_RESPONSE, expand datagram to 1200 bytes
)

func (c *Conn) handlePathChallenge(_ time.Time, dgram *datagram, data pathChallengeData) {
	// A PATH_RESPONSE is sent in a datagram expanded to 1200 bytes,
	// except when this would exceed the anti-amplification limit.
	//
	// Rather than maintaining anti-amplification state for each path
	// we may be sending a PATH_RESPONSE on, follow the following heuristic:
	//
	// If we receive a PATH_CHALLENGE in an expanded datagram,
	// respond with an expanded datagram.
	//
	// If we receive a PATH_CHALLENGE in a non-expanded datagram,
	// then the peer is presumably blocked by its own anti-amplification limit.
	// Respond with a non-expanded datagram. Receiving this PATH_RESPONSE
	// will validate the path to the peer, remove its anti-amplification limit,
	// and permit it to send a followup PATH_CHALLENGE in an expanded datagram.
	// https://www.rfc-editor.org/rfc/rfc9000.html#section-8.2.1
	if len(dgram.b) >= smallestMaxDatagramSize {
		c.path.sendPathResponse = pathResponseExpanded
	} else {
		c.path.sendPathResponse = pathResponseSmall
	}
	c.path.data = data
}

func (c *Conn) handlePathResponse(now time.Time, _ pathChallengeData) {
	// "If the content of a PATH_RESPONSE frame does not match the content of
	// a PATH_CHALLENGE frame previously sent by the endpoint,
	// the endpoint MAY generate a connection error of type PROTOCOL_VIOLATION."
	// https://www.rfc-editor.org/rfc/rfc9000.html#section-19.18-4
	//
	// We never send PATH_CHALLENGE frames.
	c.abort(now, localTransportError{
		code:   errProtocolViolation,
		reason: "PATH_RESPONSE received when no PATH_CHALLENGE sent",
	})
}

// appendPathFrames appends path validation related frames to the current packet.
// If the return value pad is true, then the packet should be padded to 1200 bytes.
func (c *Conn) appendPathFrames() (pad, ok bool) {
	if c.path.sendPathResponse == pathResponseNotNeeded {
		return pad, true
	}
	// We're required to send the PATH_RESPONSE on the path where the
	// PATH_CHALLENGE was received (RFC 9000, Section 8.2.2).
	//
	// At the moment, we don't support path migration and reject packets if
	// the peer changes its source address, so just sending the PATH_RESPONSE
	// in a regular datagram is fine.
	if !c.w.appendPathResponseFrame(c.path.data) {
		return pad, false
	}
	if c.path.sendPathResponse == pathResponseExpanded {
		pad = true
	}
	c.path.sendPathResponse = pathResponseNotNeeded
	return pad, true
}
