ONC RPC record-marking framing — RFC 5531 §11.
Each RPC message on a stream transport (TCP) is split into one or more fragments. Each fragment is preceded by a 4-byte marker:
<<last::1, length::31>>When last is 1, the fragment ends the RPC message; when 0,
more fragments follow. length is the number of payload bytes in
the fragment (NOT including the marker itself).
In practice, almost every NFSv3 client emits exactly one fragment per message — but the protocol mandates fragment-aware decoding, and our reassembler handles the multi-fragment case as well.
Encoding
encode/1 wraps a single message as one final-fragment. Use
encode_fragments/2 if you need to split a large reply across
multiple fragments — for NFSv3 message sizes (typically ≤ 1 MiB)
the single-fragment path is fine.
Decoding
decode_fragment/1 peels one fragment off the front of a buffer
and tells you whether it was the last fragment.
decode_message/1 reassembles a complete message — call it after
enough TCP bytes have arrived; return value :incomplete means
more bytes are needed and the caller should buffer them and try
again. The returned body is the concatenated fragment payloads
(i.e. the raw RPC message body, ready for RPC.Message.decode/1).
Summary
Functions
Peel a single fragment off the front of buffer. Returns
:incomplete if fewer than 4 + length bytes are available.
Reassemble fragments into a single message body. Returns
:incomplete if more bytes are needed.
Wrap body as a single final fragment.
Encode body as a single fragment with the given last? flag.
Encode body split into fragments of at most max_fragment_size
bytes each. The final fragment carries the last? flag set.
Types
Functions
@spec decode_fragment(binary()) :: fragment_result()
Peel a single fragment off the front of buffer. Returns
:incomplete if fewer than 4 + length bytes are available.
@spec decode_message(binary()) :: message_result()
Reassemble fragments into a single message body. Returns
:incomplete if more bytes are needed.
Wrap body as a single final fragment.
Accepts iodata so streaming replies (e.g. NFSv3 READ chunks pulled
from a backend Enumerable) can flow through the encode path
without flattening to a single binary. The 4-byte marker is built
from :erlang.iolist_size/1 and prepended; :gen_tcp.send/2
accepts iolist directly.
Encode body as a single fragment with the given last? flag.
Accepts iodata; returns iodata. The fragment marker is computed
from :erlang.iolist_size/1 so the caller does not need to
pre-flatten.
@spec encode_fragments(binary(), pos_integer()) :: iodata()
Encode body split into fragments of at most max_fragment_size
bytes each. The final fragment carries the last? flag set.