Capstan.Protocol.Packet (Capstan v0.1.0)

Copy Markdown View Source

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.

Summary

Types

MySQL packet sequence id — wraps at 255.

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

Functions

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

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

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

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

Frames payload under sequence_id and writes it to socket.

Types

sequence_id()

@type sequence_id() :: 0..255

MySQL packet sequence id — wraps at 255.

socket()

@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.

Functions

encode(payload, sequence_id)

@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(arg)

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

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

lenenc_str(bin)

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

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

read_packet(socket, timeout \\ 20000)

@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(arg, payload, sequence_id)

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

Frames payload under sequence_id and writes it to socket.