package autonat

import (
	
	
	

	
	
	
	

	
)

// NewAutoNATClient creates a fresh instance of an AutoNATClient
// If addrFunc is nil, h.Addrs will be used
func ( host.Host,  AddrFunc,  MetricsTracer) Client {
	if  == nil {
		 = .Addrs
	}
	return &client{h: , addrFunc: , mt: }
}

type client struct {
	h        host.Host
	addrFunc AddrFunc
	mt       MetricsTracer
}

// DialBack asks peer p to dial us back on all addresses returned by the addrFunc.
// It blocks until we've received a response from the peer.
//
// Note: A returned error Message_E_DIAL_ERROR does not imply that the server
// actually performed a dial attempt. Servers that run a version < v0.20.0 also
// return Message_E_DIAL_ERROR if the dial was skipped due to the dialPolicy.
func ( *client) ( context.Context,  peer.ID) error {
	,  := .h.NewStream(, , AutoNATProto)
	if  != nil {
		return 
	}

	if  := .Scope().SetService(ServiceName);  != nil {
		log.Debugf("error attaching stream to autonat service: %s", )
		.Reset()
		return 
	}

	if  := .Scope().ReserveMemory(maxMsgSize, network.ReservationPriorityAlways);  != nil {
		log.Debugf("error reserving memory for autonat stream: %s", )
		.Reset()
		return 
	}
	defer .Scope().ReleaseMemory(maxMsgSize)

	 := time.Now().Add(streamTimeout)
	if ,  := .Deadline();  {
		if .Before() {
			 = 
		}
	}

	.SetDeadline()
	// Might as well just reset the stream. Once we get to this point, we
	// don't care about being nice.
	defer .Close()

	 := pbio.NewDelimitedReader(, maxMsgSize)
	 := pbio.NewDelimitedWriter()

	 := newDialMessage(peer.AddrInfo{ID: .h.ID(), Addrs: .addrFunc()})
	if  := .WriteMsg();  != nil {
		.Reset()
		return 
	}

	var  pb.Message
	if  := .ReadMsg(&);  != nil {
		.Reset()
		return 
	}
	if .GetType() != pb.Message_DIAL_RESPONSE {
		.Reset()
		return fmt.Errorf("unexpected response: %s", .GetType().String())
	}

	 := .GetDialResponse().GetStatus()
	if .mt != nil {
		.mt.ReceivedDialResponse()
	}
	switch  {
	case pb.Message_OK:
		return nil
	default:
		return Error{Status: , Text: .GetDialResponse().GetStatusText()}
	}
}

// Error wraps errors signalled by AutoNAT services
type Error struct {
	Status pb.Message_ResponseStatus
	Text   string
}

func ( Error) () string {
	return fmt.Sprintf("AutoNAT error: %s (%s)", .Text, .Status.String())
}

// IsDialError returns true if the error was due to a dial back failure
func ( Error) () bool {
	return .Status == pb.Message_E_DIAL_ERROR
}

// IsDialRefused returns true if the error was due to a refusal to dial back
func ( Error) () bool {
	return .Status == pb.Message_E_DIAL_REFUSED
}

// IsDialError returns true if the AutoNAT peer signalled an error dialing back
func ( error) bool {
	,  := .(Error)
	return  && .IsDialError()
}

// IsDialRefused returns true if the AutoNAT peer signalled refusal to dial back
func ( error) bool {
	,  := .(Error)
	return  && .IsDialRefused()
}