// Copyright 2024 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package nftables provides the interface to process packets through a
// netfilter (nf) ruleset and maintain/modify the ruleset accordingly. The
// package implements a bytecode nftables interpreter that accepts an nf ruleset
// (with the accompanying assembly and/or machine code) outputted from
// the nftables binary, along with network packets (as a stack.PacketBuffer) to
// filter, modify, and evaluate packets.
// We support a subset of the functionality of the nftables binary.
// The package is not yet thread-safe.
//
// To use the package, construct a ruleset using the official nft binary and
// then pass the ruleset as a string (with flag --debug=netlink on to get the
// assembly) to InterpretRuleset command. The interpreter has strict syntax and
// only accepts rulesets outputted directly from the nftables binary.
// Maintaining and modifying the ruleset is done through the other public
// functions (Add.., Flush.., etc).
//
// To evaluate a packet through the ruleset, call the EvaluatePacket function
// with the packet and the hook to evaluate at. The EvaluatePacket function
// returns the verdict issued by the ruleset and the packet modified by the
// ruleset (if the verdict is not Drop).
//
// Inner Headers and Tunneling Headers are not supported.
//
// Finally, note that error checking for parameters/inputs is only guaranteed
// for public functions. Most private functions are assumed to have
// valid/prechecked inputs.
package nftables

import (
	"fmt"
	"slices"
	"time"

	"gvisor.dev/gvisor/pkg/abi/linux"
	"gvisor.dev/gvisor/pkg/atomicbitops"
	"gvisor.dev/gvisor/pkg/marshal"
	"gvisor.dev/gvisor/pkg/marshal/primitive"
	"gvisor.dev/gvisor/pkg/rand"
	"gvisor.dev/gvisor/pkg/sentry/socket/netlink/nlmsg"
	"gvisor.dev/gvisor/pkg/syserr"
	"gvisor.dev/gvisor/pkg/tcpip"
	"gvisor.dev/gvisor/pkg/tcpip/stack"
)

// TODO(b/345684870): Make the nftables package thread-safe! Must be done before
// the package is used in production.

// enableNFTables is a flag that indicates whether NFTables is enabled.
var enableNFTables atomicbitops.Bool

// EnableNFTables enables NFTables.
func EnableNFTables() {
	enableNFTables.Store(true)
}

// IsNFTablesEnabled returns true if NFTables is enabled.
func IsNFTablesEnabled() bool {
	return enableNFTables.Load()
}

// Defines general constants for the nftables interpreter.
const (

	// Total bytes for the registers in the nftables interpreter.
	registersByteSize = 64

	// Maximum number of nested jumps allowed, corresponding to
	// NFT_JUMP_STACK_SIZE in include/net/netfilter/nf_tables.h.
	nestedJumpLimit = 16

	// Limit (exclusive) for number of buts that can be shifted for non-boolean
	// bitwise operations.
	bitshiftLimit = 32
)

// addressFamilyProtocols maps address families to their protocol number.
var addressFamilyProtocols = map[stack.AddressFamily]uint8{
	stack.Unspec: linux.NFPROTO_UNSPEC,
	stack.IP:     linux.NFPROTO_IPV4,
	stack.IP6:    linux.NFPROTO_IPV6,
	stack.Inet:   linux.NFPROTO_INET,
	stack.Arp:    linux.NFPROTO_ARP,
	stack.Bridge: linux.NFPROTO_BRIDGE,
	stack.Netdev: linux.NFPROTO_NETDEV,
}

// AfProtocol returns the protocol number for the address family.
func AfProtocol(f stack.AddressFamily) uint8 {
	if protocol, ok := addressFamilyProtocols[f]; ok {
		return protocol
	}
	panic(fmt.Sprintf("invalid address family: %d", int(f)))
}

// validateAddressFamily ensures the family address is valid (within bounds).
func validateAddressFamily(family stack.AddressFamily) *syserr.AnnotatedError {
	// From net/netfilter/nf_tables_api.c:nf_tables_newtable
	if family < 0 || family >= stack.NumAFs {
		return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("invalid address family: %d", int(family)))
	}

	return nil
}

// supportedHooks maps each address family to its supported hooks.
var supportedHooksList [stack.NumAFs][]stack.NFHook = [stack.NumAFs][]stack.NFHook{
	stack.IP:     {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress},
	stack.IP6:    {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress},
	stack.Inet:   {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress},
	stack.Arp:    {stack.NFInput, stack.NFOutput},
	stack.Bridge: {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress},
	stack.Netdev: {stack.NFIngress, stack.NFEgress},
}

// supportedHooks maps each address family to its supported hooks.
var supportedHooks [stack.NumAFs][stack.NFNumHooks]bool = func() [stack.NumAFs][stack.NFNumHooks]bool {
	var supportedHooks [stack.NumAFs][stack.NFNumHooks]bool
	for family, hooks := range supportedHooksList {
		for _, hook := range hooks {
			supportedHooks[family][hook] = true
		}
	}
	return supportedHooks
}()

// supportedLinuxHooks maps each address family to its supported hooks for each base chain type.
// From net/netfilter/nft_chain_filter.c, net/netfilter/nft_chain_nat.c, net/netfilter/nft_chain_route.c,
var supportedLinuxHooks = map[stack.AddressFamily]map[BaseChainType][]stack.NFHook{
	stack.IP: {
		BaseChainTypeFilter: {linux.NF_INET_LOCAL_IN, linux.NF_INET_LOCAL_OUT, linux.NF_INET_FORWARD, linux.NF_INET_PRE_ROUTING, linux.NF_INET_POST_ROUTING},
		BaseChainTypeNat:    {linux.NF_INET_PRE_ROUTING, linux.NF_INET_POST_ROUTING, linux.NF_INET_LOCAL_OUT, linux.NF_INET_LOCAL_IN},
		BaseChainTypeRoute:  {linux.NF_INET_LOCAL_OUT},
	},
	stack.IP6: {
		BaseChainTypeFilter: {linux.NF_INET_LOCAL_IN, linux.NF_INET_LOCAL_OUT, linux.NF_INET_FORWARD, linux.NF_INET_PRE_ROUTING, linux.NF_INET_POST_ROUTING},
		BaseChainTypeNat:    {linux.NF_INET_PRE_ROUTING, linux.NF_INET_POST_ROUTING, linux.NF_INET_LOCAL_OUT, linux.NF_INET_LOCAL_IN},
		BaseChainTypeRoute:  {linux.NF_INET_LOCAL_OUT},
	},
	stack.Inet: {
		BaseChainTypeFilter: {linux.NF_INET_LOCAL_IN, linux.NF_INET_LOCAL_OUT, linux.NF_INET_FORWARD, linux.NF_INET_PRE_ROUTING, linux.NF_INET_POST_ROUTING},
		BaseChainTypeNat:    {linux.NF_INET_PRE_ROUTING, linux.NF_INET_POST_ROUTING, linux.NF_INET_LOCAL_OUT, linux.NF_INET_LOCAL_IN},
		BaseChainTypeRoute:  {linux.NF_INET_LOCAL_OUT},
	},
	stack.Arp: {
		BaseChainTypeFilter: {linux.NF_ARP_IN, linux.NF_ARP_OUT},
	},
	stack.Bridge: {
		BaseChainTypeFilter: {linux.NF_BR_PRE_ROUTING, linux.NF_BR_LOCAL_IN, linux.NF_BR_FORWARD, linux.NF_BR_LOCAL_OUT, linux.NF_BR_POST_ROUTING},
	},
	stack.Netdev: {
		BaseChainTypeFilter: {linux.NF_NETDEV_INGRESS, linux.NF_NETDEV_EGRESS},
	},
}

