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

package vnet

import (
	
	
	
	
	
	
	

	
)

const (
	maxReadQueueSize = 1024
)

var (
	errObsCannotBeNil       = errors.New("obs cannot be nil")
	errUseClosedNetworkConn = errors.New("use of closed network connection")
	errAddrNotUDPAddr       = errors.New("addr is not a net.UDPAddr")
	errLocAddr              = errors.New("something went wrong with locAddr")
	errAlreadyClosed        = errors.New("already closed")
	errNoRemAddr            = errors.New("no remAddr defined")
)

// vNet implements this
type connObserver interface {
	write(c Chunk) error
	onClosed(addr net.Addr)
	determineSourceIP(locIP, dstIP net.IP) net.IP
}

// UDPConn is the implementation of the Conn and PacketConn interfaces for UDP network connections.
// compatible with net.PacketConn and net.Conn
type UDPConn struct {
	locAddr   *net.UDPAddr // read-only
	remAddr   *net.UDPAddr // read-only
	obs       connObserver // read-only
	readCh    chan Chunk   // thread-safe
	closed    bool         // requires mutex
	mu        sync.Mutex   // to mutex closed flag
	readTimer *time.Timer  // thread-safe
}

var _ transport.UDPConn = &UDPConn{}

func newUDPConn(,  *net.UDPAddr,  connObserver) (*UDPConn, error) {
	if  == nil {
		return nil, errObsCannotBeNil
	}

	return &UDPConn{
		locAddr:   ,
		remAddr:   ,
		obs:       ,
		readCh:    make(chan Chunk, maxReadQueueSize),
		readTimer: time.NewTimer(time.Duration(math.MaxInt64)),
	}, nil
}

// Close closes the connection.
// Any blocked ReadFrom or WriteTo operations will be unblocked and return errors.
func ( *UDPConn) () error {
	.mu.Lock()
	defer .mu.Unlock()

	if .closed {
		return errAlreadyClosed
	}
	.closed = true
	close(.readCh)

	.obs.onClosed(.locAddr)
	return nil
}

// LocalAddr returns the local network address.
func ( *UDPConn) () net.Addr {
	return .locAddr
}

// RemoteAddr returns the remote network address.
func ( *UDPConn) () net.Addr {
	return .remAddr
}

// SetDeadline sets the read and write deadlines associated
// with the connection. It is equivalent to calling both
// SetReadDeadline and SetWriteDeadline.
//
// A deadline is an absolute time after which I/O operations
// fail with a timeout (see type Error) instead of
// blocking. The deadline applies to all future and pending
// I/O, not just the immediately following call to ReadFrom or
// WriteTo. After a deadline has been exceeded, the connection
// can be refreshed by setting a deadline in the future.
//
// An idle timeout can be implemented by repeatedly extending
// the deadline after successful ReadFrom or WriteTo calls.
//
// A zero value for t means I/O operations will not time out.
func ( *UDPConn) ( time.Time) error {
	return .SetReadDeadline()
}

// SetReadDeadline sets the deadline for future ReadFrom calls
// and any currently-blocked ReadFrom call.
// A zero value for t means ReadFrom will not time out.
func ( *UDPConn) ( time.Time) error {
	var  time.Duration
	var  time.Time
	if  ==  {
		 = time.Duration(math.MaxInt64)
	} else {
		 = time.Until()
	}
	.readTimer.Reset()
	return nil
}

// SetWriteDeadline sets the deadline for future WriteTo calls
// and any currently-blocked WriteTo call.
// Even if write times out, it may return n > 0, indicating that
// some of the data was successfully written.
// A zero value for t means WriteTo will not time out.
func ( *UDPConn) (time.Time) error {
	// Write never blocks.
	return nil
}

// Read reads data from the connection.
// Read can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetReadDeadline.
func ( *UDPConn) ( []byte) (int, error) {
	, ,  := .ReadFrom()
	return , 
}

