package client

import (
	
	
	
	
	
	

	
	
)

// SendToKDC performs network actions to send data to the KDC.
func ( *Client) ( []byte,  string) ([]byte, error) {
	var  []byte
	if .Config.LibDefaults.UDPPreferenceLimit == 1 {
		//1 means we should always use TCP
		,  := .sendKDCTCP(, )
		if  != nil {
			if ,  := .(messages.KRBError);  {
				return , 
			}
			return , fmt.Errorf("communication error with KDC via TCP: %v", )
		}
		return , nil
	}
	if len() <= .Config.LibDefaults.UDPPreferenceLimit {
		//Try UDP first, TCP second
		,  := .sendKDCUDP(, )
		if  != nil {
			if ,  := .(messages.KRBError);  && .ErrorCode != errorcode.KRB_ERR_RESPONSE_TOO_BIG {
				// Got a KRBError from KDC
				// If this is not a KRB_ERR_RESPONSE_TOO_BIG we will return immediately otherwise will try TCP.
				return , 
			}
			// Try TCP
			,  := .sendKDCTCP(, )
			if  != nil {
				if ,  := .(messages.KRBError);  {
					// Got a KRBError
					return , 
				}
				return , fmt.Errorf("failed to communicate with KDC. Attempts made with UDP (%v) and then TCP (%v)", , )
			}
			 = 
		}
		return , nil
	}
	//Try TCP first, UDP second
	,  := .sendKDCTCP(, )
	if  != nil {
		if ,  := .(messages.KRBError);  {
			// Got a KRBError from KDC so returning and not trying UDP.
			return , 
		}
		,  := .sendKDCUDP(, )
		if  != nil {
			if ,  := .(messages.KRBError);  {
				// Got a KRBError
				return , 
			}
			return , fmt.Errorf("failed to communicate with KDC. Attempts made with TCP (%v) and then UDP (%v)", , )
		}
	}
	return , nil
}

// sendKDCUDP sends bytes to the KDC via UDP.
func ( *Client) ( string,  []byte) ([]byte, error) {
	var  []byte
	, ,  := .Config.GetKDCs(, false)
	if  != nil {
		return , 
	}
	,  = dialSendUDP(, )
	if  != nil {
		return , 
	}
	return checkForKRBError()
}

// dialSendUDP establishes a UDP connection to a KDC.
func dialSendUDP( map[int]string,  []byte) ([]byte, error) {
	var  []string
	for  := 1;  <= len(); ++ {
		,  := net.ResolveUDPAddr("udp", [])
		if  != nil {
			 = append(, fmt.Sprintf("error resolving KDC address: %v", ))
			continue
		}

		,  := net.DialTimeout("udp", .String(), 5*time.Second)
		if  != nil {
			 = append(, fmt.Sprintf("error setting dial timeout on connection to %s: %v", [], ))
			continue
		}
		if  := .SetDeadline(time.Now().Add(5 * time.Second));  != nil {
			 = append(, fmt.Sprintf("error setting deadline on connection to %s: %v", [], ))
			continue
		}
		// conn is guaranteed to be a UDPConn
		,  := sendUDP(.(*net.UDPConn), )
		if  != nil {
			 = append(, fmt.Sprintf("error sneding to %s: %v", [], ))
			continue
		}
		return , nil
	}
	return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(, "; "))
}

// sendUDP sends bytes to connection over UDP.
func sendUDP( *net.UDPConn,  []byte) ([]byte, error) {
	var  []byte
	defer .Close()
	,  := .Write()
	if  != nil {
		return , fmt.Errorf("error sending to (%s): %v", .RemoteAddr().String(), )
	}
	 := make([]byte, 4096)
	, ,  := .ReadFrom()
	 = [:]
	if  != nil {
		return , fmt.Errorf("sending over UDP failed to %s: %v", .RemoteAddr().String(), )
	}
	if len() < 1 {
		return , fmt.Errorf("no response data from %s", .RemoteAddr().String())
	}
	return , nil
}

// sendKDCTCP sends bytes to the KDC via TCP.
func ( *Client) ( string,  []byte) ([]byte, error) {
	var  []byte
	, ,  := .Config.GetKDCs(, true)
	if  != nil {
		return , 
	}
	,  = dialSendTCP(, )
	if  != nil {
		return , 
	}
	return checkForKRBError()
}

// dialKDCTCP establishes a TCP connection to a KDC.
func dialSendTCP( map[int]string,  []byte) ([]byte, error) {
	var  []string
	for  := 1;  <= len(); ++ {
		,  := net.ResolveTCPAddr("tcp", [])
		if  != nil {
			 = append(, fmt.Sprintf("error resolving KDC address: %v", ))
			continue
		}

		,  := net.DialTimeout("tcp", .String(), 5*time.Second)
		if  != nil {
			 = append(, fmt.Sprintf("error setting dial timeout on connection to %s: %v", [], ))
			continue
		}
		if  := .SetDeadline(time.Now().Add(5 * time.Second));  != nil {
			 = append(, fmt.Sprintf("error setting deadline on connection to %s: %v", [], ))
			continue
		}
		// conn is guaranteed to be a TCPConn
		,  := sendTCP(.(*net.TCPConn), )
		if  != nil {
			 = append(, fmt.Sprintf("error sneding to %s: %v", [], ))
			continue
		}
		return , nil
	}
	return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(, "; "))
}

// sendTCP sends bytes to connection over TCP.
func sendTCP( *net.TCPConn,  []byte) ([]byte, error) {
	defer .Close()
	var  []byte
	// RFC 4120 7.2.2 specifies the first 4 bytes indicate the length of the message in big endian order.
	 := make([]byte, 4, 4)
	binary.BigEndian.PutUint32(, uint32(len()))
	 = append(, ...)

	,  := .Write()
	if  != nil {
		return , fmt.Errorf("error sending to KDC (%s): %v", .RemoteAddr().String(), )
	}

	 := make([]byte, 4, 4)
	_,  = .Read()
	if  != nil {
		return , fmt.Errorf("error reading response size header: %v", )
	}
	 := binary.BigEndian.Uint32()

	 := make([]byte, , )
	_,  = io.ReadFull(, )
	if  != nil {
		return , fmt.Errorf("error reading response: %v", )
	}
	if len() < 1 {
		return , fmt.Errorf("no response data from KDC %s", .RemoteAddr().String())
	}
	return , nil
}

// checkForKRBError checks if the response bytes from the KDC are a KRBError.
func checkForKRBError( []byte) ([]byte, error) {
	var  messages.KRBError
	if  := .Unmarshal();  == nil {
		return , 
	}
	return , nil
}