// validateHook ensures the hook is within bounds and supported for the given
// address family.
func validateHook(hook stack.NFHook, family stack.AddressFamily) *syserr.AnnotatedError {
	if hook >= stack.NFNumHooks {
		return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("invalid hook: %d", int(hook)))
	}
	if supportedHooks[family][hook] {
		return nil
	}
	// The hook is not supported for the given address family.
	return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("hook %d is not supported for address family %d", int(hook), int(family)))
}

// ValidLinuxHook ensures the hook is within bounds and supported for the
// given address family and base chain type.
func ValidLinuxHook(family stack.AddressFamily, bcType BaseChainType, hook uint32) bool {
	if hook >= linux.NFT_MAX_HOOKS {
		return false
	}

	typeToSupportedHooks, ok := supportedLinuxHooks[family]
	if !ok {
		return false
	}

	supportedHooks, ok := typeToSupportedHooks[bcType]
	if !ok {
		return false
	}

	return slices.Contains(supportedHooks, stack.NFHook(hook))
}

// FamilyHookKey is a struct that represents a stack.AddressFamily and linux hook pair.
type FamilyHookKey struct {
	Family stack.AddressFamily
	Hook   uint32
}

// linuxHookToStackHook maps the linux hook constants to the stack hook constants.
var linuxHookToStackHook = map[FamilyHookKey]stack.NFHook{
	{Family: stack.IP, Hook: linux.NF_INET_LOCAL_IN}:     stack.NFInput,
	{Family: stack.IP, Hook: linux.NF_INET_LOCAL_OUT}:    stack.NFOutput,
	{Family: stack.IP, Hook: linux.NF_INET_FORWARD}:      stack.NFForward,
	{Family: stack.IP, Hook: linux.NF_INET_PRE_ROUTING}:  stack.NFPrerouting,
	{Family: stack.IP, Hook: linux.NF_INET_POST_ROUTING}: stack.NFPostrouting,

	{Family: stack.IP6, Hook: linux.NF_INET_LOCAL_IN}:     stack.NFInput,
	{Family: stack.IP6, Hook: linux.NF_INET_LOCAL_OUT}:    stack.NFOutput,
	{Family: stack.IP6, Hook: linux.NF_INET_FORWARD}:      stack.NFForward,
	{Family: stack.IP6, Hook: linux.NF_INET_PRE_ROUTING}:  stack.NFPrerouting,
	{Family: stack.IP6, Hook: linux.NF_INET_POST_ROUTING}: stack.NFPostrouting,

	{Family: stack.Inet, Hook: linux.NF_INET_LOCAL_IN}:     stack.NFInput,
	{Family: stack.Inet, Hook: linux.NF_INET_LOCAL_OUT}:    stack.NFOutput,
	{Family: stack.Inet, Hook: linux.NF_INET_FORWARD}:      stack.NFForward,
	{Family: stack.Inet, Hook: linux.NF_INET_PRE_ROUTING}:  stack.NFPrerouting,
	{Family: stack.Inet, Hook: linux.NF_INET_POST_ROUTING}: stack.NFPostrouting,

	{Family: stack.Arp, Hook: linux.NF_ARP_IN}:  stack.NFInput,
	{Family: stack.Arp, Hook: linux.NF_ARP_OUT}: stack.NFOutput,

	{Family: stack.Bridge, Hook: linux.NF_BR_PRE_ROUTING}:  stack.NFPrerouting,
	{Family: stack.Bridge, Hook: linux.NF_BR_LOCAL_IN}:     stack.NFInput,
	{Family: stack.Bridge, Hook: linux.NF_BR_FORWARD}:      stack.NFForward,
	{Family: stack.Bridge, Hook: linux.NF_BR_LOCAL_OUT}:    stack.NFOutput,
	{Family: stack.Bridge, Hook: linux.NF_BR_POST_ROUTING}: stack.NFPostrouting,

	{Family: stack.Netdev, Hook: linux.NF_NETDEV_INGRESS}: stack.NFIngress,
	{Family: stack.Netdev, Hook: linux.NF_NETDEV_EGRESS}:  stack.NFEgress,
}

// StackHook returns the stack hook for the given linux hook.
func StackHook(family stack.AddressFamily, hook uint32) (stack.NFHook, *syserr.AnnotatedError) {
	if hook, ok := linuxHookToStackHook[FamilyHookKey{Family: family, Hook: hook}]; ok {
		return hook, nil
	}

	return stack.NFHook(0), syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("invalid linux hook: %d", int(hook)))
}

// NFTables represents the nftables state for all address families.
// Note: unlike iptables, nftables doesn't start with any initialized tables.
type NFTables struct {
	filters            [stack.NumAFs]*addressFamilyFilter  // Filters for each address family.
	ip4InetBaseChains  [stack.NFNumHooks]hookFunctionStack // List of base chains for each hook in the IPv4-inet family.
	ip6InetBaseChains  [stack.NFNumHooks]hookFunctionStack // List of base chains for each hook in the IPv6-inet family.
	clock              tcpip.Clock                         // Clock for timing evaluations.
	startTime          time.Time                           // Time NFTables object was created.
	rng                rand.RNG                            // Random number generator.
	tableHandleCounter atomicbitops.Uint64                 // Table handle counter.
	genid              uint32                              // Generation ID for nftables.
	connTrack          *stack.ConnTrack                    // Conntrack object for tracking connections.
	connTrackReaper    tcpip.Timer                         // Reaper timer for reaping timed out connections.
	natEnabled         bool                                // Whether the nat module is enabled.
	stack              *stack.Stack                        // Parent stack object.
}

// Ensures NFTables implements the NFTablesInterface.
var _ stack.NFTablesInterface = (*NFTables)(nil)

// addressFamilyFilter represents the nftables state for a specific address
// family.
type addressFamilyFilter struct {
	// family is the address family of the filter.
	family stack.AddressFamily

	// nftState is the NFTables object the filter belongs to.
	nftState *NFTables

	// tables is a map of tables for each address family.
	tables map[string]*Table

	// tableHandles is a map of table handles (ids) to tables for a given address family.
	tableHandles map[uint64]*Table

	// hfStacks is a map of hook function stacks (slice of base chains for a
	// given hook ordered by priority).
	hfStacks map[stack.NFHook]*hookFunctionStack
}

// Table represents a single table as a collection of named chains.
// Note: as tables are simply collections of chains, evaluations aren't done on
// the table-level and instead are done on the chain- and hook- level.
type Table struct {
	// name is the name of the table.
	name string

	// afFilter is the address family filter that the table belongs to.
	// Note: this is used to reference the hook function stack as necessary.
	afFilter *addressFamilyFilter

	// chains is a map of chains for each table.
	chains map[string]*Chain

	// chainHandles is a map of chain handles (ids) to chains for a given table.
	chainHandles map[uint64]*Chain

	// chainIDs is a map of temporary transaction chain IDs to chains for a given table.
	chainIDs map[uint32]*Chain

	// flagSet is the set of optional flags for the table.
	// Note: currently nftables only has the single Dormant flag.
	flagSet map[TableFlag]struct{}

	// sets is a map of sets for each table.
	sets       map[string]*nftSet
	setHandles map[uint64]*nftSet

	// handleCounter is the counter for chain and rule handles.
	handleCounter atomicbitops.Uint64

	// handle is the id of the table.
	handle uint64

	// owner is the port id of the table's owner, if it is specified.
	owner uint32

	// userData is the user-specified metadata for the table. This is not used
	// by the kernel, but rather userspace applications like nft binary.
	userData []byte
}

