package client

import (
	
	
	

	
	
	
	pbv2 
	
	

	ma 
)

const maxMessageSize = 4096

var DialTimeout = time.Minute
var DialRelayTimeout = 5 * time.Second

// relay protocol errors; used for signalling deduplication
type relayError struct {
	err string
}

func ( relayError) () string {
	return .err
}

func newRelayError( string,  ...interface{}) error {
	return relayError{err: fmt.Sprintf(, ...)}
}

func isRelayError( error) bool {
	,  := .(relayError)
	return 
}

// dialer
func ( *Client) ( context.Context,  ma.Multiaddr,  peer.ID) (*Conn, error) {
	// split /a/p2p-circuit/b into (/a, /p2p-circuit/b)
	,  := ma.SplitFunc(, func( ma.Component) bool {
		return .Protocol().Code == ma.P_CIRCUIT
	})

	// If the address contained no /p2p-circuit part, the second part is nil.
	if  == nil {
		return nil, fmt.Errorf("%s is not a relay address", )
	}

	if  == nil {
		return nil, fmt.Errorf("can't dial a p2p-circuit without specifying a relay: %s", )
	}

	 := peer.AddrInfo{ID: }

	// Strip the /p2p-circuit prefix from the destaddr so that we can pass the destination address
	// (if present) for active relays
	_,  = ma.SplitFirst()
	if  != nil {
		.Addrs = append(.Addrs, )
	}

	,  := peer.AddrInfoFromP2pAddr()
	if  != nil {
		return nil, fmt.Errorf("error parsing relay multiaddr '%s': %w", , )
	}

	// deduplicate active relay dials to the same peer
:
	.mx.Lock()
	,  := .activeDials[]
	if ! {
		 = &completion{ch: make(chan struct{}), relay: .ID}
		.activeDials[] = 
	}
	.mx.Unlock()

	if  {
		select {
		case <-.ch:
			if .err != nil {
				if .relay != .ID {
					// different relay, retry
					goto 
				}

				if !isRelayError(.err) {
					// not a relay protocol error, retry
					goto 
				}

				// don't try the same relay if it failed to connect with a protocol error
				return nil, fmt.Errorf("concurrent active dial through the same relay failed with a protocol error")
			}

			return nil, fmt.Errorf("concurrent active dial succeeded")

		case <-.Done():
			return nil, .Err()
		}
	}

	,  := .dialPeer(, *, )

	.mx.Lock()
	.err = 
	close(.ch)
	delete(.activeDials, )
	.mx.Unlock()

	return , 
}

func ( *Client) ( context.Context, ,  peer.AddrInfo) (*Conn, error) {
	log.Debugf("dialing peer %s through relay %s", .ID, .ID)

	if len(.Addrs) > 0 {
		.host.Peerstore().AddAddrs(.ID, .Addrs, peerstore.TempAddrTTL)
	}

	,  := context.WithTimeout(, DialRelayTimeout)
	defer ()
	,  := .host.NewStream(, .ID, proto.ProtoIDv2Hop)
	if  != nil {
		return nil, fmt.Errorf("error opening hop stream to relay: %w", )
	}
	return .connect(, )
}

func ( *Client) ( network.Stream,  peer.AddrInfo) (*Conn, error) {
	if  := .Scope().ReserveMemory(maxMessageSize, network.ReservationPriorityAlways);  != nil {
		.Reset()
		return nil, 
	}
	defer .Scope().ReleaseMemory(maxMessageSize)

	 := util.NewDelimitedReader(, maxMessageSize)
	 := util.NewDelimitedWriter()
	defer .Close()

	var  pbv2.HopMessage

	.Type = pbv2.HopMessage_CONNECT.Enum()
	.Peer = util.PeerInfoToPeerV2()

	.SetDeadline(time.Now().Add(DialTimeout))

	 := .WriteMsg(&)
	if  != nil {
		.Reset()
		return nil, 
	}

	.Reset()

	 = .ReadMsg(&)
	if  != nil {
		.Reset()
		return nil, 
	}

	.SetDeadline(time.Time{})

	if .GetType() != pbv2.HopMessage_STATUS {
		.Reset()
		return nil, newRelayError("unexpected relay response; not a status message (%d)", .GetType())
	}

	 := .GetStatus()
	if  != pbv2.Status_OK {
		.Reset()
		return nil, newRelayError("error opening relay circuit: %s (%d)", pbv2.Status_name[int32()], )
	}

	// check for a limit provided by the relay; if the limit is not nil, then this is a limited
	// relay connection and we mark the connection as transient.
	var  network.ConnStats
	if  := .GetLimit();  != nil {
		.Limited = true
		.Extra = make(map[interface{}]interface{})
		.Extra[StatLimitDuration] = time.Duration(.GetDuration()) * time.Second
		.Extra[StatLimitData] = .GetData()
	}

	return &Conn{stream: , remote: , stat: , client: }, nil
}