// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package stun

import (
	
	
	
	
	

	
)

const (
	familyIPv4 uint16 = 0x01
	familyIPv6 uint16 = 0x02
)

// XORMappedAddress implements XOR-MAPPED-ADDRESS attribute.
//
// RFC 5389 Section 15.2
type XORMappedAddress struct {
	IP   net.IP
	Port int
}

func ( XORMappedAddress) () string {
	return net.JoinHostPort(.IP.String(), strconv.Itoa(.Port))
}

// isIPv4 returns true if ip with len of net.IPv6Len seems to be ipv4.
func isIPv4( net.IP) bool {
	// Optimized for performance. Copied from net.IP.To4.
	return isZeros([0:10]) && [10] == 0xff && [11] == 0xff
}

// Is p all zeros?
func isZeros( net.IP) bool {
	for  := 0;  < len(); ++ {
		if [] != 0 {
			return false
		}
	}
	return true
}

// ErrBadIPLength means that len(IP) is not net.{IPv6len,IPv4len}.
var ErrBadIPLength = errors.New("invalid length of IP value")

// AddToAs adds XOR-MAPPED-ADDRESS value to m as t attribute.
func ( XORMappedAddress) ( *Message,  AttrType) error {
	var (
		 = familyIPv4
		     = .IP
	)
	if len(.IP) == net.IPv6len {
		if isIPv4() {
			 = [12:16] // like in ip.To4()
		} else {
			 = familyIPv6
		}
	} else if len() != net.IPv4len {
		return ErrBadIPLength
	}
	 := make([]byte, 32+128)
	[0] = 0 // first 8 bits are zeroes
	 := make([]byte, net.IPv6len)
	copy([4:], .TransactionID[:])
	bin.PutUint32([0:4], magicCookie)
	bin.PutUint16([0:2], )
	bin.PutUint16([2:4], uint16(.Port^magicCookie>>16))
	xor.XorBytes([4:4+len()], , )
	.Add(, [:4+len()])
	return nil
}

// AddTo adds XOR-MAPPED-ADDRESS to m. Can return ErrBadIPLength
// if len(a.IP) is invalid.
func ( XORMappedAddress) ( *Message) error {
	return .AddToAs(, AttrXORMappedAddress)
}

// GetFromAs decodes XOR-MAPPED-ADDRESS attribute value in message
// getting it as for t type.
func ( *XORMappedAddress) ( *Message,  AttrType) error {
	,  := .Get()
	if  != nil {
		return 
	}
	 := bin.Uint16([0:2])
	if  != familyIPv6 &&  != familyIPv4 {
		return newDecodeErr("xor-mapped address", "family",
			fmt.Sprintf("bad value %d", ),
		)
	}
	 := net.IPv4len
	if  == familyIPv6 {
		 = net.IPv6len
	}
	// Ensuring len(a.IP) == ipLen and reusing a.IP.
	if len(.IP) <  {
		.IP = .IP[:cap(.IP)]
		for len(.IP) <  {
			.IP = append(.IP, 0)
		}
	}
	.IP = .IP[:]
	for  := range .IP {
		.IP[] = 0
	}
	if len() <= 4 {
		return io.ErrUnexpectedEOF
	}
	if  := CheckOverflow(, len([4:]), len(.IP));  != nil {
		return 
	}
	.Port = int(bin.Uint16([2:4])) ^ (magicCookie >> 16)
	 := make([]byte, 4+TransactionIDSize)
	bin.PutUint32([0:4], magicCookie)
	copy([4:], .TransactionID[:])
	xor.XorBytes(.IP, [4:], )
	return nil
}

// GetFrom decodes XOR-MAPPED-ADDRESS attribute in message and returns
// error if any. While decoding, a.IP is reused if possible and can be
// rendered to invalid state (e.g. if a.IP was set to IPv6 and then
// IPv4 value were decoded into it), be careful.
//
// Example:
//
//	expectedIP := net.ParseIP("213.141.156.236")
//	expectedIP.String() // 213.141.156.236, 16 bytes, first 12 of them are zeroes
//	expectedPort := 21254
//	addr := &XORMappedAddress{
//	  IP:   expectedIP,
//	  Port: expectedPort,
//	}
//	// addr were added to message that is decoded as newMessage
//	// ...
//
//	addr.GetFrom(newMessage)
//	addr.IP.String()    // 213.141.156.236, net.IPv4Len
//	expectedIP.String() // d58d:9cec::ffff:d58d:9cec, 16 bytes, first 4 are IPv4
//	// now we have len(expectedIP) = 16 and len(addr.IP) = 4.
func ( *XORMappedAddress) ( *Message) error {
	return .GetFromAs(, AttrXORMappedAddress)
}