// TableInfo represents data between an AFfilter and a Table.
type TableInfo struct {
	Name   string
	Handle uint64
}

// HookInfo represents data retrieved from the NFTA_CHAIN_HOOK attribute.
type HookInfo struct {
	HookNum   uint32
	Priority  int32
	ChainType BaseChainType
}

// hookFunctionStack represents the list of base chains for a specific hook.
// The stack is ordered by priority and built as chains are added to tables.
type hookFunctionStack struct {
	baseChains    []*Chain
	natBaseChains []*Chain
}

// TableFlag is a flag for a table as supported by the nftables binary.
type TableFlag int

const (
	// TableFlagDormant is set if the table is dormant. Dormant tables are not
	// evaluated by the kernel.
	TableFlagDormant TableFlag = iota
	// TableFlagOwner is set if the table has an owner. The owner is the port
	// where the table is created.
	TableFlagOwner
)

// Chain represents a single chain as a list of rules.
// A chain can be either a base chain or a regular chain.
// Base chains (aka hook functions) contain a hook which attaches it directly to
// the netfilter pipeline to be called whenever the hook is encountered.
// Regular chains have a nil hook and must be called by base chains for
// evaluation.
type Chain struct {
	// name is the name of the chain.
	name string

	// table is a pointer to the table that the chain belongs to.
	// Note: this is tracked to check if the table is dormant.
	table *Table

	// handle is the id of the chain.
	handle uint64

	// flags is the set of optional flags for the chain.
	flags uint8

	// baseChainInfo is the base chain info for the chain if it is a base chain.
	// Otherwise, it is nil.
	baseChainInfo *BaseChainInfo

	// rules is a list of rules for the chain.
	rules []*Rule

	// handleToRule is a map of rule handles to rules for the chain.
	handleToRule map[uint64]*Rule

	// userData is the user-specified metadata for the chain. This is not used
	// by the kernel, but rather userspace applications like nft binary.
	userData []byte

	// From net/netfilter/nf_tables_api.c: nft_data_hold
	// chainUse is the number of jump references to this chain.
	chainUse uint32

	// comment is the optional comment for the table.
	comment string
}

// TODO(b/345684870): BaseChainInfo Implementation. Encode how bcType affects
// evaluation of a packet.

// BaseChainInfo stores hook-related info for attaching a chain to the pipeline.
type BaseChainInfo struct {
	// LINT.IfChange(base_chain_info)

	// BcType is the base chain type of the chain (filter, nat, route).
	BcType BaseChainType

	// Hook is the hook to attach the chain to in the netfilter pipeline
	Hook stack.NFHook

	// LinuxHookNum is the linux hook number for the hook. Used for filling out the information
	// for a retrieved base chain.
	LinuxHookNum uint32

	// Priority determines the order in which base chains with the same hook are
	// traversed. Each priority is associated with a signed integer priority value
	// which rank base chains in ascending order. See the Priority struct below
	// for more details.
	Priority Priority

	// Device is an optional parameter and is mainly relevant to the bridge and
	// netdev address families. It specifies the device associated with chain.
	Device string

	// PolicyDrop determines whether to change the chain's policy from Accept to
	// Drop. The policy of a chain is the verdict to issue when a packet is not
	// explicitly accepted or rejected by the rules. A chain's policy defaults to
	// Accept, but this can be used to specify otherwise.
	PolicyDrop bool

	// LINT.ThenChange(:base_chain_info_copy)
}

// PolicyBoolToValue converts the policy drop boolean to a uint8.
func (bc *BaseChainInfo) PolicyBoolToValue() uint8 {
	if bc.PolicyDrop {
		return uint8(linux.NF_DROP)
	}
	return uint8(linux.NF_ACCEPT)
}

// NewBaseChainInfo creates a new BaseChainInfo object with the given values.
// The device and policyDrop parameters are optional in the nft binary and
// should be set to empty string and false if not needed.
func NewBaseChainInfo(bcType BaseChainType, hook stack.NFHook, priority Priority, device string, policyDrop bool) *BaseChainInfo {
	return &BaseChainInfo{
		BcType:     bcType,
		Hook:       hook,
		Priority:   priority,
		Device:     device,
		PolicyDrop: policyDrop,
	}
}

// BaseChainType represents the supported chain types for base chains.
type BaseChainType int

// Constants for BaseChainType
const (
	// BaseChainTypeFilter type  is supported by all Hooks.
	BaseChainTypeFilter BaseChainType = iota

	// BaseChainTypeNat type     is supported by Prerouting, Input, Output, Postrouting Hooks.
	BaseChainTypeNat

	// BaseChainTypeRoute type   is supported by the Output Hook only.
	BaseChainTypeRoute

	// NumBaseChainTypes is the number of base chain types supported by nftables.
	NumBaseChainTypes
)

// baseChainTypeStrings maps base chain types to their string representation.
var baseChainTypeStrings = map[BaseChainType]string{
	BaseChainTypeFilter: "filter",
	BaseChainTypeNat:    "nat",
	BaseChainTypeRoute:  "route",
}

// String for BaseChainType returns the name of the base chain type.
func (bcType BaseChainType) String() string {
	if bcTypeString, ok := baseChainTypeStrings[bcType]; ok {
		return bcTypeString
	}
	panic(fmt.Sprintf("invalid base chain type: %d", int(bcType)))
}

// supportedAFsForBaseChainTypes maps each base chain type to its supported
// address families.
var supportedAFsForBaseChainTypes [NumBaseChainTypes][]stack.AddressFamily = [NumBaseChainTypes][]stack.AddressFamily{
	BaseChainTypeFilter: {stack.IP, stack.IP6, stack.Inet, stack.Bridge, stack.Arp, stack.Netdev},
	BaseChainTypeNat:    {stack.IP, stack.IP6, stack.Inet},
	BaseChainTypeRoute:  {stack.IP, stack.IP6},
}

// supportedHooksForBaseChainTypes maps each base chain type to its supported
// hooks.
var supportedHooksForBaseChainTypes [NumBaseChainTypes][]stack.NFHook = [NumBaseChainTypes][]stack.NFHook{
	BaseChainTypeFilter: {stack.NFPrerouting, stack.NFInput, stack.NFForward, stack.NFOutput, stack.NFPostrouting, stack.NFIngress, stack.NFEgress},
	BaseChainTypeNat:    {stack.NFPrerouting, stack.NFInput, stack.NFOutput, stack.NFPostrouting},
	BaseChainTypeRoute:  {stack.NFOutput},
}

//
// Priority Object Implementation.
// Object contents are hidden to prevent creating invalid Priority objects.
//

// Priority represents the priority of a base chain which specifies the order
// in which base chains with the same hook value are traversed.
// nftables allows for 2 types of priorities: 1) a simple signed integer value
// or 2) a predefined standard priority name (which is implicitly mapped to a
// signed integer value). Priorities are traversed in ascending order such that
// lower priority value have precedence.
// Use the respective NewIntPriority or NewStandardPriority to create new
// Priority objects.
type Priority struct {
	// Contents are hidden to prevent creating invalid Priority objects.

	// value is the priority value of the base chain (in ascending order). This is
	// set whether the priority is represented by a simple signed integer value or
	// a standard priority name.
	value int

	// standardPriority is the standard priority name if the priority is a
	// predefined standard priority name, otherwise it is the empty string.
	standardPriorityName string
}

// NewIntPriority creates a new Priority object given a simple signed integer
// priority value.
func NewIntPriority(value int) Priority {
	return Priority{value: value}
}

