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

package vnet

import (
	
	
	
	
	
	
)

type tcpFlag uint8

const (
	tcpFIN tcpFlag = 0x01
	tcpSYN tcpFlag = 0x02
	tcpRST tcpFlag = 0x04
	tcpPSH tcpFlag = 0x08
	tcpACK tcpFlag = 0x10
)

func ( tcpFlag) () string {
	var  []string
	if &tcpFIN != 0 {
		 = append(, "FIN")
	}
	if &tcpSYN != 0 {
		 = append(, "SYN")
	}
	if &tcpRST != 0 {
		 = append(, "RST")
	}
	if &tcpPSH != 0 {
		 = append(, "PSH")
	}
	if &tcpACK != 0 {
		 = append(, "ACK")
	}

	return strings.Join(, "-")
}

// Generate a base36-encoded unique tag
// See: https://play.golang.org/p/0ZaAID1q-HN
var assignChunkTag = func() func() string { //nolint:gochecknoglobals
	var  uint64

	return func() string {
		 := atomic.AddUint64(&, 1)
		return strconv.FormatUint(, 36)
	}
}()

// Chunk represents a packet passed around in the vnet
type Chunk interface {
	setTimestamp() time.Time                 // used by router
	getTimestamp() time.Time                 // used by router
	getSourceIP() net.IP                     // used by router
	getDestinationIP() net.IP                // used by router
	setSourceAddr(address string) error      // used by nat
	setDestinationAddr(address string) error // used by nat

	SourceAddr() net.Addr
	DestinationAddr() net.Addr
	UserData() []byte
	Tag() string
	Clone() Chunk
	Network() string // returns "udp" or "tcp"
	String() string
}

type chunkIP struct {
	timestamp     time.Time
	sourceIP      net.IP
	destinationIP net.IP
	tag           string
}

func ( *chunkIP) () time.Time {
	.timestamp = time.Now()
	return .timestamp
}

func ( *chunkIP) () time.Time {
	return .timestamp
}

func ( *chunkIP) () net.IP {
	return .destinationIP
}

func ( *chunkIP) () net.IP {
	return .sourceIP
}

func ( *chunkIP) () string {
	return .tag
}

type chunkUDP struct {
	chunkIP
	sourcePort      int
	destinationPort int
	userData        []byte
}

func newChunkUDP(,  *net.UDPAddr) *chunkUDP {
	return &chunkUDP{
		chunkIP: chunkIP{
			sourceIP:      .IP,
			destinationIP: .IP,
			tag:           assignChunkTag(),
		},
		sourcePort:      .Port,
		destinationPort: .Port,
	}
}

func ( *chunkUDP) () net.Addr {
	return &net.UDPAddr{
		IP:   .sourceIP,
		Port: .sourcePort,
	}
}

func ( *chunkUDP) () net.Addr {
	return &net.UDPAddr{
		IP:   .destinationIP,
		Port: .destinationPort,
	}
}

func ( *chunkUDP) () []byte {
	return .userData
}

func ( *chunkUDP) () Chunk {
	var  []byte
	if .userData != nil {
		 = make([]byte, len(.userData))
		copy(, .userData)
	}

	return &chunkUDP{
		chunkIP: chunkIP{
			timestamp:     .timestamp,
			sourceIP:      .sourceIP,
			destinationIP: .destinationIP,
			tag:           .tag,
		},
		sourcePort:      .sourcePort,
		destinationPort: .destinationPort,
		userData:        ,
	}
}

func ( *chunkUDP) () string {
	return udp
}

func ( *chunkUDP) () string {
	 := .SourceAddr()
	 := .DestinationAddr()
	return fmt.Sprintf("%s chunk %s %s => %s",
		.Network(),
		.tag,
		.String(),
		.String(),
	)
}

func ( *chunkUDP) ( string) error {
	,  := net.ResolveUDPAddr(udp, )
	if  != nil {
		return 
	}
	.sourceIP = .IP
	.sourcePort = .Port
	return nil
}

func ( *chunkUDP) ( string) error {
	,  := net.ResolveUDPAddr(udp, )
	if  != nil {
		return 
	}
	.destinationIP = .IP
	.destinationPort = .Port
	return nil
}

type chunkTCP struct {
	chunkIP
	sourcePort      int
	destinationPort int
	flags           tcpFlag // control bits
	userData        []byte  // only with PSH flag
	// seq             uint32  // always starts with 0
	// ack             uint32  // always starts with 0
}

func newChunkTCP(,  *net.TCPAddr,  tcpFlag) *chunkTCP {
	return &chunkTCP{
		chunkIP: chunkIP{
			sourceIP:      .IP,
			destinationIP: .IP,
			tag:           assignChunkTag(),
		},
		sourcePort:      .Port,
		destinationPort: .Port,
		flags:           ,
	}
}

func ( *chunkTCP) () net.Addr {
	return &net.TCPAddr{
		IP:   .sourceIP,
		Port: .sourcePort,
	}
}

func ( *chunkTCP) () net.Addr {
	return &net.TCPAddr{
		IP:   .destinationIP,
		Port: .destinationPort,
	}
}

func ( *chunkTCP) () []byte {
	return .userData
}

func ( *chunkTCP) () Chunk {
	 := make([]byte, len(.userData))
	copy(, .userData)

	return &chunkTCP{
		chunkIP: chunkIP{
			timestamp:     .timestamp,
			sourceIP:      .sourceIP,
			destinationIP: .destinationIP,
		},
		sourcePort:      .sourcePort,
		destinationPort: .destinationPort,
		userData:        ,
	}
}

func ( *chunkTCP) () string {
	return "tcp"
}

func ( *chunkTCP) () string {
	 := .SourceAddr()
	 := .DestinationAddr()
	return fmt.Sprintf("%s %s chunk %s %s => %s",
		.Network(),
		.flags.String(),
		.tag,
		.String(),
		.String(),
	)
}

func ( *chunkTCP) ( string) error {
	,  := net.ResolveTCPAddr("tcp", )
	if  != nil {
		return 
	}
	.sourceIP = .IP
	.sourcePort = .Port
	return nil
}

func ( *chunkTCP) ( string) error {
	,  := net.ResolveTCPAddr("tcp", )
	if  != nil {
		return 
	}
	.destinationIP = .IP
	.destinationPort = .Port
	return nil
}