# `Linx.Netfilter.Encoder`
[🔗](https://github.com/oshlabs/linx/blob/v0.2.0/lib/linx/netfilter/encoder.ex#L1)

Converts `%Linx.Netfilter.*{}` value structs into the
`%Linx.Netlink.Message{}` shapes that ride inside a
`NFNL_MSG_BATCH_BEGIN` / `NFNL_MSG_BATCH_END` envelope.

Each public function returns a single `%Message{}` (or a list,
for entities that map to multiple wire messages). `to_batch/2`
walks a full `%Ruleset{}` and produces the ordered message list
for a `:replace`-mode push. `Linx.Netlink.Nfnl.batch/2` wraps the
envelope and drives the transaction.

## Wire format quirks

  * Every nftables `NLA_U32` / `NLA_U64` is **big-endian** on the
    wire — opposite to rtnetlink. `Linx.Netfilter.Wire.u32_be/1`
    / `u64_be/1` do the conversion.
  * The `nfgenmsg.family` byte carries the family this message
    applies to (`NFPROTO_INET`, etc.); `res_id` is 0 for most
    ops, the batch target's subsys id only for BATCH_BEGIN/END.
  * Attribute IDs are netfilter-namespaced (NFTA_TABLE_*,
    NFTA_CHAIN_*, etc.) — the same numeric ID can mean different
    things in different attribute sets.

## References

  * [`nft_table.c`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/nf_tables_api.c)
    — kernel-side parser; canonical authority on attribute order
    and required fields.

# `chain`

```elixir
@spec chain(Linx.Netfilter.Chain.t(), Linx.Netfilter.Table.family(), keyword()) ::
  Linx.Netlink.Message.t()
```

Builds a `NEWCHAIN` message for `chain` within `family`.

Required at construction:

  * `NFTA_CHAIN_TABLE` (string) — taken from `chain.table`.
  * `NFTA_CHAIN_NAME` (string).

Base-chain attributes (set together when `chain` is a base chain):

  * `NFTA_CHAIN_HOOK` (nested) — `NFTA_HOOK_HOOKNUM` (u32 BE),
    `NFTA_HOOK_PRIORITY` (s32 BE), and `NFTA_HOOK_DEV` (string)
    for `:ingress`/`:egress`.
  * `NFTA_CHAIN_TYPE` (string: `"filter"` | `"nat"` | `"route"`).
  * `NFTA_CHAIN_POLICY` (u32 BE, NF_ACCEPT=1 / NF_DROP=0) — only
    when `chain.policy` is set.
  * `NFTA_CHAIN_FLAGS` (u32 BE) — when `chain.flags` is non-empty.

Regular chains skip the HOOK / TYPE / POLICY attributes entirely.

# `delete_chain`

```elixir
@spec delete_chain(Linx.Netfilter.Table.family(), String.t(), String.t()) ::
  Linx.Netlink.Message.t()
```

Builds a `DELCHAIN` message — removes a chain by name.

# `delete_rule`

```elixir
@spec delete_rule(
  Linx.Netfilter.Table.family(),
  String.t(),
  String.t(),
  pos_integer()
) ::
  Linx.Netlink.Message.t()
```

Builds a `DELRULE` message — removes a single rule by its
kernel-assigned handle.

# `delete_set`

```elixir
@spec delete_set(Linx.Netfilter.Table.family(), String.t(), String.t()) ::
  Linx.Netlink.Message.t()
```

Builds a `DELSET` message — removes a named set by `(family,
table, name)`. Returns `:enoent` from the kernel if missing.

# `delete_set_elements`

```elixir
@spec delete_set_elements(
  Linx.Netfilter.Table.family(),
  String.t(),
  String.t(),
  [term()],
  atom(),
  atom() | nil
) :: Linx.Netlink.Message.t()
```

Builds a `DELSETELEM` message that removes the given elements
from a set by raw element values.

# `deltable`

```elixir
@spec deltable(Linx.Netfilter.Table.family(), String.t()) :: Linx.Netlink.Message.t()
```

Builds a DELTABLE message for the given table.

Returns `:enoent` from the kernel if the table doesn't exist —
use `destroytable/2` for silent-if-missing semantics (6.3+).

# `destroytable`

```elixir
@spec destroytable(Linx.Netfilter.Table.family(), String.t()) ::
  Linx.Netlink.Message.t()
```

Builds a DESTROYTABLE message — silently a no-op if the table
doesn't exist. Used by `:replace`-mode push to clear pre-existing
state before re-creating it.

Requires kernel ≥ 6.3 (where DESTROY* messages were added).

# `from_patch`

```elixir
@spec from_patch(Linx.Netfilter.Patch.t()) :: [Linx.Netlink.Message.t()]
```

Encodes a `%Linx.Netfilter.Patch{}` into the ordered list of
`%Message{}`s to send inside a single BATCH transaction.

Sets need their NFTA_SET_ID generated per-batch; this function
assigns ids in patch order so element-add ops can later
reference them by name (the kernel resolves by name regardless,
but libnftnl convention is to also send the id).

# `gettable`

```elixir
@spec gettable(Linx.Netfilter.Table.family(), String.t()) :: Linx.Netlink.Message.t()
```

Builds a GETTABLE request — for fetching one table by
`(family, name)` from the kernel.

No `NLM_F_DUMP` — that's `gettable_dump/1` (for listing all
tables in the netns).