// NewStandardPriority creates a new Priority object given a standard priority
// name, returning an error if the standard priority name is not compatible with
// the given address family and hook.
func NewStandardPriority(name string, family stack.AddressFamily, hook stack.NFHook) (Priority, *syserr.AnnotatedError) {
	// Validates address family and hook first.
	if err := validateAddressFamily(family); err != nil {
		return Priority{}, err
	}
	if err := validateHook(hook, family); err != nil {
		return Priority{}, err
	}

	// Ensures the standard priority name is set.
	if name == "" {
		return Priority{}, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "standard priority name cannot be empty")
	}

	// Looks up standard priority name in the standard priority matrix.
	familyMatrix, exists := standardPriorityMatrix[family]
	if !exists {
		return Priority{}, syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, fmt.Sprintf("standard priority names are not available for address family %v", family))
	}
	sp, exists := familyMatrix[name]
	if !exists {
		return Priority{}, syserr.NewAnnotatedError(syserr.ErrNoFileOrDir, fmt.Sprintf("standard priority name %s not compatible for address family %s", name, family))
	}

	// Checks for hook compatibility.
	if !slices.Contains(sp.hooks, hook) {
		return Priority{}, syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("hook %s is not compatible with standard priority %s", hook, name))
	}

	return Priority{value: sp.value, standardPriorityName: name}, nil
}

// IsStandardPriority returns true if the priority is a standard priority name.
func (p Priority) IsStandardPriority() bool {
	return p.standardPriorityName != ""
}

// GetValue returns the priority value for the Priority object.
func (p Priority) GetValue() int {
	return p.value
}

// GetStandardPriorityName returns the standard priority name for the Priority
// object. It panics if the priority is not a standard priority name.
func (p Priority) GetStandardPriorityName() string {
	if !p.IsStandardPriority() {
		panic("priority is not a standard priority")
	}
	return p.standardPriorityName
}

// String for Priority returns the string representation of the Priority object.
func (p Priority) String() string {
	if p.IsStandardPriority() {
		return p.standardPriorityName
	}
	return fmt.Sprintf("%d", p.value)
}

// standardPriority represents the information for a predefined standard
// priority name and mapping. Standard priorities are only available for the IP,
// IP6, Inet, and Bridge address families, and the matrix below maps each
// standard priority name to the priority value and hooks that the priority
// applies to for the supported address families.
type standardPriority struct {
	// name is the standard priority name.
	name string
	// value is the priority value of the standard priority name.
	value int
	// hooks are the hooks that are compatible with the standard priority name.
	hooks []stack.NFHook
}

// standardPriorityMatrix is used to look up information for the predefined
// standard priority names.
// TODO: b/493710955 - Not used, clean up.
var standardPriorityMatrix = map[stack.AddressFamily](map[string]standardPriority){
	stack.IP: spmIP,
	// Note: IPv6 standard priorities constants currently have the same values as
	// IPv4's, but the definitions (in the linux kernel) may change in the future.
	stack.IP6: map[string]standardPriority{ // from uapi/linux/netfilter_ipv6.h
		"raw":      {name: "raw", value: linux.NF_IP6_PRI_RAW, hooks: supportedHooksList[stack.IP6]},
		"mangle":   {name: "mangle", value: linux.NF_IP6_PRI_MANGLE, hooks: supportedHooksList[stack.IP6]},
		"dstnat":   {name: "dstnat", value: linux.NF_IP6_PRI_NAT_DST, hooks: []stack.NFHook{stack.NFPrerouting}},
		"filter":   {name: "filter", value: linux.NF_IP6_PRI_FILTER, hooks: supportedHooksList[stack.IP6]},
		"security": {name: "security", value: linux.NF_IP6_PRI_SECURITY, hooks: supportedHooksList[stack.IP6]},
		"srcnat":   {name: "srcnat", value: linux.NF_IP6_PRI_NAT_SRC, hooks: []stack.NFHook{stack.NFPostrouting}},
	},
	stack.Inet: spmIP,
	stack.Arp: map[string]standardPriority{ // defined as same as IP filter priority
		"filter": {name: "filter", value: spmIP["filter"].value, hooks: supportedHooksList[stack.Arp]},
	},
	stack.Bridge: map[string]standardPriority{ // from uapi/linux/netfilter_bridge.h
		"dstnat": {name: "dstnat", value: linux.NF_BR_PRI_NAT_DST_BRIDGED, hooks: []stack.NFHook{stack.NFPrerouting}},
		"filter": {name: "filter", value: linux.NF_BR_PRI_FILTER_BRIDGED, hooks: supportedHooksList[stack.Bridge]},
		"out":    {name: "out", value: linux.NF_BR_PRI_NAT_DST_OTHER, hooks: []stack.NFHook{stack.NFOutput}},
		"srcnat": {name: "srcnat", value: linux.NF_BR_PRI_NAT_SRC, hooks: []stack.NFHook{stack.NFPostrouting}},
	},
	stack.Netdev: map[string]standardPriority{ // defined as same as IP filter priority
		"filter": {name: "filter", value: spmIP["filter"].value, hooks: supportedHooksList[stack.Netdev]},
	},
}

// Used in the standardPriorityMatrix above.
// Note: IPv4 and Inet address families use the same standard priority names.
var spmIP = map[string]standardPriority{ // from uapi/linux/netfilter_ipv4.h
	"raw":      {name: "raw", value: linux.NF_IP_PRI_RAW, hooks: supportedHooksList[stack.IP]},
	"mangle":   {name: "mangle", value: linux.NF_IP_PRI_MANGLE, hooks: supportedHooksList[stack.IP]},
	"dstnat":   {name: "dstnat", value: linux.NF_IP_PRI_NAT_DST, hooks: []stack.NFHook{stack.NFPrerouting}},
	"filter":   {name: "filter", value: linux.NF_IP_PRI_FILTER, hooks: supportedHooksList[stack.IP]},
	"security": {name: "security", value: linux.NF_IP_PRI_SECURITY, hooks: supportedHooksList[stack.IP]},
	"srcnat":   {name: "srcnat", value: linux.NF_IP_PRI_NAT_SRC, hooks: []stack.NFHook{stack.NFPostrouting}},
}

// validateBaseChainInfo ensures the base chain info is valid by checking the
// compatibility of the set base chain type, hook, and priority, and the given
// address family.
// Note: errors if the provided base chain info is nil.
func validateBaseChainInfo(info *BaseChainInfo, family stack.AddressFamily) *syserr.AnnotatedError {
	if info == nil {
		return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "base chain info is nil")
	}

	// Validates the hook.
	if err := validateHook(info.Hook, family); err != nil {
		return err
	}

	// Validates the base chain type.
	if info.BcType < 0 || info.BcType >= NumBaseChainTypes {
		return syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("base chain type %d is invalid", int(info.BcType)))
	}
	if !slices.Contains(supportedAFsForBaseChainTypes[info.BcType], family) {
		return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("base chain type %d is not supported for address family %v", int(info.BcType), family))
	}
	if !slices.Contains(supportedHooksForBaseChainTypes[info.BcType], info.Hook) {
		return syserr.NewAnnotatedError(syserr.ErrNotSupported, fmt.Sprintf("base chain type %v is not valid for hook %v", info.BcType, info.Hook))
	}

	// Priority assumed to be valid since it's a result of a constructor call.
	return nil
}

