package quic

import (
	
	
	
	

	
	
	
	
	
)

type pathID int64

const invalidPathID pathID = -1

// Maximum number of paths to keep track of.
// If the peer probes another path (before the pathTimeout of an existing path expires),
// this probing attempt is ignored.
const maxPaths = 3

// If no packet is received for a path for pathTimeout,
// the path can be evicted when the peer probes another path.
// This prevents an attacker from churning through paths by duplicating packets and
// sending them with spoofed source addresses.
const pathTimeout = 5 * time.Second

type path struct {
	id             pathID
	addr           net.Addr
	lastPacketTime monotime.Time
	pathChallenge  [8]byte
	validated      bool
	rcvdNonProbing bool
}

type pathManager struct {
	nextPathID pathID
	// ordered by lastPacketTime, with the most recently used path at the end
	paths []*path

	getConnID    func(pathID) (_ protocol.ConnectionID, ok bool)
	retireConnID func(pathID)

	logger utils.Logger
}

func newPathManager(
	 func(pathID) ( protocol.ConnectionID,  bool),
	 func(pathID),
	 utils.Logger,
) *pathManager {
	return &pathManager{
		paths:        make([]*path, 0, maxPaths+1),
		getConnID:    ,
		retireConnID: ,
		logger:       ,
	}
}

// Returns a path challenge frame if one should be sent.
// May return nil.
func ( *pathManager) (
	 net.Addr,
	 monotime.Time,
	 *wire.PathChallengeFrame, // may be nil if the packet didn't contain a PATH_CHALLENGE
	 bool,
) ( protocol.ConnectionID,  []ackhandler.Frame,  bool) {
	var  *path
	for ,  := range .paths {
		if addrsEqual(.addr, ) {
			 = 
			.lastPacketTime = 
			// already sent a PATH_CHALLENGE for this path
			if  {
				.rcvdNonProbing = true
			}
			if .logger.Debug() {
				.logger.Debugf("received packet for path %s that was already probed, validated: %t", , .validated)
			}
			 = .validated && .rcvdNonProbing
			if  != len(.paths)-1 {
				// move the path to the end of the list
				.paths = slices.Delete(.paths, , +1)
				.paths = append(.paths, )
			}
			if  == nil {
				return protocol.ConnectionID{}, nil, 
			}
		}
	}

	if len(.paths) >= maxPaths {
		if .paths[0].lastPacketTime.Add(pathTimeout).After() {
			if .logger.Debug() {
				.logger.Debugf("received packet for previously unseen path %s, but already have %d paths", , len(.paths))
			}
			return protocol.ConnectionID{}, nil, 
		}
		// evict the oldest path, if the last packet was received more than pathTimeout ago
		.retireConnID(.paths[0].id)
		.paths = .paths[1:]
	}

	var  pathID
	if  != nil {
		 = .id
	} else {
		 = .nextPathID
	}

	// previously unseen path, initiate path validation by sending a PATH_CHALLENGE
	,  := .getConnID()
	if ! {
		.logger.Debugf("skipping validation of new path %s since no connection ID is available", )
		return protocol.ConnectionID{}, nil, 
	}

	 := make([]ackhandler.Frame, 0, 2)
	if  == nil {
		var  [8]byte
		rand.Read([:])
		 = &path{
			id:             .nextPathID,
			addr:           ,
			lastPacketTime: ,
			rcvdNonProbing: ,
			pathChallenge:  ,
		}
		.nextPathID++
		.paths = append(.paths, )
		 = append(, ackhandler.Frame{
			Frame:   &wire.PathChallengeFrame{Data: .pathChallenge},
			Handler: (*pathManagerAckHandler)(),
		})
		.logger.Debugf("enqueueing PATH_CHALLENGE for new path %s", )
	}
	if  != nil {
		 = append(, ackhandler.Frame{
			Frame:   &wire.PathResponseFrame{Data: .Data},
			Handler: (*pathManagerAckHandler)(),
		})
	}
	return , , 
}

func ( *pathManager) ( *wire.PathResponseFrame) {
	for ,  := range .paths {
		if .Data == .pathChallenge {
			// path validated
			.validated = true
			.logger.Debugf("path %s validated", .addr)
			break
		}
	}
}

// SwitchToPath is called when the connection switches to a new path
func ( *pathManager) ( net.Addr) {
	// retire all other paths
	for ,  := range .paths {
		if addrsEqual(.addr, ) {
			.logger.Debugf("switching to path %d (%s)", .id, )
			continue
		}
		.retireConnID(.id)
	}
	clear(.paths)
	.paths = .paths[:0]
}

type pathManagerAckHandler pathManager

var _ ackhandler.FrameHandler = &pathManagerAckHandler{}

// Acknowledging the frame doesn't validate the path, only receiving the PATH_RESPONSE does.
func ( *pathManagerAckHandler) ( wire.Frame) {}

func ( *pathManagerAckHandler) ( wire.Frame) {
	,  := .(*wire.PathChallengeFrame)
	if ! {
		return
	}
	for ,  := range .paths {
		if .pathChallenge == .Data {
			.paths = slices.Delete(.paths, , +1)
			.retireConnID(.id)
			break
		}
	}
}

func addrsEqual(,  net.Addr) bool {
	if  == nil ||  == nil {
		return false
	}
	,  := .(*net.UDPAddr)
	,  := .(*net.UDPAddr)
	if  &&  {
		return .IP.Equal(.IP) && .Port == .Port
	}
	return .String() == .String()
}