Wire framing for PeerNet — length-prefixed Erlang Term Format payloads with safety guards against malformed or hostile input.
Wire shape
| 4-byte big-endian length N | N bytes of safe ETF |The length prefix bounds how much we'll buffer before attempting a decode.
The body is decoded with :erlang.binary_to_term/2 in :safe mode.
Safety guarantees
- Atom exhaustion —
:safemode rejects ETF that contains atoms not already present in the BEAM's atom table. Without this, a malicious or confused peer could permanently leak atoms by sending random binaries. - Frame size cap —
max_frame_bytes/0(default 1 MiB) bounds the largest single frame we'll buffer. A peer claiming a frame larger than this is rejected before any allocation happens. - Function references —
:safemode also rejects function term references, which would otherwise let a peer inject closures.
Decoder return values
decode/1 returns one of:
{:ok, term, leftover}— successful decode, with any bytes after the frame returned for the next decode call.:incomplete— not enough bytes to make a complete frame yet; caller should accumulate more bytes and retry.{:error, reason}— wire is malformed and the connection should be closed.reasonis one of:invalid_termor:frame_too_large.
Examples
iex> bin = PeerNet.Frame.encode({:hello, :world})
iex> {:ok, term, ""} = PeerNet.Frame.decode(bin)
iex> term
{:hello, :world}
Summary
Functions
Try to decode a complete frame from binary.
Pull one length-prefixed frame's body out of binary, without
ETF-decoding it.
Encode an arbitrary term to a complete frame.
Wrap an opaque binary in the same length-prefixed frame, without
ETF-wrapping the body. Use this for already-encoded bytes — the
AEAD-ciphertext-plus-tag payloads emitted by PeerNet.Channel,
for example. The matching decoder is decode_raw/1.
The maximum frame size, in bytes, that decode/1 will accept.
Functions
@spec decode(binary()) :: {:ok, term(), binary()} | :incomplete | {:error, :invalid_term | :frame_too_large}
Try to decode a complete frame from binary.
See module docs for return shapes.
Pull one length-prefixed frame's body out of binary, without
ETF-decoding it.
Returns:
{:ok, body, leftover}— the body bytes plus anything after the frame in the stream.:incomplete— caller should buffer more bytes and retry.{:error, :frame_too_large}— frame size exceeded the cap.
Encode an arbitrary term to a complete frame.
No size check on output — encoders are trusted (we control what we send). Decoders apply the cap.
Wrap an opaque binary in the same length-prefixed frame, without
ETF-wrapping the body. Use this for already-encoded bytes — the
AEAD-ciphertext-plus-tag payloads emitted by PeerNet.Channel,
for example. The matching decoder is decode_raw/1.
@spec max_frame_bytes() :: pos_integer()
The maximum frame size, in bytes, that decode/1 will accept.
Frames whose length prefix exceeds this cap are rejected with
{:error, :frame_too_large} before any body bytes are buffered.