// Rule represents a single rule in a chain and is represented as a list of
// operations that are evaluated sequentially (on a packet).
// Rules must be registered to a chain to be used and evaluated, and rules that
// have been registered to a chain cannot be modified.
// Note: Empty rules should be created directly (via &Rule{}).
type Rule struct {
	chain  *Chain
	ops    []operation
	handle uint64
	udata  []byte
}

// ExprInfo represents the information for a single expression nested under
// NFTA_EXPRESSIONS.
type ExprInfo struct {
	ExprName string
	ExprData nlmsg.AttrsView
}

type opCompatCtx struct {
	chain *Chain
}

type opEvalCtx struct {
	pkt      *stack.PacketBuffer
	hook     stack.NFHook
	rule     *Rule
	route    *stack.Route
	nftState *NFTables
}

// operation represents a single operation in a rule.
type operation interface {
	// GetExprName returns the name of the expression.
	GetExprName() string
	// Dump dumps the parameters.
	Dump() ([]byte, *syserr.AnnotatedError)

	evaluate(regs *registerSet, evalCtx opEvalCtx)

	// checkCompatibility returns if the operation is compatible with the context
	// it is being bound to. It is called during rule registration to verify
	// that the operation's requirements are met by the NFTables execution context.
	checkCompatibility(cCtx *opCompatCtx) *syserr.AnnotatedError

	// deepCopy returns a deep copy of the operation.
	deepCopy() operation

	// updateReferences updates any references/pointers to objects in the given table.
	updateReferences(table *Table, sourceTable *Table, sourceOp operation)

	// destroy performs cleanup for the operation.
	destroy()
}

// Ensures all operations implement the Operation interface at compile time.
var (
	_ operation = (*immediate)(nil)
	_ operation = (*comparison)(nil)
	_ operation = (*ranged)(nil)
	_ operation = (*payloadLoad)(nil)
	_ operation = (*payloadSet)(nil)
	_ operation = (*bitwise)(nil)
	_ operation = (*counter)(nil)
	_ operation = (*last)(nil)
	_ operation = (*route)(nil)
	_ operation = (*byteorder)(nil)
	_ operation = (*metaLoad)(nil)
	_ operation = (*metaSet)(nil)
	_ operation = (*natOp)(nil)
	_ operation = (*lookupOp)(nil)
	_ operation = (*fib)(nil)
	_ operation = (*ctGet)(nil)
	_ operation = (*ctSet)(nil)
	_ operation = (*masqOp)(nil)
)

// OpType represents the type of operation.
type OpType int

const (
	// OpTypeImmediate is the immediate operation type.
	OpTypeImmediate OpType = iota
	// OpTypeComparison is the comparison operation type.
	OpTypeComparison
	// OpTypeRanged is the ranged operation type.
	OpTypeRanged
	// OpTypePayload is the payload operation type.
	OpTypePayload
	// OpTypeBitwise is the bitwise operation type.
	OpTypeBitwise
	// OpTypeCounter is the counter operation type.
	OpTypeCounter
	// OpTypeLast is the last operation type.
	OpTypeLast
	// OpTypeRoute is the route operation type.
	OpTypeRoute
	// OpTypeByteorder is the byteorder operation type.
	OpTypeByteorder
	// OpTypeMeta is the meta operation type.
	OpTypeMeta
	// OpTypeNAT is the NAT operation type.
	OpTypeNAT
	// OpTypeLookup is the lookup operation type.
	OpTypeLookup
	// OpTypeFIB is the FIB operation type.
	OpTypeFIB
	// OpTypeCT is the conntrack operation type.
	OpTypeCT
	// OpTypeMasq is the masquerade operation type.
	OpTypeMasq
	// OpTypeUnknown is the unknown operation type.
	OpTypeUnknown
)

var opTypeStrings = []string{
	OpTypeImmediate:  "immediate",
	OpTypeComparison: "cmp",
	OpTypeRanged:     "ranged",
	OpTypePayload:    "payload",
	OpTypeBitwise:    "bitwise",
	OpTypeCounter:    "counter",
	OpTypeLast:       "last",
	OpTypeRoute:      "route",
	OpTypeByteorder:  "byteorder",
	OpTypeMeta:       "meta",
	OpTypeNAT:        "nat",
	OpTypeLookup:     "lookup",
	OpTypeFIB:        "fib",
	OpTypeCT:         "ct",
	OpTypeMasq:       "masq",
	OpTypeUnknown:    "unknown",
}

var stringToOpType = func() map[string]OpType {
	m := make(map[string]OpType)
	for i, s := range opTypeStrings {
		if _, ok := m[s]; ok {
			panic(fmt.Sprintf("duplicate operation type string: %s", s))
		}
		m[s] = OpType(i)
	}
	return m
}()

// String returns a string representation of the operation type.
func (o OpType) String() string {
	if o >= 0 && o < OpTypeUnknown {
		return opTypeStrings[o]
	}
	return "unknown"
}

// ToOpType converts a string to an operation type.
func ToOpType(s string) OpType {
	if o, ok := stringToOpType[s]; ok {
		return o
	}
	return OpTypeUnknown
}

//
// Register and Register-Related Implementations.
// Note: Registers are represented by type uint8 for the register number.
//

func isVerdictRegister(reg uint8) bool {
	return reg == linux.NFT_REG_VERDICT
}

func is16ByteRegister(reg uint8) bool {
	return reg >= linux.NFT_REG_1 && reg <= linux.NFT_REG_4
}

func is4ByteRegister(reg uint8) bool {
	return reg >= linux.NFT_REG32_00 && reg <= linux.NFT_REG32_15
}

func isRegister(reg uint8) bool {
	return isVerdictRegister(reg) || is16ByteRegister(reg) || is4ByteRegister(reg)
}

// registerSet represents the set of registers supported by the kernel.
// Use registerData.storeData to set data in the registers.
// Note: Corresponds to nft_regs from include/net/netfilter/nf_tables.h.
type registerSet struct {
	verdict Verdict                 // 16-byte verdict register
	data    [registersByteSize]byte // 4 16-byte registers or 16 4-byte registers
}

// newRegisterSet creates a new registerSet with the Continue Verdict and all
// registers set to 0.
func newRegisterSet() registerSet {
	return registerSet{
		verdict: Verdict{Code: VC(linux.NFT_CONTINUE)},
		data:    [registersByteSize]byte{0},
	}
}

// Verdict returns the verdict data.
func (regs *registerSet) Verdict() Verdict {
	return regs.verdict
}

func (regs *registerSet) String() string {
	return fmt.Sprintf("verdict: %v, data: %x", regs.verdict, regs.data)
}

// NF Verdict Helper Functions

// VerdictString returns a string representation of the verdict.
func VerdictString(v Verdict) string {
	out := VerdictCodeToString(v.Code)
	if v.Chain != nil {
		out += fmt.Sprintf(" -> %s", v.Chain.GetName())
	}
	return out
}

// VC converts a numeric code to a uint32 number representing the verdict.
func VC(v int32) uint32 {
	return uint32(v)
}

// verdictCodeStrings is a map of verdict code to its string representation.
var verdictCodeStrings = map[uint32]string{
	// Netfilter (External) Verdicts:
	VC(linux.NF_DROP):   "Drop",
	VC(linux.NF_ACCEPT): "Accept",
	VC(linux.NF_STOLEN): "Stolen",
	VC(linux.NF_QUEUE):  "Queue",
	VC(linux.NF_REPEAT): "Repeat",
	VC(linux.NF_STOP):   "Stop",
	// Nftable (Internal) Verdicts:
	VC(linux.NFT_CONTINUE): "Continue",
	VC(linux.NFT_BREAK):    "Break",
	VC(linux.NFT_JUMP):     "Jump",
	VC(linux.NFT_GOTO):     "Goto",
	VC(linux.NFT_RETURN):   "Return",
}