// ReadFrom reads a packet from the connection,
// copying the payload into p. It returns the number of
// bytes copied into p and the return address that
// was on the packet.
// It returns the number of bytes read (0 <= n <= len(p))
// and any error encountered. Callers should always process
// the n > 0 bytes returned before considering the error err.
// ReadFrom can be made to time out and return
// an Error with Timeout() == true after a fixed time limit;
// see SetDeadline and SetReadDeadline.
func ( *UDPConn) ( []byte) ( int,  net.Addr,  error) {
:
	for {
		select {
		case ,  := <-.readCh:
			if ! {
				break 
			}
			var  error
			 := copy(, .UserData())
			 := .SourceAddr()
			if  < len(.UserData()) {
				 = io.ErrShortBuffer
			}

			if .remAddr != nil {
				if .String() != .remAddr.String() {
					break // discard (shouldn't happen)
				}
			}
			return , , 

		case <-.readTimer.C:
			return 0, nil, &net.OpError{
				Op:   "read",
				Net:  .locAddr.Network(),
				Addr: .locAddr,
				Err:  newTimeoutError("i/o timeout"),
			}
		}
	}

	return 0, nil, &net.OpError{
		Op:   "read",
		Net:  .locAddr.Network(),
		Addr: .locAddr,
		Err:  errUseClosedNetworkConn,
	}
}

// ReadFromUDP acts like ReadFrom but returns a UDPAddr.
func ( *UDPConn) ( []byte) (int, *net.UDPAddr, error) {
	, ,  := .ReadFrom()

	,  := .(*net.UDPAddr)
	if ! {
		return -1, nil, fmt.Errorf("%w: %s", transport.ErrNotUDPAddress, )
	}

	return , , 
}

// ReadMsgUDP reads a message from c, copying the payload into b and
// the associated out-of-band data into oob. It returns the number of
// bytes copied into b, the number of bytes copied into oob, the flags
// that were set on the message and the source address of the message.
//
// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
// used to manipulate IP-level socket options in oob.
func ( *UDPConn) ([]byte, []byte) (, ,  int,  *net.UDPAddr,  error) {
	return -1, -1, -1, nil, transport.ErrNotSupported
}

// Write writes data to the connection.
// Write can be made to time out and return an Error with Timeout() == true
// after a fixed time limit; see SetDeadline and SetWriteDeadline.
func ( *UDPConn) ( []byte) (int, error) {
	if .remAddr == nil {
		return 0, errNoRemAddr
	}

	return .WriteTo(, .remAddr)
}

// WriteTo writes a packet with payload p to addr.
// WriteTo can be made to time out and return
// an Error with Timeout() == true after a fixed time limit;
// see SetDeadline and SetWriteDeadline.
// On packet-oriented connections, write timeouts are rare.
func ( *UDPConn) ( []byte,  net.Addr) ( int,  error) {
	,  := .(*net.UDPAddr)
	if ! {
		return 0, errAddrNotUDPAddr
	}

	 := .obs.determineSourceIP(.locAddr.IP, .IP)
	if  == nil {
		return 0, errLocAddr
	}
	 := &net.UDPAddr{
		IP:   ,
		Port: .locAddr.Port,
	}

	 := newChunkUDP(, )
	.userData = make([]byte, len())
	copy(.userData, )
	if  := .obs.write();  != nil {
		return 0, 
	}
	return len(), nil
}

// WriteToUDP acts like WriteTo but takes a UDPAddr.
func ( *UDPConn) ( []byte,  *net.UDPAddr) (int, error) {
	return .WriteTo(, )
}

// WriteMsgUDP writes a message to addr via c if c isn't connected, or
// to c's remote address if c is connected (in which case addr must be
// nil). The payload is copied from b and the associated out-of-band
// data is copied from oob. It returns the number of payload and
// out-of-band bytes written.
//
// The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be
// used to manipulate IP-level socket options in oob.
func ( *UDPConn) ([]byte, []byte, *net.UDPAddr) (,  int,  error) {
	return -1, -1, transport.ErrNotSupported
}

// SetReadBuffer sets the size of the operating system's
// receive buffer associated with the connection.
func ( *UDPConn) (int) error {
	return transport.ErrNotSupported
}

// SetWriteBuffer sets the size of the operating system's
// transmit buffer associated with the connection.
func ( *UDPConn) (int) error {
	return transport.ErrNotSupported
}

func ( *UDPConn) ( Chunk) {
	.mu.Lock()
	defer .mu.Unlock()

	if .closed {
		return
	}

	select {
	case .readCh <- :
	default:
	}
}