Tahr.RPC.RecordMarking (tahr v0.1.0)

Copy Markdown View Source

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

Types

Result of decoding a single fragment.

Result of decoding a complete message.

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

fragment_result()

@type fragment_result() ::
  {:ok, last? :: boolean(), body :: binary(), rest :: binary()}
  | :incomplete
  | {:error, term()}

Result of decoding a single fragment.

message_result()

@type message_result() ::
  {:ok, body :: binary(), rest :: binary()} | :incomplete | {:error, term()}

Result of decoding a complete message.

Functions

decode_fragment(arg1)

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

decode_message(buffer)

@spec decode_message(binary()) :: message_result()

Reassemble fragments into a single message body. Returns :incomplete if more bytes are needed.

encode(body)

@spec encode(iodata()) :: iodata()

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_fragment(body, last?)

@spec encode_fragment(iodata(), boolean()) :: iodata()

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.

encode_fragments(body, max_fragment_size)

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