// VerdictCodeToString prints names for the supported verdicts.
func VerdictCodeToString(v uint32) string {

	if vcStr, ok := verdictCodeStrings[v]; ok {
		return vcStr
	}
	return fmt.Sprintf("invalid verdict: %d", v)
}

// netlinkAFToStackAF maps address families from linux/netfilter.h to their
// corresponding netfilter address families.
// From linux/include/uapi/linux/netfilter.h
var netlinkAFToStackAF = map[uint8]stack.AddressFamily{
	linux.NFPROTO_UNSPEC: stack.Unspec,
	linux.NFPROTO_INET:   stack.Inet,
	linux.NFPROTO_IPV4:   stack.IP,
	linux.NFPROTO_ARP:    stack.Arp,
	linux.NFPROTO_NETDEV: stack.Netdev,
	linux.NFPROTO_BRIDGE: stack.Bridge,
	linux.NFPROTO_IPV6:   stack.IP6,
}

// NftSetBackend is an interface for handling set operations.
type NftSetBackend interface {
	// Evaluate returns the index of the element in the set if found, otherwise -1.
	// keyIdx is the index of the key in the register set.
	Evaluate(regs *registerSet, keyIdx int) int

	// Find returns the index of the element given the raw key data, otherwise -1.
	Find(key []byte) int

	// Add inserts an element to the set and returns the insertion index.
	// If the element already exists,
	// it returns the index of the existing element.
	Add(e *nftSetElem, idx int) (int, *syserr.AnnotatedError)

	// Remove removes an element from the set and returns the index of the
	// removed element.
	// If the element does not exist, it returns -1 and an error.
	Remove(e *nftSetElem) (int, *syserr.AnnotatedError)

	// Update updates the index of an element in the set backend.
	Update(e *nftSetElem, idx int) *syserr.AnnotatedError

	// RemoveAll removes all elements from the set backend.
	RemoveAll() *syserr.AnnotatedError

	// Clone returns a copy of the set backend.
	Clone() NftSetBackend
}

// Set represents an nftables set.
// Ref: include/net/netfilter/nf_tables.h:nft_set
type nftSet struct {
	// name represents the unique name for this set.
	name string
	// keyType represents the type of key; i.e. IPv4, IPv6, port, etc.
	keyType uint32
	// dataType represents the type of data; i.e. VERDICT or DATA.
	dataType uint32
	// objType represents the type of object that the set is storing.
	objType uint32
	// descSize represents the max number of elements.
	// If 0, then the set has no bounds on the number of elements.
	descSize uint32
	// fieldLen represents the length of each sub-key.
	fieldLen []uint8
	// fieldCount represents the number of sub-keys.
	fieldCount uint8
	// bindings represents the list of lookupOps that use this set.
	bindings []*lookupOp
	// timeout represents the timeout for the elements in the set.
	// TODO: b/505409691 - Add support for set timeout.
	timeout uint64
	// gcInterval represents the interval for garbage collection.
	// GC gets rid of expired elements in the set.
	// TODO: b/505409691 - Add support for gc interval.
	gcInterval uint32
	// policy passed by the user hints at
	// whether the set should be memory or performance optimized.
	// TODO: b/505409691 - Add support for set policy.
	policy uint32
	// udata represents the user data of the set.
	udata []byte
	// exprInfos represents the stateful nft expressions/ops;
	// counter, limit, etc.
	// that are directly associated with the individual set elements.
	// TODO: b/505409691 - Add support for expressions other than the counters.
	exprInfos []ExprInfo
	// flags determines the set's configuration.
	flags uint16
	// dead marks the set as deleted.
	dead uint8
	// genmask links to nftables generation.
	// We don't need to support this as we make a full copy of
	// the set on each transaction; so each NFTables instance
	// is it's own "generation".
	genmask uint16
	// keyLen is the length of the key,
	// or the combined length of all the sub-keys.
	keyLen uint8
	// dataLen is the length of the data;
	// incase of a verdict set, this is not required.
	dataLen uint8
	// handle is the NFTables unique identifier for this set.
	handle uint64
	// elements represents the stored key-value elements in the set.
	elements []nftSetElem
	// catchAllElem is a default element that is used when the key
	// does not match any of the other elements.
	catchAllElem *nftSetElem
	// backend represents the backend object that handles the set operations.
	backend NftSetBackend
}

// dataOrVerdict represents the data or verdict of the set element.
type dataOrVerdict struct {
	isVerdict bool
	verdict   Verdict
	data      []byte
}

// nftSetElem represents an element in an nftables set.
// Ref: include/net/netfilter/nf_tables.h:nft_set_elem
type nftSetElem struct {
	// startKey represents the lower bound key for the element.
	// For a simple set, this is the key itself.
	// For a set range, this is the lower bound of the range.
	startKey []byte
	// endKey represents the upper bound key for the element.
	// Only required for ranged sets.
	// TODO: b/505409691 - Add support for ranged sets.
	endKey []byte
	// data represents the data or verdict of the set element.
	data dataOrVerdict
	// ops represents the Nftables ops(counter, limit, etc.)
	// that are associated with the set element.
	ops []operation
	// timeout represents the timeout for the element.
	// TODO: b/505409691 - Add support for set element timeout.
	timeout uint64
	// expiration represents the expiration time of the element.
	// TODO: b/505409691 - Add support for set element expiration.
	expiration uint64
	// userData represents the user data that can be associated with the element.
	// It's only for the user to see and has no affect on the set element.
	userData []byte
}

// AFtoNetlinkAF converts a generic address family to a netfilter address family.
// On error, we simply cast it to be a stack.AddressFamily and return an error to allow netfilter
// sockets to handle it accordingly if needed.
func AFtoNetlinkAF(af uint8) (stack.AddressFamily, *syserr.Error) {
	naf, ok := netlinkAFToStackAF[af]
	if !ok {
		return stack.NumAFs, syserr.ErrNotSupported
	}
	return naf, nil
}

// parseVerdictAttrs parses and validates the verdict data from the data attributes.
func parseVerdictAttrs(tab *Table, dataAttrs map[uint16]nlmsg.BytesView) (Verdict, *syserr.AnnotatedError) {
	v := Verdict{}
	vBytes, ok := dataAttrs[linux.NFTA_DATA_VERDICT]
	if !ok {
		return v, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_DATA_VERDICT attribute is not found")
	}
	return parseAndValidateVerdictData(tab, nlmsg.AttrsView(vBytes))
}

func parseDataAttrs(dataAttrs map[uint16]nlmsg.BytesView) ([]byte, *syserr.AnnotatedError) {
	vBytes, ok := dataAttrs[linux.NFTA_DATA_VALUE]
	if !ok {
		return nil, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_DATA_VALUE attribute is not found")
	}
	return nlmsg.AttrsView(vBytes), nil
}

// validateDataRegister ensures that the data register and it's access is valid.
func validateDataRegister(regStartIdx int, dataSizeBytes int) *syserr.AnnotatedError {
	if dataSizeBytes == 0 {
		return syserr.NewAnnotatedError(syserr.ErrRange, "data size cannot be zero")
	}
	// Although this check is not needed as the next check will catch this,
	// added it just for readable error messages.
	if regStartIdx >= registersByteSize {
		return syserr.NewAnnotatedError(syserr.ErrRange, "register start index is invalid")
	}
	// Kernel code: net/netfilter/nf_tables_api.c:nft_validate_register_store
	endIdx := regStartIdx + dataSizeBytes - 1
	if endIdx >= registersByteSize {
		return syserr.NewAnnotatedError(syserr.ErrRange, "data is too large for register set")
	}
	return nil
}