# `gettable_dump`

```elixir
@spec gettable_dump(atom()) :: Linx.Netlink.Message.t()
```

Builds a GETTABLE dump request — returns every table in the
netns. Filtering by family is optional (`:unspec` returns all
families).

# `rule`

```elixir
@spec rule(
  Linx.Netfilter.Rule.t(),
  Linx.Netfilter.Table.family(),
  String.t(),
  String.t(),
  keyword()
) ::
  Linx.Netlink.Message.t()
```

Builds a `NEWRULE` message for `rule` inside the `(family, table, chain)`
scope.

Attribute payload:

  * `NFTA_RULE_TABLE` (string).
  * `NFTA_RULE_CHAIN` (string).
  * `NFTA_RULE_EXPRESSIONS` (nested list of `NFTA_LIST_ELEM`s)
    — each list element wraps `NFTA_EXPR_NAME` (string) plus
    `NFTA_EXPR_DATA` (nested, per-expression attribute set).

Comment / tag round-trip via `NFTA_RULE_USERDATA`.

# `set`

```elixir
@spec set(
  Linx.Netfilter.Set.t() | Linx.Netfilter.Map.t(),
  Linx.Netfilter.Table.family(),
  keyword()
) ::
  Linx.Netlink.Message.t()
```

Builds a `NEWSET` message. Accepts either a `%Linx.Netfilter.Set{}`
(plain set) or a `%Linx.Netfilter.Map{}` (typed map, including
vmaps — `data_type: :verdict`).

The encoder auto-derives the `NFT_SET_F_MAP` flag for maps and
`NFT_SET_F_EVAL` for `:dynamic` sets; callers shouldn't include
them in `:flags` directly (but `set_flags_int/1` accepts them
for round-trip purposes).

# `set_elements`

```elixir
@spec set_elements(
  Linx.Netfilter.Set.t() | Linx.Netfilter.Map.t(),
  Linx.Netfilter.Table.family(),
  keyword()
) :: Linx.Netlink.Message.t() | nil
```

Builds a `NEWSETELEM` message — adds one or more elements to a
named set.

Elements is a list of either:

  * raw key terms (for plain sets) — `[{10,0,0,1}, "1.2.3.4", ...]`.
  * `{key, data}` tuples (for maps and vmaps) — `[{22, %Verdict{...}}, ...]`.

The encoder normalises each into the wire form using
`key_type` / `data_type` from the parent set.

# `table`

```elixir
@spec table(
  Linx.Netfilter.Table.t(),
  keyword()
) :: Linx.Netlink.Message.t()
```

Builds a single-table NEWTABLE message. The result is a
`%Message{}` ready to drop into a batch.

Default flags: `NLM_F_CREATE` — creates the table if missing,
updates flags otherwise. Pass `excl: true` for create-or-fail
(`NLM_F_EXCL`).

Attribute payload:

  * `NFTA_TABLE_NAME` (string)
  * `NFTA_TABLE_FLAGS` (u32 BE) — `:dormant` | `:owner` |
    `:persist` flags OR'd via `Wire.table_flags_int/1`. Omitted
    when zero (libnftnl convention).
  * `NFTA_TABLE_USERDATA` (binary) — omitted when nil.

# `to_batch`

```elixir
@spec to_batch(
  Linx.Netfilter.Ruleset.t(),
  keyword()
) :: [Linx.Netlink.Message.t()]
```

Builds the ordered message list for a `:replace`-mode push of
`ruleset`.

For each table in `ruleset`, the batch contains:

  1. `DESTROYTABLE` — silently destroys any existing table by the
     same `(family, name)`, ensuring fresh state.
  2. `NEWTABLE` — recreate with the desired flags.
  3. `NEWCHAIN` for each chain in the table (in map iteration
     order; the kernel doesn't care about chain order).
  4. `NEWRULE` for each rule in each chain, **in rule-list
     order** — rule ordering matters at runtime, the kernel
     preserves the batch order via `NLM_F_APPEND`.

Objects and flowtables are not yet emitted. The batch shape
supports them by interleaving in step 3.

This list is intended to drop straight into
`Linx.Netlink.Nfnl.batch/2`; it does not include the
`BATCH_BEGIN` / `BATCH_END` envelope (that's `batch/2`'s job).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
