URP.Protocol (urp v0.10.0)

Copy Markdown

Low-level URP (UNO Remote Protocol) binary wire format.

Handles framing, encoding, decoding, and reply parsing for the binaryurp protocol used by LibreOffice's soffice process.

References

Summary

Functions

Decode a compressed string, returning {string, rest}.

Encode a string with URP compressed-length prefix.

Like enc_str/1 but returns iodata to avoid copying large payloads.

True if the frame is a reply (long header, no REQUEST flag). Everything else (long-header request or short-header) is a request.

Null CurrentContext reference: empty OID (0x00) + cache sentinel (0xFFFF).

True if the given func_id is release (one-way, no reply expected).

Parse a reply returning any(string) — extracts the string value.

Extract a human-readable error message from an exception reply.

Parse a reply returning a single signed 32-bit integer.

Parse a reply returning a single interface reference (OID string).

Parse a queryInterface reply — extracts OID from any(XInterface) return value.

Parse a readBytes reply — return value (long) + out param (sequence<byte>).

Parse an incoming request, extracting func_id, body, and one_way flag.

Parse a reply returning sequence<string>.

Encode a UNO PropertyValue struct.

Receive a single URP block, returning the payload.

Build a void reply (LONGHEADER only).

Build a reply with body.

Build a void reply with explicit TID (for cross-thread replies).

Build a reply with body and explicit TID (for cross-thread replies).

Build a URP request header with automatic flag computation.

Send a single URP block: <<size::32, count::32, payload>>.

Reference a type already in the peer's cache.

Register a new interface type in the peer's cache.

Functions

dec_str(arg)

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

Decode a compressed string, returning {string, rest}.

enc_str(s)

@spec enc_str(binary()) :: binary()

Encode a string with URP compressed-length prefix.

enc_str_iodata(s)

@spec enc_str_iodata(binary()) :: iodata()

Like enc_str/1 but returns iodata to avoid copying large payloads.

is_reply?(arg)

@spec is_reply?(binary()) :: boolean()

True if the frame is a reply (long header, no REQUEST flag). Everything else (long-header request or short-header) is a request.

null_ctx()

@spec null_ctx() :: binary()

Null CurrentContext reference: empty OID (0x00) + cache sentinel (0xFFFF).

one_way?(arg1)

@spec one_way?(non_neg_integer()) :: boolean()

True if the given func_id is release (one-way, no reply expected).

Per the URP spec, release (func_id 2) is the only one-way call we'll encounter from soffice. Sending a reply to a one-way call is a protocol violation.

parse_any_string_reply(payload)

@spec parse_any_string_reply(binary()) :: {:ok, String.t()} | {:error, String.t()}

Parse a reply returning any(string) — extracts the string value.

parse_exception(payload)

@spec parse_exception(binary()) :: String.t() | nil

Extract a human-readable error message from an exception reply.

UNO exceptions are Any values containing a struct. The first member of all exception structs is Message (string). Returns the message string, or a fallback if parsing fails.

parse_int32_reply(payload)

@spec parse_int32_reply(binary()) :: {:ok, integer()} | {:error, String.t()}

Parse a reply returning a single signed 32-bit integer.

parse_interface_reply(payload)

@spec parse_interface_reply(binary()) :: {:ok, String.t()} | {:error, String.t()}

Parse a reply returning a single interface reference (OID string).

parse_qi_reply(payload)

@spec parse_qi_reply(binary()) :: {:ok, String.t()} | {:error, String.t()}

Parse a queryInterface reply — extracts OID from any(XInterface) return value.

parse_read_bytes_reply(payload)

@spec parse_read_bytes_reply(binary()) :: {:ok, binary()} | {:error, String.t()}

Parse a readBytes reply — return value (long) + out param (sequence<byte>).

parse_request(arg)

@spec parse_request(binary()) :: %{
  func_id: non_neg_integer(),
  body: binary(),
  type_cache: non_neg_integer() | nil,
  tid: binary() | nil
}

Parse an incoming request, extracting func_id, body, and one_way flag.

Handles both long headers (LONGHEADER set, REQUEST set) and short headers (LONGHEADER not set — func_id in lower 6 bits, all cached values reused).

one_way is true when the sender does not expect a reply (MOREFLAGS absent or MUSTREPLY not set). One-way calls like release must not receive replies.

parse_string_sequence_reply(payload)

@spec parse_string_sequence_reply(binary()) ::
  {:ok, [String.t()]} | {:error, String.t()}

Parse a reply returning sequence<string>.

property(name, type_class, value_bytes)

@spec property(String.t(), non_neg_integer(), iodata()) :: iodata()

Encode a UNO PropertyValue struct.

recv_frame(sock, timeout \\ 120_000, max_frame_size \\ 536_870_912)

@spec recv_frame(:gen_tcp.socket(), timeout(), pos_integer()) :: binary()

Receive a single URP block, returning the payload.

Raises if the block contains more than one message (the C++ writer always sends count=1, but the spec allows count>1).

reply()

@spec reply() :: binary()

Build a void reply (LONGHEADER only).

reply(body)

@spec reply(binary()) :: binary()

Build a reply with body.

reply_with_tid(tid)

@spec reply_with_tid(binary()) :: binary()

Build a void reply with explicit TID (for cross-thread replies).

reply_with_tid(body, tid)

@spec reply_with_tid(binary(), binary()) :: binary()

Build a reply with body and explicit TID (for cross-thread replies).

request(func_id, opts \\ [])

@spec request(
  non_neg_integer(),
  keyword()
) :: binary()

Build a URP request header with automatic flag computation.

Options:

  • :type{:new, type_name, cache_idx} or {:cached, cache_idx}
  • :oid{oid_string, cache_idx}
  • :tid{tid_bytes, cache_idx}

Omitting an option reuses the value from the previous message on the wire.

send_frame(sock, payload)

@spec send_frame(:gen_tcp.socket(), iodata()) :: :ok

Send a single URP block: <<size::32, count::32, payload>>.

type_cached(cache_idx)

@spec type_cached(non_neg_integer()) :: binary()

Reference a type already in the peer's cache.

type_new(name, cache_idx)

@spec type_new(String.t(), non_neg_integer()) :: iodata()

Register a new interface type in the peer's cache.