// dumpDataAttr dumps the data attribute for the dump operation.
func dumpDataAttr(data []byte) ([]byte, *syserr.AnnotatedError) {
	m := &nlmsg.Message{}
	m.PutAttr(linux.NFTA_DATA_VALUE, primitive.AsByteSlice(data))
	return m.Buffer(), nil
}

// dumpVerdictDataAttr dumps the verdict data attribute for the dump operation.
func dumpVerdictDataAttr(verdict Verdict) ([]byte, *syserr.AnnotatedError) {
	nestedAttr := nlmsg.NestedAttr{}
	nestedAttr.PutAttr(linux.NFTA_VERDICT_CODE, nlmsg.PutU32(uint32(verdict.Code)))
	if int32(verdict.Code) == linux.NFT_JUMP || int32(verdict.Code) == linux.NFT_GOTO {
		cn := ""
		if verdict.Chain != nil {
			cn = verdict.Chain.GetName()
		}
		nestedAttr.PutAttrString(linux.NFTA_VERDICT_CHAIN, cn)
	}
	m := &nlmsg.Message{}
	m.PutNestedAttr(linux.NFTA_DATA_VERDICT, nestedAttr)
	return m.Buffer(), nil
}

// regNumToIdx converts a register number to an index for the registerSet.data.
// Also validates that the data register and it's access is valid.
func regNumToIdx(reg uint8, dataLenBytes int) (int, *syserr.AnnotatedError) {
	regIdx, ok := func() (int, bool) {
		if is4ByteRegister(reg) {
			return int((reg - linux.NFT_REG32_00) * linux.NFT_REG32_SIZE), true
		}
		if is16ByteRegister(reg) {
			return int((reg - linux.NFT_REG_1) * linux.NFT_REG_SIZE), true
		}
		return -1, false
	}()
	if !ok {
		return -1, syserr.NewAnnotatedError(syserr.ErrRange, fmt.Sprintf("Nftables: Unsupported register number: %d", reg))
	}
	if err := validateDataRegister(regIdx, dataLenBytes); err != nil {
		return -1, err
	}
	return regIdx, nil
}

// formatRegIdxForDump formats the register index for the dump operation.
// net/netfilter/nf_tables_api.c:nft_dump_register
func formatRegIdxForDump(regIdx int) marshal.Marshallable {
	if regIdx >= registersByteSize {
		return nlmsg.PutU32(0)
	}
	if regIdx%linux.NFT_REG_SIZE == 0 {
		val := uint32(regIdx/linux.NFT_REG_SIZE) + linux.NFT_REG_1
		return nlmsg.PutU32(val)
	}
	if regIdx%linux.NFT_REG32_SIZE != 0 {
		return nlmsg.PutU32(0)
	}
	val := uint32(regIdx/linux.NFT_REG32_SIZE) + linux.NFT_REG32_00
	return nlmsg.PutU32(val)
}

// validateVerdictData validates the verdict data bytes and returns the data as a verdict.
func parseAndValidateVerdictData(tab *Table, bytes nlmsg.AttrsView) (Verdict, *syserr.AnnotatedError) {
	v := Verdict{}
	verdictAttrs, ok := NfParse(bytes)
	if !ok {
		return v, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Failed to parse verdict data")
	}

	verdictCodeBytes, ok := verdictAttrs[linux.NFTA_VERDICT_CODE]
	if !ok {
		return v, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_VERDICT_CODE attribute is not found")
	}

	verdictCode, ok := verdictCodeBytes.Uint32()
	if !ok {
		return v, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: NFTA_VERDICT_CODE attribute cannot be parsed to a uint32")
	}

	verdictCode = nlmsg.NetToHostU32(verdictCode)
	switch int32(verdictCode) {
	case linux.NF_ACCEPT, linux.NF_DROP, linux.NF_QUEUE,
		linux.NFT_CONTINUE, linux.NFT_BREAK, linux.NFT_RETURN:

	case linux.NFT_JUMP, linux.NFT_GOTO:
		var chain *Chain
		var err *syserr.AnnotatedError
		if chainNameBytes, ok := verdictAttrs[linux.NFTA_VERDICT_CHAIN]; ok {
			if chain, err = tab.GetChain(chainNameBytes.String()); err != nil {
				return v, err
			}
		} else if chainID, ok := AttrNetToHost[uint32](linux.NFTA_VERDICT_CHAIN_ID, verdictAttrs); ok {
			if chain, err = tab.GetChainByID(chainID); err != nil {
				return v, err
			}
		} else {
			return v, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Attributes for verdict data must contain a chain name or chain id")
		}

		if chain.IsBaseChain() {
			return v, syserr.NewAnnotatedError(syserr.ErrNotSupported, "Nftables: Base chains are not supported as jump targets")
		}

		if chain.IsBound() {
			return v, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, "Nftables: Already Bound chains cannot be jump targets")
		}

		if !chain.IncrementChainUse() {
			return v, syserr.NewAnnotatedError(syserr.ErrTooManyOpenFiles, fmt.Sprintf("Nftables: Chain use exceeds the maximum number of chains that can jump to chain %s", chain.GetName()))
		}

		v.Chain = chain
		v.Code = verdictCode
		return v, nil
	default:
		return v, syserr.NewAnnotatedError(syserr.ErrInvalidArgument, fmt.Sprintf("Nftables: Unsupported verdict code: %d", verdictCode))
	}
	v.Code = verdictCode
	return v, nil
}

// deepCopyRule returns a deep copy of the Rule struct.
func deepCopyRule(rule *Rule, chainCopy *Chain) *Rule {
	ruleCopy := &Rule{
		chain:  chainCopy,
		handle: rule.handle,
		udata:  slices.Clone(rule.udata),
	}

	ruleCopy.ops = make([]operation, 0, len(rule.ops))
	for _, op := range rule.ops {
		ruleCopy.ops = append(ruleCopy.ops, op.deepCopy())
	}
	return ruleCopy
}

// deepCopyChain returns a deep copy of the Chain struct.
func deepCopyChain(chain *Chain, tableCopy *Table) *Chain {
	chainCopy := &Chain{
		name:         chain.name,
		table:        tableCopy,
		handle:       chain.handle,
		flags:        chain.flags,
		handleToRule: make(map[uint64]*Rule),
		userData:     slices.Clone(chain.userData),
		chainUse:     chain.chainUse,
		comment:      chain.comment,
	}

	// LINT.IfChange(base_chain_info_copy)

	// BaseChainInfo is immutable after creation and it only contains
	// primitives, so we can safely copy it.
	if chain.baseChainInfo != nil {
		chainCopy.baseChainInfo = &BaseChainInfo{}
		*chainCopy.baseChainInfo = *chain.baseChainInfo
	}

	// LINT.ThenChange()

	for _, rule := range chain.rules {
		ruleCopy := deepCopyRule(rule, chainCopy)
		chainCopy.rules = append(chainCopy.rules, ruleCopy)
		chainCopy.handleToRule[ruleCopy.handle] = ruleCopy
	}
	return chainCopy
}

func deepCopySetElement(elem *nftSetElem) *nftSetElem {
	elemCopy := &nftSetElem{
		startKey:   slices.Clone(elem.startKey),
		endKey:     slices.Clone(elem.endKey),
		data:       elem.data,
		timeout:    elem.timeout,
		expiration: elem.expiration,
		ops:        make([]operation, 0, len(elem.ops)),
		userData:   slices.Clone(elem.userData),
	}
	elemCopy.data.data = slices.Clone(elem.data.data)
	for _, op := range elem.ops {
		elemCopy.ops = append(elemCopy.ops, op.deepCopy())
	}
	return elemCopy
}

