mirror of
https://github.com/troglobit/finit.git
synced 2026-10-01 05:22:48 +07:00
Device seqnums are 64-bit; the writer and reader stopped at u32.
Adds t/x/d to the skip path so a{sv} consumers tolerate them.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
483 lines
21 KiB
C
483 lines
21 KiB
C
/* libink — brokerless D-Bus server library, born inside Finit
|
|
*
|
|
* Copyright (c) 2026 Joachim Wiberg <troglobit@gmail.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#ifndef LIBINK_LINK_H_
|
|
#define LIBINK_LINK_H_
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef struct link_server link_server_t;
|
|
typedef struct link_connection link_connection_t;
|
|
typedef struct link_call link_call_t;
|
|
typedef struct link_client link_client_t;
|
|
|
|
/* D-Bus message type codes -- see link_reply_t.type. */
|
|
#define LINK_MSG_INVALID 0
|
|
#define LINK_MSG_METHOD_CALL 1
|
|
#define LINK_MSG_METHOD_RETURN 2
|
|
#define LINK_MSG_ERROR 3
|
|
#define LINK_MSG_SIGNAL 4
|
|
|
|
/* Writer is exposed so callers can stack-allocate one for marshalling
|
|
* signal/reply bodies. Treat the fields as opaque; use link_writer_init
|
|
* + the link_w_* helpers + link_writer_finish. Sized for typical D-Bus
|
|
* messages -- the array stack supports up to 8 levels of nesting. */
|
|
#define LINK_WRITER_MAX_NESTING 8
|
|
typedef struct link_writer {
|
|
uint8_t *buf;
|
|
size_t cap;
|
|
size_t off;
|
|
int err;
|
|
struct {
|
|
size_t lenpos;
|
|
size_t elemstart;
|
|
} arrays[LINK_WRITER_MAX_NESTING];
|
|
size_t array_depth;
|
|
} link_writer_t;
|
|
|
|
/* Reader is exposed so callers can stack-allocate one for decoding
|
|
* reply or signal bodies received from a peer. Treat fields as
|
|
* opaque; use link_reader_init + the link_r_* helpers. */
|
|
typedef struct link_reader {
|
|
const uint8_t *base;
|
|
size_t off;
|
|
size_t cap;
|
|
int err; /* sticky */
|
|
} link_reader_t;
|
|
|
|
/* View of an inbound message (method-return, error, or signal),
|
|
* populated by link_client_call(_v) and link_client_wait(), and
|
|
* returned by link_client_reply(). All pointers reference internal
|
|
* client storage and are invalidated by the next call or wait on the
|
|
* same client, or by link_client_close(). `body` is NULL iff
|
|
* body_len==0; `error_name` is non-NULL only when type == LINK_MSG_ERROR;
|
|
* `path`/`interface`/`member` are non-NULL on signals. */
|
|
typedef struct {
|
|
uint8_t type; /* LINK_MSG_METHOD_RETURN, _ERROR, or _SIGNAL */
|
|
const char *signature;
|
|
const char *error_name;
|
|
const char *path;
|
|
const char *interface;
|
|
const char *member;
|
|
const uint8_t *body;
|
|
size_t body_len;
|
|
} link_reply_t;
|
|
|
|
/* ---------- debug tracing ---------- */
|
|
|
|
/* Receives one line per connection, method call, and authorization
|
|
* decision. `func` is the libink function that emitted it, so a host
|
|
* can format it the way it formats its own traces.
|
|
*
|
|
* With no logger installed, the default, a trace point costs one NULL
|
|
* test. With one installed the message is formatted before the host
|
|
* sees it, so a host that only wants tracing sometimes should install
|
|
* and remove the callback rather than discard by level. */
|
|
typedef void (*link_log_cb_t)(void *userdata, const char *func, const char *msg);
|
|
|
|
void link_set_logger(link_log_cb_t cb, void *userdata);
|
|
|
|
/* ---------- caller identity on a broker connection ---------- */
|
|
|
|
/* Handle for a call parked while its caller is identified. Opaque,
|
|
* and safe to hold: it encodes a slot and a generation, so resolving
|
|
* a stale handle is a no-op rather than a use-after-free. */
|
|
typedef uint64_t link_authz_t;
|
|
|
|
/* "Nobody asked yet", distinct from any real uid. link_call_uid()
|
|
* returns this on a broker connection until something forces the
|
|
* question, which today only a LINK_METHOD_PRIVILEGED method does. */
|
|
#define LINK_UID_UNKNOWN ((uid_t)-1)
|
|
|
|
/* A broker sets SENDER to a unique name, ":1.<u32>", so this is very
|
|
* generous. Callers that key anything on a sender must reject longer
|
|
* names rather than truncate: two senders sharing a truncated key
|
|
* would share an identity. */
|
|
#define LINK_SENDER_MAX 64
|
|
|
|
/* Answer "which uid is `sender`?" for a privileged call arriving on a
|
|
* broker connection, where SO_PEERCRED describes the broker and not
|
|
* the caller.
|
|
*
|
|
* Return 0 with *uid set when the answer is already known, 1 to answer
|
|
* later by calling link_uid_resolved() with `tok`, or -1 when it
|
|
* cannot be determined, which fails the call closed. Returning 1
|
|
* without ever calling link_uid_resolved() leaks the slot and leaves
|
|
* the caller without a reply, so always answer. */
|
|
typedef int (*link_uid_resolver_t)(link_connection_t *conn, const char *sender,
|
|
link_authz_t tok, uid_t *uid, void *userdata);
|
|
|
|
void link_server_set_uid_resolver(link_server_t *server, link_uid_resolver_t cb,
|
|
void *userdata);
|
|
|
|
/* May this caller invoke a LINK_METHOD_PRIVILEGED method? Return
|
|
* non-zero to allow. `groups` is the caller's group set (primary gid
|
|
* first), captured from the kernel with SO_PEERGROUPS so the embedder
|
|
* can decide membership without an NSS lookup that would block PID 1;
|
|
* `ngroups` is 0 when the set is unknown, e.g. a call arriving over a
|
|
* broker, where only `uid` is available. Who counts as privileged is
|
|
* the embedder's policy; with no authorizer installed only uid 0 may. */
|
|
typedef int (*link_authorizer_t)(uid_t uid, const gid_t *groups, int ngroups,
|
|
void *userdata);
|
|
|
|
void link_server_set_authorizer(link_server_t *server, link_authorizer_t cb,
|
|
void *userdata);
|
|
|
|
/* Complete a deferred resolve and resume the parked call. Pass
|
|
* (uid_t)-1 to say the caller could not be identified, which denies
|
|
* it. Resolving a handle twice, or one whose connection has since
|
|
* closed, does nothing. The connection is the one the resolver was
|
|
* asked about; a reply callback is handed it as its first argument. */
|
|
void link_uid_resolved(link_connection_t *conn, link_authz_t tok, uid_t uid);
|
|
|
|
/* Called with the reply to an outbound link_connection_call(). `reply`
|
|
* is NULL if the connection dropped before one arrived. */
|
|
typedef void (*link_reply_cb_t)(link_connection_t *conn, const link_reply_t *reply,
|
|
void *userdata);
|
|
|
|
/* Issue a method call on an established connection and invoke `cb`
|
|
* when the reply lands. Unlike link_client_call() this never blocks:
|
|
* the reply is picked up by the normal read loop. Argument marshalling
|
|
* matches link_client_call_v(). */
|
|
int link_connection_call(link_connection_t *conn, const char *destination,
|
|
const char *path, const char *interface, const char *member,
|
|
link_reply_cb_t cb, void *userdata,
|
|
const char *signature, ...);
|
|
|
|
/* Nothing in libink runs a clock, it has no event loop, so calls that
|
|
* go unanswered in either direction are the embedder's to time out.
|
|
* This drops anything held longer than `age_ms` on one connection: a
|
|
* park whose resolver never answered, which leaves its caller
|
|
* org.freedesktop.DBus.Error.TimedOut, and a call whose reply never
|
|
* came, whose callback runs once with a NULL reply exactly as a
|
|
* dropped connection would.
|
|
*
|
|
* Returns how many are still outstanding, so a sweep can stop
|
|
* rearming once nothing is left. */
|
|
int link_connection_expire(link_connection_t *conn, unsigned int age_ms);
|
|
|
|
/* ---------- server / connection lifecycle ---------- */
|
|
|
|
/* Bind a listening socket at `path` with file mode `mode`, e.g. 0660
|
|
* to keep it to root and one group. The mode is applied at bind(),
|
|
* so the socket is never briefly more permissive than asked; setting
|
|
* the owning group afterwards is the caller's job. */
|
|
int link_server_new (link_server_t **server, const char *path, mode_t mode);
|
|
void link_server_free (link_server_t *server);
|
|
int link_server_get_fd(const link_server_t *server);
|
|
|
|
int link_server_accept(link_server_t *server, link_connection_t **conn);
|
|
|
|
/* Insert an externally-authenticated fd into the server's connection
|
|
* set. Used to integrate an outbound peer (e.g. a client-side
|
|
* handshake against an external dbus-daemon) so the same dispatch +
|
|
* signal-fan-out machinery covers it. `peer_uid` becomes what
|
|
* privileged-method checks see; pass (uid_t)-1 to make all
|
|
* LINK_METHOD_PRIVILEGED methods reject by default.
|
|
*
|
|
* On success the connection takes ownership of `fd`. On any failure
|
|
* `fd` is closed before the function returns NULL, so callers never
|
|
* have to track partial state.
|
|
*
|
|
* LINK_ATTACH_BROKER says the peer is a message bus rather than an
|
|
* ordinary client. A broker subscribes on behalf of its own clients
|
|
* and never sends us AddMatch, so signals go to it unconditionally
|
|
* instead of being filtered by this connection's match rules. */
|
|
#define LINK_ATTACH_BROKER 0x01
|
|
|
|
link_connection_t *link_server_attach(link_server_t *server, int fd, uid_t peer_uid,
|
|
unsigned int attach_flags);
|
|
|
|
int link_connection_get_fd (const link_connection_t *conn);
|
|
uid_t link_connection_get_uid (const link_connection_t *conn);
|
|
int link_connection_process (link_connection_t *conn);
|
|
void link_connection_close (link_connection_t *conn);
|
|
|
|
/* ---------- object registration ---------- */
|
|
|
|
typedef int (*link_method_fn)(link_call_t *call, void *userdata);
|
|
|
|
/* Method flags for link_method_t.flags */
|
|
#define LINK_METHOD_PRIVILEGED (1u << 0) /* peer must be uid 0 (root) */
|
|
|
|
typedef struct {
|
|
const char *name; /* member name */
|
|
const char *in_sig; /* input signature (D-Bus, e.g. "" or "s") */
|
|
const char *out_sig; /* output signature */
|
|
unsigned flags; /* OR of LINK_METHOD_* */
|
|
link_method_fn handler;
|
|
} link_method_t;
|
|
|
|
/* A read-only property descriptor. Set via the Properties.Set side
|
|
* is not yet implemented; only Get and GetAll are. The framework
|
|
* emits the variant signature from `sig`; the getter writes only the
|
|
* bare value into the provided writer (link_w_string for "s",
|
|
* link_w_u32 for "u", ...), so the declared type is the single
|
|
* source of truth. */
|
|
typedef int (*link_property_getter_fn)(link_writer_t *w, void *userdata);
|
|
|
|
typedef struct {
|
|
const char *name; /* property name */
|
|
const char *sig; /* D-Bus signature, e.g. "s" */
|
|
link_property_getter_fn getter;
|
|
} link_property_t;
|
|
|
|
/*
|
|
* Declares a signal for introspection only; emission is unchecked,
|
|
* see link_connection_emit_signal().
|
|
*/
|
|
typedef struct {
|
|
const char *name; /* member name */
|
|
const char *sig; /* D-Bus signature, e.g. "sss" */
|
|
} link_signal_t;
|
|
|
|
typedef struct {
|
|
const char *interface; /* e.g. "org.finit.Manager1" */
|
|
const link_method_t *methods; /* terminated by {NULL, ...}, or NULL */
|
|
const link_property_t *properties; /* terminated by {NULL, ...}, or NULL */
|
|
const link_signal_t *signals; /* terminated by {NULL, ...}, or NULL */
|
|
} link_vtable_t;
|
|
|
|
/* Register one (interface, methods) at `path`. Calling repeatedly
|
|
* with the same path and different vtables adds more interfaces at
|
|
* that object. The vtable pointer must outlive the server (typically
|
|
* a static table). */
|
|
int link_server_add_object(link_server_t *server, const char *path,
|
|
const link_vtable_t *vt, void *userdata);
|
|
|
|
/* Remove every vtable registered at `path` and free the object.
|
|
* Returns 0 if the object existed, -1 (errno=ENOENT) otherwise. */
|
|
int link_server_remove_object(link_server_t *server, const char *path);
|
|
|
|
/* ---------- call accessors ---------- */
|
|
|
|
const char *link_call_path (const link_call_t *call);
|
|
const char *link_call_interface(const link_call_t *call);
|
|
const char *link_call_member (const link_call_t *call);
|
|
uid_t link_call_uid (const link_call_t *call);
|
|
|
|
/* ---------- reading method-call arguments ----------
|
|
*
|
|
* Cursor starts at the beginning of the request body. Each
|
|
* function returns 0 on success and advances the cursor; on
|
|
* failure it returns -1 and leaves the cursor in an error state
|
|
* (subsequent reads also fail). Strings reference the
|
|
* connection's rx buffer and are valid for the duration of the
|
|
* method handler. */
|
|
|
|
int link_call_read_byte (link_call_t *call, uint8_t *out);
|
|
int link_call_read_bool (link_call_t *call, int *out);
|
|
int link_call_read_u32 (link_call_t *call, uint32_t *out);
|
|
int link_call_read_u64 (link_call_t *call, uint64_t *out);
|
|
int link_call_read_string(link_call_t *call, const char **out); /* "s" */
|
|
int link_call_read_path (link_call_t *call, const char **out); /* "o" */
|
|
|
|
/* ---------- reply construction ---------- */
|
|
|
|
/* Get the writer for the reply body, write args into it, return 0
|
|
* from the handler. Dispatch finalizes and sends the reply with
|
|
* the out_sig declared on the vtable. May be called once per
|
|
* call. */
|
|
link_writer_t *link_call_reply(link_call_t *call);
|
|
|
|
/* Send a D-Bus error reply. `name` must be a valid D-Bus error
|
|
* name (e.g. "org.freedesktop.DBus.Error.UnknownMethod"); `message`
|
|
* may be NULL. */
|
|
int link_call_reply_error(link_call_t *call, const char *name, const char *message);
|
|
|
|
/* ---------- signal emission ----------
|
|
*
|
|
* Send a signal to a single peer if its AddMatch rules accept it.
|
|
* Callers marshal the body separately and pass the resulting bytes.
|
|
* Returns 0 on success (or "filtered out, nothing sent"), -1 with
|
|
* errno set on failure: EMSGSIZE and EINVAL mean nothing hit the
|
|
* wire and the connection is still usable; anything else is a
|
|
* transport failure that may have left a partial frame -- the
|
|
* caller must drop the peer. */
|
|
int link_connection_emit_signal(link_connection_t *conn,
|
|
const char *path,
|
|
const char *interface,
|
|
const char *member,
|
|
const char *signature,
|
|
const uint8_t *body, size_t body_len);
|
|
|
|
/* ---------- client (outgoing method calls) ----------
|
|
*
|
|
* Connect, authenticate as the current effective uid, send BEGIN.
|
|
* Returns NULL on any failure (caller can fall back to another
|
|
* transport if it has one). */
|
|
link_client_t *link_client_open(const char *path);
|
|
|
|
/* As link_client_open but applies SO_SNDTIMEO + SO_RCVTIMEO before
|
|
* the connect/AUTH handshake. After link_server_attach flips the fd
|
|
* to non-blocking the timeout is silently inert; it only protects
|
|
* the synchronous open path against a hung peer. timeout_ms == 0
|
|
* disables the budget (same behaviour as link_client_open). */
|
|
link_client_t *link_client_open_timeout(const char *path, int timeout_ms);
|
|
|
|
void link_client_close(link_client_t *c);
|
|
|
|
/* Address subsequent calls on `c` to a well-known name. Needed when
|
|
* a broker routes the message, e.g. "org.freedesktop.DBus" to reach
|
|
* the bus driver itself; a brokerless link has a single peer and
|
|
* needs no destination, which is the default. `destination` is not
|
|
* copied, so it must outlive the client. */
|
|
void link_client_set_destination(link_client_t *c, const char *destination);
|
|
|
|
/* Detach the authenticated socket from the client and return the raw
|
|
* fd; subsequent link_client_close on `c` is invalid because the
|
|
* structure has already been freed. Used by callers (e.g. system-bus
|
|
* integration) that want to promote an outbound client connection
|
|
* into a server-attached peer via link_server_attach(). */
|
|
int link_client_steal_fd(link_client_t *c);
|
|
|
|
/* Status codes returned by link_client_call(_v). */
|
|
#define LINK_CALL_OK 0 /* method-return received */
|
|
#define LINK_CALL_ERROR 1 /* server replied with an error */
|
|
#define LINK_CALL_FAIL (-1) /* transport, parse, or invalid-arg failure */
|
|
|
|
/* Send a METHOD_CALL and read the reply synchronously.
|
|
*
|
|
* `signature` and `body`/`body_len` describe the outgoing body --
|
|
* marshal it yourself with link_writer_init + the link_w_* helpers
|
|
* + link_writer_finish. Pass signature=NULL and body=NULL for
|
|
* methods that take no arguments.
|
|
*
|
|
* After the call, inspect the reply via link_client_reply() -- it
|
|
* exposes the body bytes (for callers that want to decode them with
|
|
* link_reader_init + link_r_*) and the error name on LINK_CALL_ERROR.
|
|
* The reply view is invalidated by the next call on the same client
|
|
* or by link_client_close(). */
|
|
int link_client_call(link_client_t *c,
|
|
const char *obj_path,
|
|
const char *interface,
|
|
const char *member,
|
|
const char *signature,
|
|
const uint8_t *body, size_t body_len);
|
|
|
|
/* Convenience wrapper that marshals the outgoing body from varargs
|
|
* matching `signature`. Supported type codes (one per arg):
|
|
* 'y' -> int (promoted uint8_t)
|
|
* 'b' -> int (0/non-zero)
|
|
* 'u' -> uint32_t
|
|
* 's' -> const char *
|
|
* 'o' -> const char * (object path)
|
|
*
|
|
* Pass signature=NULL or "" for void calls. Return value matches
|
|
* link_client_call; an unsupported type code returns LINK_CALL_FAIL
|
|
* with no message sent. */
|
|
int link_client_call_v(link_client_t *c,
|
|
const char *obj_path,
|
|
const char *interface,
|
|
const char *member,
|
|
const char *signature, ...);
|
|
|
|
const link_reply_t *link_client_reply(link_client_t *c);
|
|
|
|
/* Convenience accessors for the common case where a reply carries
|
|
* exactly one string ("s" or "o") or one u32 ("u"). They wrap the
|
|
* link_reader_init + link_r_* pattern; on success return 0 and
|
|
* populate *out, on parse failure or missing body return -1. Use
|
|
* link_client_reply + link_reader_init directly for richer payloads. */
|
|
int link_reply_get_string(const link_reply_t *r, const char **out);
|
|
int link_reply_get_u32 (const link_reply_t *r, uint32_t *out);
|
|
|
|
/* Wait up to `timeout_ms` milliseconds for the next inbound message
|
|
* (typically a SIGNAL delivered after an AddMatch subscription), and
|
|
* populate the same view returned by link_client_reply().
|
|
* timeout_ms < 0 : block forever
|
|
* timeout_ms == 0 : non-blocking (returns 1 immediately if no data)
|
|
* timeout_ms > 0 : wait that long
|
|
* Returns 0 on success, 1 on timeout, -1 on transport/parse error.
|
|
*
|
|
* Note: the timeout gates only the wait for the first byte of the
|
|
* next frame. Once data starts arriving the rest of the message is
|
|
* read blockingly; callers that need a hard upper bound should pass
|
|
* a positive timeout AND have a watchdog at a higher level. */
|
|
int link_client_wait(link_client_t *c, int timeout_ms);
|
|
|
|
/* ---------- standalone writer ----------
|
|
*
|
|
* For marshalling bodies outside a method-call handler (signals,
|
|
* pre-computed replies). Initialise on a caller-owned buffer,
|
|
* write args via link_w_*, then call link_writer_finish which
|
|
* returns the body length or -1 on overflow. */
|
|
void link_writer_init (link_writer_t *w, uint8_t *buf, size_t cap);
|
|
ssize_t link_writer_finish(link_writer_t *w);
|
|
|
|
/* ---------- writer (mirrors the internal marshaller) ---------- */
|
|
|
|
void link_w_byte (link_writer_t *w, uint8_t v);
|
|
void link_w_bool (link_writer_t *w, int v);
|
|
void link_w_u32 (link_writer_t *w, uint32_t v);
|
|
void link_w_u64 (link_writer_t *w, uint64_t v); /* "t" */
|
|
void link_w_string (link_writer_t *w, const char *s); /* "s" */
|
|
void link_w_path (link_writer_t *w, const char *s); /* "o" */
|
|
void link_w_variant_string(link_writer_t *w, const char *s); /* "v" containing "s" */
|
|
void link_w_array_begin (link_writer_t *w, char element_sig);
|
|
void link_w_array_end (link_writer_t *w);
|
|
void link_w_struct_begin(link_writer_t *w);
|
|
void link_w_struct_end (link_writer_t *w);
|
|
|
|
/* ---------- standalone reader ----------
|
|
*
|
|
* For decoding bodies received off the wire (reply or signal).
|
|
* Initialise on the body pointer + length, read via link_r_*,
|
|
* check link_r_done() to confirm everything was consumed. */
|
|
void link_reader_init(link_reader_t *r, const uint8_t *body, size_t len);
|
|
int link_r_byte (link_reader_t *r, uint8_t *out);
|
|
int link_r_bool (link_reader_t *r, int *out);
|
|
int link_r_u32 (link_reader_t *r, uint32_t *out);
|
|
int link_r_u64 (link_reader_t *r, uint64_t *out);
|
|
int link_r_string (link_reader_t *r, const char **out); /* "s" */
|
|
int link_r_path (link_reader_t *r, const char **out); /* "o" */
|
|
int link_r_variant_begin (link_reader_t *r, char *type); /* sig header, cursor at value */
|
|
int link_r_skip_basic (link_reader_t *r, char type); /* skip one basic value */
|
|
int link_r_variant_string(link_reader_t *r, const char **out); /* "v" containing "s" */
|
|
int link_r_align (link_reader_t *r, size_t n); /* skip to next n-byte boundary */
|
|
int link_r_done (const link_reader_t *r);
|
|
|
|
/* Begin reading an "a<T>" array. On success returns 0 and sets
|
|
* *out_end to the absolute reader offset at which the array ends;
|
|
* caller loops while link_r_pos < *out_end. For dict-entry arrays
|
|
* ("a{T}") call link_r_align(r, 8) at the top of each iteration --
|
|
* the element-alignment skip from the array prefix only covers the
|
|
* first entry. */
|
|
int link_r_array_begin(link_reader_t *r, size_t *out_end);
|
|
|
|
/* Byte offset of the next read inside the original body buffer.
|
|
* Use together with the *out_end returned by link_r_array_begin to
|
|
* walk the elements of an "a<T>" payload. */
|
|
size_t link_r_pos (const link_reader_t *r);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* LIBINK_LINK_H_ */
|