# `Capstan.Protocol.Packet`
[🔗](https://github.com/baselabs/capstan/blob/v1.3.1/lib/capstan/protocol/packet.ex#L1)

MySQL client/server protocol framing.

Every MySQL wire packet is a 4-byte header — `<<length::24-little, sequence_id::8>>` —
followed by exactly `length` payload bytes.

## Split packets (>= 16 MiB)

A logical payload of `0xFFFFFF` bytes or more does not fit the 24-bit length field, so
the peer splits it: each `0xFFFFFF`-byte packet means *more follows*, and the sequence
terminates with the first packet shorter than `0xFFFFFF` — which is **zero-length** when
the logical payload is an exact multiple of `0xFFFFFF`.

Reading a continuation packet as a fresh header does not fail loudly: the reader
desynchronises mid-payload and every subsequent frame is garbage. `read_packet/2`
therefore reassembles the whole logical payload before returning, and **raises** on a
short or truncated read rather than returning a partial payload.

Errors raised here never include payload bytes — only lengths, frame indices and the
transport reason.

# `sequence_id`

```elixir
@type sequence_id() :: 0..255
```

MySQL packet sequence id — wraps at 255.

# `socket`

```elixir
@type socket() :: {:gen_tcp, :gen_tcp.socket()} | {:ssl, :ssl.sslsocket()}
```

A socket tagged with its transport module, so the same framing serves both the
plaintext and the TLS-upgraded connection.

# `encode`

```elixir
@spec encode(binary(), sequence_id()) :: iodata()
```

Frames `payload` under `sequence_id`, splitting it when it reaches 16 MiB.

A payload that is an exact multiple of `0xFFFFFF` bytes is terminated with a
zero-length packet, mirroring what `read_packet/2` expects to read. Sequence ids
increment per frame and wrap at 255.

# `lenenc_int`

```elixir
@spec lenenc_int(binary()) :: {non_neg_integer(), binary()}
```

Reads a length-encoded integer, returning `{value, rest}`.

# `lenenc_str`

```elixir
@spec lenenc_str(binary()) :: {binary(), binary()}
```

Reads a length-encoded string, returning `{value, rest}`.

# `read_packet`

```elixir
@spec read_packet(socket(), timeout()) :: {sequence_id(), binary()}
```

Reads one logical packet, reassembling a split (>= 16 MiB) payload.

Returns `{sequence_id, payload}` where `sequence_id` is the id of the **last** frame of
the sequence. Raises if the stream ends or stalls mid-packet.

# `send_packet`

```elixir
@spec send_packet(socket(), binary(), sequence_id()) :: :ok | {:error, term()}
```

Frames `payload` under `sequence_id` and writes it to `socket`.

---

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