PeerNet.Frame (PeerNet v0.1.0)

Copy Markdown View Source

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:safe mode 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 capmax_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:safe mode 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. reason is one of :invalid_term or :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

decode(arg1)

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

decode_raw(arg1)

@spec decode_raw(binary()) ::
  {:ok, binary(), binary()} | :incomplete | {:error, :frame_too_large}

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(term)

@spec encode(term()) :: binary()

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.

encode_raw(body)

@spec encode_raw(binary()) :: binary()

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.

max_frame_bytes()

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