// updateReferences updates all ops in the rule.
func (r *Rule) updateReferences(table *Table, sourceTable *Table, sourceRule *Rule) {
	for i, op := range r.ops {
		op.updateReferences(table, sourceTable, sourceRule.ops[i])
	}
}

// updateReferences updates all rules in the chain.
func (c *Chain) updateReferences(table *Table, sourceTable *Table, sourceChain *Chain) {
	for i, rule := range c.rules {
		rule.updateReferences(table, sourceTable, sourceChain.rules[i])
	}
}

// updateReferences updates the verdict and ops within a set element.
func (e *nftSetElem) updateReferences(table *Table, sourceTable *Table, sourceElem *nftSetElem) {
	if e.data.isVerdict && e.data.verdict.Chain != nil {
		e.data.verdict.Chain = table.chains[sourceElem.data.verdict.Chain.name]
	}
	for i, op := range e.ops {
		op.updateReferences(table, sourceTable, sourceElem.ops[i])
	}
}

// updateReferences updates the catchall element and array elements in the set.
func (s *nftSet) updateReferences(table *Table, sourceTable *Table, sourceSet *nftSet) {
	if s.catchAllElem != nil {
		s.catchAllElem.updateReferences(table, sourceTable, sourceSet.catchAllElem)
	}
	for i := range s.elements {
		s.elements[i].updateReferences(table, sourceTable, &sourceSet.elements[i])
	}
}

// updateReferences calls updateReferences on all chains and sets in the table, passing the original table.
func (t *Table) updateReferences(sourceTable *Table) {
	for _, chain := range t.chains {
		chain.updateReferences(t, sourceTable, sourceTable.chains[chain.name])
	}
	for _, set := range t.sets {
		set.updateReferences(t, sourceTable, sourceTable.sets[set.name])
	}
}

// deepCopySet returns a deep copy of the Set struct.
func deepCopySet(set *nftSet, copyBindings bool) *nftSet {
	setCopy := &nftSet{
		name:       set.name,
		keyType:    set.keyType,
		dataType:   set.dataType,
		objType:    set.objType,
		descSize:   set.descSize,
		fieldLen:   slices.Clone(set.fieldLen),
		fieldCount: set.fieldCount,
		timeout:    set.timeout,
		gcInterval: set.gcInterval,
		policy:     set.policy,
		udata:      slices.Clone(set.udata),
		exprInfos:  slices.Clone(set.exprInfos),
		flags:      set.flags,
		dead:       set.dead,
		genmask:    set.genmask,
		keyLen:     set.keyLen,
		dataLen:    set.dataLen,
		handle:     set.handle,
		backend:    set.backend.Clone(),
	}
	if set.catchAllElem != nil {
		setCopy.catchAllElem = deepCopySetElement(set.catchAllElem)
	}
	setCopy.elements = make([]nftSetElem, 0, len(set.elements))
	for _, elem := range set.elements {
		elemCopy := deepCopySetElement(&elem)
		setCopy.elements = append(setCopy.elements, *elemCopy)
	}

	return setCopy
}

// deepCopyTable returns a deep copy of the Table struct.
func deepCopyTable(table *Table, afFilter *addressFamilyFilter) *Table {
	tableCopy := &Table{
		name:         table.name,
		afFilter:     afFilter,
		chains:       make(map[string]*Chain),
		chainHandles: make(map[uint64]*Chain),
		flagSet:      make(map[TableFlag]struct{}),
		handle:       table.handle,
		sets:         make(map[string]*nftSet),
		setHandles:   make(map[uint64]*nftSet),
		owner:        table.owner,
		userData:     slices.Clone(table.userData),
	}
	tableCopy.handleCounter.Store(table.handleCounter.Load())

	for flag := range table.flagSet {
		tableCopy.flagSet[flag] = struct{}{}
	}

	for setName, set := range table.sets {
		// Bindings are updated in the rule deepCopy.
		setCopy := deepCopySet(set, false /*=copyBindings*/)
		tableCopy.sets[setName] = setCopy
		tableCopy.setHandles[setCopy.handle] = setCopy
	}

	for chainName, chain := range table.chains {
		chainCopy := deepCopyChain(chain, tableCopy)
		tableCopy.chains[chainName] = chainCopy
		tableCopy.chainHandles[chainCopy.handle] = chainCopy
	}

	return tableCopy
}

// DeepCopy returns a deep copy of the NFTables struct.
// Assumes that the caller has already locked the mutex.
// **********************************************************************
func (nf *NFTables) DeepCopy() *NFTables {
	nftCopy := &NFTables{
		clock:              nf.clock,
		startTime:          nf.startTime,
		rng:                nf.rng,
		tableHandleCounter: atomicbitops.Uint64{},
		genid:              nf.genid,
		connTrack:          nf.connTrack,
		connTrackReaper:    nf.connTrackReaper,
		natEnabled:         nf.natEnabled,
		stack:              nf.stack,
	}

	nftCopy.tableHandleCounter.Store(nf.tableHandleCounter.Load())
	for i, filter := range nf.filters {
		if filter == nil {
			continue
		}

		nftCopy.filters[i] = &addressFamilyFilter{
			family:       filter.family,
			nftState:     nftCopy,
			tables:       make(map[string]*Table),
			tableHandles: make(map[uint64]*Table),
			hfStacks:     make(map[stack.NFHook]*hookFunctionStack),
		}

		for tableName, table := range filter.tables {
			tableCopy := deepCopyTable(table, nftCopy.filters[i])
			nftCopy.filters[i].tables[tableName] = tableCopy
			nftCopy.filters[i].tableHandles[tableCopy.handle] = tableCopy
		}

		for _, tableCopy := range nftCopy.filters[i].tables {
			tableCopy.updateReferences(filter.tables[tableCopy.name])
		}

		for hook, hfStack := range filter.hfStacks {
			hfStackCopy := &hookFunctionStack{}
			for _, chain := range hfStack.baseChains {
				chainCopy := nftCopy.filters[i].tables[chain.table.name].chains[chain.name]
				hfStackCopy.baseChains = append(hfStackCopy.baseChains, chainCopy)
				nftCopy.addChainToCache(chainCopy)
			}
			for _, chain := range hfStack.natBaseChains {
				chainCopy := nftCopy.filters[i].tables[chain.table.name].chains[chain.name]
				hfStackCopy.natBaseChains = append(hfStackCopy.natBaseChains, chainCopy)
				nftCopy.addChainToCache(chainCopy)
			}
			nftCopy.filters[i].hfStacks[hook] = hfStackCopy
		}
	}
	return nftCopy
}

//
// Verdict Implementation.
// There are two types of verdicts:
// 1. Netfilter (External) Verdicts: Drop, Accept, Stolen, Queue, Repeat, Stop
// 		These are terminal verdicts that are returned to the kernel.
// 2. Nftable (Internal) Verdicts:, Continue, Break, Jump, Goto, Return
// 		These are internal verdicts that only exist within the nftables library.
// Both share the same numeric space (uint32 Verdict Code).
//

// Verdict represents the result of evaluating a packet against a rule or chain.
type Verdict struct {
	// Code is the numeric code that represents the verdict issued.
	Code uint32
	// Chain is the resolved chain to continue evaluation if the verdict is
	// Jump or Goto. It allows fast pointer traversal during packet ruleset processing.
	Chain *Chain
}
