441 lines
25 KiB
C
441 lines
25 KiB
C
#ifndef PROTOCOL_H
|
|
#define PROTOCOL_H
|
|
|
|
#include "data.h"
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdatomic.h>
|
|
|
|
/* Maximum allowed string size for receive_str (64 KB) */
|
|
#define MAX_STRING_SIZE (64 * 1024)
|
|
|
|
/* Hard cap on the optional server->client rejection detail carried by
|
|
* STATUS_ERROR_DETAIL (protocol 2.21.0). A longer message is sliced to this
|
|
* many bytes before it is sent, so a peer can never be made to retain more than
|
|
* this for a rejection and the detail frame stays a small, fixed bound. */
|
|
#define MAX_ERROR_DETAIL_BYTES 4096
|
|
|
|
/* Hard cap on a client diagnostic forwarded over the STATUS_CLIENT_MSG channel
|
|
* (protocol 2.30.0, rsync's --stderr=client). The body is reused from the
|
|
* bounded-string wire helper and sliced to this many bytes before it is sent,
|
|
* so a peer can never be made to retain more than this per message. */
|
|
#define MAX_CLIENT_MSG_BYTES 4096
|
|
|
|
/* Maximum uncompressed file payload accepted by the receiver's whole-file
|
|
* paths. A single whole file is charged against the per-connection memory
|
|
* reservation (MAX_CONNECTION_MEMORY) and against the server allocation
|
|
* ceiling (MAX_SERVER_ALLOC), so this mirrors those 256 MB bounds rather than
|
|
* the older 64 MB chunk-era cap. Chunk-serialized payloads keep their own
|
|
* 64 MB cap (MAX_CHUNK_SIZE). */
|
|
#define MAX_RECEIVE_WHOLE_FILE_SIZE (256ULL * 1024 * 1024)
|
|
|
|
/* Maximum allowed data payload size for receive_data (whole-file bound) */
|
|
#define MAX_DATA_PAYLOAD_SIZE MAX_RECEIVE_WHOLE_FILE_SIZE
|
|
|
|
/* Runtime whole-file receive bound. It defaults to MAX_RECEIVE_WHOLE_FILE_SIZE
|
|
* and exists so the test suite can lower the ceiling (via the
|
|
* FASTSYNC_MAX_WHOLE_FILE_SIZE environment variable, a byte count) and exercise
|
|
* the streaming path with a small, fast transfer. A payload at or below the
|
|
* bound keeps the historical whole-buffer path; a larger one is streamed
|
|
* through a bounded buffer. The value is resolved once per process and never
|
|
* exceeds the compile-time ceiling, so a malicious environment cannot raise it
|
|
* beyond the protocol limit. */
|
|
unsigned long long protocol_whole_file_receive_limit(void);
|
|
|
|
/* Maximum chunk size (64 MB) — prevents unbounded allocation from the wire */
|
|
#define MAX_CHUNK_SIZE (64ULL * 1024 * 1024)
|
|
/* Files larger than this are not kept fully in memory while loading: the
|
|
* loader skips them so the sender streams from the path, and file_checksum
|
|
* hashes them from disk in bounded buffers instead of forcing a full load. */
|
|
#define STREAM_THRESHOLD (64ULL * 1024 * 1024)
|
|
#define MAX_MANIFEST_ENTRIES (1024 * 1024)
|
|
/* Aggregate bytes retained by one received deletion manifest. */
|
|
#define MAX_MANIFEST_BYTES (16ULL * 1024 * 1024)
|
|
#define DEFAULT_MAX_ALLOC (1ULL * 1024 * 1024 * 1024)
|
|
/* Server policy ceiling for a client-provided allocation limit. */
|
|
#define MAX_SERVER_ALLOC (256ULL * 1024 * 1024)
|
|
/* Server-owned floor for the per-message I/O deadline. A client --timeout=0
|
|
(rsync's default) disables the client's own deadlines, but a server session
|
|
must never be held open forever by a silent peer (slow-loris), so the server
|
|
floors the effective deadline at this value. */
|
|
#define SERVER_IO_TIMEOUT_SEC 60
|
|
/* Bounded cumulative per-connection receive budget. In-flight wire buffers,
|
|
decompression buffers and queued (not yet written) file payloads for a
|
|
connection must stay within this ceiling. */
|
|
#define MAX_CONNECTION_MEMORY (256ULL * 1024 * 1024)
|
|
|
|
typedef struct ssl_st SSL;
|
|
|
|
typedef struct ProtocolSession ProtocolSession;
|
|
|
|
/*
|
|
* Transport vtable: the per-session set of I/O primitives the three protocol
|
|
* loops (send, receive, status-read) dispatch through. The ops are selected
|
|
* once, when the session is initialized or its SSL is installed, so the loops
|
|
* never branch on the transport at runtime. A plaintext session uses the
|
|
* read()/write() ops; a TLS session uses the SSL_read()/SSL_write() ops.
|
|
*
|
|
* `send`/`recv` attempt exactly one transfer and return:
|
|
* > 0 bytes transferred,
|
|
* PROTOCOL_IO_RETRY no progress; poll on *wait_events and retry,
|
|
* PROTOCOL_IO_CLOSED peer closed the stream,
|
|
* PROTOCOL_IO_ERROR fatal transport error.
|
|
* `has_pending` reports bytes already buffered by the transport (a TLS record
|
|
* residue); the receive loops skip the poll() gate when it is true.
|
|
*/
|
|
typedef struct ProtocolIoOps {
|
|
ssize_t (*send)(ProtocolSession* session, const void* data, size_t size, short* wait_events);
|
|
ssize_t (*recv)(ProtocolSession* session, void* data, size_t size, short* wait_events);
|
|
bool (*has_pending)(const ProtocolSession* session);
|
|
} ProtocolIoOps;
|
|
|
|
/* Negative sentinels returned by ProtocolIoOps.send/recv (see above). */
|
|
enum {
|
|
PROTOCOL_IO_RETRY = -1,
|
|
PROTOCOL_IO_CLOSED = -2,
|
|
PROTOCOL_IO_ERROR = -3,
|
|
};
|
|
|
|
/*
|
|
* Explicit owner of protocol I/O. A session does not own the descriptors or
|
|
* SSL object; it only describes the transport used by a transfer. This makes
|
|
* it safe to pass the transport to a worker without relying on inherited
|
|
* thread-local state.
|
|
*/
|
|
struct ProtocolSession {
|
|
int read_fd;
|
|
int write_fd;
|
|
SSL* ssl;
|
|
/* Transport dispatch selected by protocol_session_init()/set_ssl(). */
|
|
const ProtocolIoOps* ops;
|
|
unsigned long long bwlimit;
|
|
long long bw_tokens;
|
|
long long bw_last_refill_sec;
|
|
long bw_last_refill_nsec;
|
|
atomic_ullong total_allocated_bytes;
|
|
bool eight_bit_output;
|
|
unsigned long long max_alloc;
|
|
/* Per-session deadline (seconds) applied to every protocol send/receive by
|
|
* protocol_send_n_data / protocol_receive_n_data. The initialized default is
|
|
* the built-in 60 s window; a value <= 0 disables the deadline (rsync's
|
|
* --timeout=0). Set from the negotiated Config->timeout so --timeout is
|
|
* honored by the poll()-driven protocol I/O, not just the socket
|
|
* SO_RCVTIMEO/SO_SNDTIMEO. The server does not propagate a client 0 here: it
|
|
* installs protocol_server_io_timeout_sec() so its sessions keep a floor. */
|
|
int io_timeout_sec;
|
|
};
|
|
|
|
typedef int Status;
|
|
enum NET_STATUS {
|
|
STATUS_OK,
|
|
STATUS_ERROR,
|
|
STATUS_FINISHED,
|
|
STATUS_NEXT,
|
|
STATUS_CHUNK,
|
|
STATUS_MANIFEST,
|
|
STATUS_CHECK,
|
|
STATUS_DELTA_SIGNATURE,
|
|
STATUS_DELTA_DATA,
|
|
STATUS_KEEPALIVE,
|
|
STATUS_ABORT,
|
|
STATUS_CHECK_BATCH,
|
|
/* An explicit directory entry (--dirs / an empty source directory): the sender
|
|
* transmits the path and, when metadata/xattrs are negotiated, their blocks;
|
|
* the receiver creates the directory below the receive root. Protocol 2.30.0
|
|
* inserts an int32 probe flag right after the status when report_dest_info is
|
|
* negotiated: probe=1 is a report-only frame (path only; the receiver answers
|
|
* STATUS_DEST_INFO and creates nothing), probe=0 is a real create that is
|
|
* answered with the directory's pre-transfer state before it is created. */
|
|
STATUS_MKDIR,
|
|
/* --append / --append-verify tail resume. STATUS_APPEND is sent by the
|
|
* receiver after a per-file STATUS_CHECK when the existing destination file
|
|
* is SHORTER than the source and an append mode is negotiated: its payload is
|
|
* the resume offset (the number of prefix bytes already present), after which
|
|
* the sender answers either directly with STATUS_APPEND_DATA (plain --append,
|
|
* prefix not verified) or, for --append-verify, first with STATUS_APPEND_SIG
|
|
* carrying the xxHash64 of the source prefix; the receiver then replies
|
|
* STATUS_APPEND_OK (prefix matched -> sender transmits the tail) or
|
|
* STATUS_NEXT (prefix mismatch -> sender falls back to a full transfer).
|
|
* STATUS_APPEND_DATA carries the tail bytes (compressed data frame). */
|
|
STATUS_APPEND,
|
|
STATUS_APPEND_SIG,
|
|
STATUS_APPEND_OK,
|
|
STATUS_APPEND_DATA,
|
|
/* --hard-links/-H: a sibling (later member) of a source hard-link group.
|
|
* The sender transmits only the path, the run-local link-group id, and the
|
|
* first (data-carrying) member's destination-relative wire path; the receiver
|
|
* creates this entry as a hard link to the first member's installed file
|
|
* (falling back to a byte-identical copy if link() fails). Protocol 2.12.0. */
|
|
STATUS_HARDLINK,
|
|
/* A symlink-type entry (-l/--links, -k/--copy-dirlinks' keep-as-symlink
|
|
* branch). The sender transmits the destination path, the (sender-munged,
|
|
* if --munge-links) symlink target, and optional metadata; the receiver
|
|
* creates a symlink to the unmunged target beneath the receive root (see
|
|
* file_receive_symlink). Protocol 2.13.0. */
|
|
STATUS_SYMLINK,
|
|
/* --devices / --specials (-D): a device or special node the sender wants
|
|
* recreated (not written from content). Payload: destination path, the
|
|
* metadata frame (whose mode's S_IFMT bits carry the node kind), and two
|
|
* int32 rdev major/minor fields. The receiver validates the kind and rdev,
|
|
* confines the node below the receive root, and recreates it (mknod/mkfifo),
|
|
* privilege-gating the mknod. Protocol 2.13.0. */
|
|
STATUS_SPECIAL,
|
|
/* Directory-time superstructure (P7 Wave D, protocol 2.17.0): one or more
|
|
* trailing frames sent after all file data (and after the optional delete
|
|
* manifest) carrying the source directories' captured metadata so the
|
|
* receiver can apply directory mtimes/atimes AFTER all of a directory's
|
|
* children have been written. Payload per frame: an int count, then count
|
|
* repetitions of (wire path string, metadata frame); an entry count larger
|
|
* than MAX_MANIFEST_ENTRIES is split across repeated frames. The receiver
|
|
* defers the actual utimensat until its own delete/publish phase has
|
|
* committed, then skips the whole set when -O/--omit-dir-times is set. */
|
|
STATUS_DIR_TIMES,
|
|
/* Daemon SCRAM-SHA-256 authentication (A7 remediation, protocol 2.19.0).
|
|
* STATUS_AUTH_CHALLENGE: the server requires auth and is about to send the
|
|
* iteration count, the base64 salt and the base64 server nonce.
|
|
* STATUS_AUTH_RESPONSE: the client's reply, followed by the base64 client
|
|
* nonce and the base64 ClientProof. STATUS_AUTH_OK: the client proof
|
|
* verified, followed by the base64 ServerSignature. STATUS_AUTH_FAILED:
|
|
* a single generic refusal (unknown user, off-list user, wrong proof,
|
|
* missing/malformed credentials) after which the server closes without
|
|
* writing any data. */
|
|
STATUS_AUTH_CHALLENGE,
|
|
STATUS_AUTH_RESPONSE,
|
|
STATUS_AUTH_OK,
|
|
STATUS_AUTH_FAILED,
|
|
/* Optional server->client rejection detail (protocol 2.21.0). When the
|
|
* server refuses a transfer for a concrete reason it may send
|
|
* STATUS_ERROR_DETAIL followed by a length-prefixed, bounded string instead
|
|
* of a bare STATUS_ERROR. receive_status() consumes the string and maps the
|
|
* status back to STATUS_ERROR, so every pre-2.21 call site keeps working;
|
|
* callers that want the human-readable reason consult protocol_last_error().
|
|
* Appended immediately after STATUS_AUTH_FAILED so the existing wire values
|
|
* never move. */
|
|
STATUS_ERROR_DETAIL,
|
|
/* Server-contacting --dry-run (protocol 2.21.0). Sent by the receiver in
|
|
* response to a per-file STATUS_CHECK when the wire config carries
|
|
* dry_run=true and the file is NOT already up to date: it tells the sender
|
|
* the file WOULD be transferred, and the sender must NOT transmit any data
|
|
* (the receiver reads none in dry-run). STATUS_OK keeps its meaning in this
|
|
* path ("already up to date / nothing to do"). Appended after
|
|
* STATUS_ERROR_DETAIL so no existing status is renumbered. */
|
|
STATUS_DRY_RUN_TRANSFER,
|
|
/* --max-delete budget exhausted (protocol 2.23.0). Sent by the receiver as
|
|
* the terminal success status INSTEAD of STATUS_OK when a --delete/
|
|
* --delete-missing-args commit removed up to the --max-delete bound but had
|
|
* to skip further extras. The transfer itself succeeded and all file data is
|
|
* stored; the sender maps this to rsync's exit code 25 ("the --max-delete
|
|
* limit stopped deletions"). Appended after STATUS_DRY_RUN_TRANSFER so no
|
|
* existing status is renumbered. */
|
|
STATUS_DELETE_LIMIT,
|
|
/* Destination-state report for output parity (protocol 2.23.0; extended to
|
|
* directories/symlinks in 2.30.0). When the wire config carries
|
|
* report_dest_info=true, the receiver answers every per-file STATUS_CHECK
|
|
* request with STATUS_DEST_INFO FIRST, followed by a fixed record describing
|
|
* the pre-transfer destination entry (int32 has_old; int32 target_matches;
|
|
* uint64 size; int64 mtime; int64 mtime_nsec; uint32 mode; int32 uid;
|
|
* int32 gid). The ordinary STATUS_OK/STATUS_NEXT/... verdict follows, so the
|
|
* sender can render rsync-accurate -i/--out-format columns (new vs modified,
|
|
* and which of size/time/perms/owner/group differ) without changing the
|
|
* transfer decision itself. Protocol 2.30.0 also uses this record for
|
|
* STATUS_MKDIR and STATUS_SYMLINK: the sender consumes it into the entry's
|
|
* dest_state before emitting its change line, and target_matches reports
|
|
* whether an existing symlink's on-disk target already equals the incoming
|
|
* one (so the sender can render `cLc........` vs `.L..t......` and suppress
|
|
* an unchanged symlink). Appended after STATUS_DELETE_LIMIT so no existing
|
|
* status is renumbered. */
|
|
STATUS_DEST_INFO,
|
|
/* Per-directory delete plan (protocol 2.24.0). The sender of a
|
|
* --delete-during/--delete-delay transfer streams one frame per source
|
|
* directory in directory order instead of a single whole-tree keep-set
|
|
* manifest. The receiver applies the plan when it arrives
|
|
* (--delete-during removes that directory's extras immediately) or records
|
|
* the extras and applies them only after the whole transfer succeeded
|
|
* (--delete-delay). Payload: an int32 has_config flag (1 on the first plan
|
|
* of the run, 0 afterwards); when set, the three global config sections
|
|
* (protected-prefix count+paths, size-skipped count+paths, missing-args
|
|
* count+paths); then an int32 apply flag (1 for a real plan, 0 for a
|
|
* config-only carrier frame that must not walk a directory); then the
|
|
* destination-relative directory path wire string
|
|
* ("." for the receive root); then the child-directory count + names and the
|
|
* child-file count + names that must be kept. Appended after
|
|
* STATUS_DEST_INFO so no existing status is renumbered. */
|
|
STATUS_DELETE_PLAN,
|
|
/* End-of-transfer receiver counter report (protocol 2.25.0). When the wire
|
|
* config carries report_stats=true, the receiver sends this status once,
|
|
* immediately before its terminal success status, followed by a fixed stats
|
|
* record (see format_stats_send/receive in format.h) and, when the run is a
|
|
* --dry-run with --delete, the would-delete path list. Protocol 2.30.0
|
|
* appends the four deleted_reg/dir/link/special counters to that record, so
|
|
* --stats can render rsync's `Number of deleted files` per-type breakdown.
|
|
* Appended after STATUS_DELETE_PLAN so no existing status is renumbered. */
|
|
STATUS_STATS,
|
|
/* Client diagnostic channel (protocol 2.30.0, rsync's --stderr=client /
|
|
* --no-msgs2stderr). When the client's --stderr mode is `client`, the
|
|
* client forwards its own diagnostics over this client->server frame
|
|
* (STATUS_CLIENT_MSG followed by a bounded length-prefixed string, capped at
|
|
* MAX_CLIENT_MSG_BYTES) instead of writing them to its local stderr. The
|
|
* receiver reads the string and writes it to the server's stderr (respecting
|
|
* the server log destination). Appended after STATUS_STATS so no existing
|
|
* status is renumbered. */
|
|
STATUS_CLIENT_MSG,
|
|
/* Receiver-side partial transfer (protocol 2.30.0). Sent by the receiver as
|
|
* the terminal status INSTEAD of STATUS_OK when one or more entries failed
|
|
* per-entry without aborting the stream (currently a --devices mknod
|
|
* EPERM/EACCES). The transfer otherwise succeeded and every successfully
|
|
* stored file was acknowledged, so the sender may still remove
|
|
* --remove-source-files sources; the sender maps this to rsync's exit code
|
|
* 23 ("partial transfer due to error"), distinct from a fatal STATUS_ERROR.
|
|
* Appended after STATUS_CLIENT_MSG so no existing status is renumbered. */
|
|
STATUS_PARTIAL
|
|
};
|
|
|
|
void io_set_fds(int read_fd, int write_fd);
|
|
void io_set_bwlimit(unsigned long long bytes_per_sec);
|
|
unsigned long long io_get_bwlimit(void);
|
|
void io_set_ssl(SSL* ssl);
|
|
SSL* io_get_ssl(void);
|
|
/* SSL object of the transport in effect on this thread: the currently bound
|
|
* session's SSL when a TLS session is bound, otherwise the legacy thread-local
|
|
* io_ssl. NULL for a plaintext transport. Unlike io_get_ssl(), this resolves
|
|
* worker threads that bound a TLS session via protocol_session_set_ssl()/
|
|
* protocol_session_bind() but never called io_set_ssl() themselves (C11
|
|
* _Thread_local state is not inherited by a new thread). A bound session only
|
|
* wins when its selected dispatch is TLS; a bound plaintext session (ssl ==
|
|
* NULL) falls back to io_ssl so it can never mask a live encrypted transport.
|
|
* Callers that must choose a TLS-only code path (e.g. file_send.c's sendfile
|
|
* fallback) must use this instead of io_get_ssl(). */
|
|
SSL* protocol_current_ssl(void);
|
|
|
|
/* Process-wide wire byte counters. protocol_send_n_data/protocol_receive_n_data
|
|
* update them; the zero-copy sendfile path reports through
|
|
* protocol_note_bytes_written. Used by the client to render rsync's
|
|
* --stats/--progress totals and the --out-format %b/%c tokens. */
|
|
unsigned long long protocol_bytes_written(void);
|
|
unsigned long long protocol_bytes_read(void);
|
|
void protocol_note_bytes_written(unsigned long long bytes);
|
|
/* Apply --bwlimit pacing to bytes written outside protocol_send_n_data (the
|
|
* plaintext zero-copy sendfile fast path). `file_descriptor` is the wire fd
|
|
* the bytes were written to, so the legacy session is resolved exactly as the
|
|
* preceding send_n_data call resolved it (the bound TLS session still wins when
|
|
* set); resolving with the same fd avoids re-initializing the legacy session
|
|
* and granting a second first-call burst. Runs the same token-bucket throttle,
|
|
* so the sendfile transport is paced identically to the buffered/TLS paths. A
|
|
* no-op when the effective session has no bandwidth limit. */
|
|
void protocol_throttle_bytes(int file_descriptor, size_t bytes);
|
|
|
|
void protocol_session_init(ProtocolSession* session, int read_fd, int write_fd);
|
|
/* Transitional bridge for helpers whose signatures still carry only an fd. */
|
|
void protocol_session_bind(ProtocolSession* session);
|
|
void protocol_session_unbind(void);
|
|
void protocol_session_set_ssl(ProtocolSession* session, SSL* ssl);
|
|
void protocol_session_set_bwlimit(ProtocolSession* session, unsigned long long bytes_per_sec);
|
|
void protocol_session_set_max_alloc(ProtocolSession* session, unsigned long long max_alloc);
|
|
/* Override the per-message send/receive deadline for this session. The value
|
|
* is stored verbatim: a positive value sets the deadline, `sec` <= 0 disables
|
|
* it (rsync's --timeout=0). An explicit long deadline (e.g. the delete-ack
|
|
* wait) is applied per-call by protocol_receive_status_timed and is unaffected
|
|
* by this setter. */
|
|
void protocol_session_set_io_timeout(ProtocolSession* session, int sec);
|
|
/* Effective per-message I/O deadline (seconds) for the currently-bound session.
|
|
* Zero means the deadline is disabled (rsync's --timeout=0). Used by the
|
|
* plaintext sendfile path which bypasses the protocol send primitive. */
|
|
int protocol_get_io_timeout_sec(void);
|
|
/* The server-side effective deadline for a client-requested timeout: a positive
|
|
* client value is honored, otherwise the SERVER_IO_TIMEOUT_SEC floor applies so
|
|
* a silent peer can never hold a session open forever. */
|
|
int protocol_server_io_timeout_sec(int client_timeout);
|
|
void* protocol_alloc(size_t size);
|
|
void* protocol_realloc(void* ptr, size_t size);
|
|
void protocol_session_set_8_bit_output(ProtocolSession* session, bool enabled);
|
|
void protocol_set_8_bit_output(bool enabled);
|
|
bool protocol_send_n_data(ProtocolSession* session, const void* data, size_t data_size);
|
|
bool protocol_receive_n_data(ProtocolSession* session, void* data, size_t data_size);
|
|
bool protocol_send_str(ProtocolSession* session, const char* data);
|
|
char* protocol_receive_str(ProtocolSession* session);
|
|
/* Redacted string variants: identical wire framing to protocol_send_str /
|
|
* protocol_receive_str, but the payload body is replaced by `<redacted>` in the
|
|
* LOG_DEBUG_PROTO debug log. Used for daemon auth material (the username and
|
|
* the proof/signature fields) so a --verbose log can never capture a credential
|
|
* that could be replayed. */
|
|
bool protocol_send_str_redacted(ProtocolSession* session, const char* data);
|
|
char* protocol_receive_str_redacted(ProtocolSession* session);
|
|
bool protocol_send_data(ProtocolSession* session, const Data* data);
|
|
Data* protocol_receive_data_limited(ProtocolSession* session, unsigned long long maximum_size);
|
|
bool protocol_send_int(ProtocolSession* session, int data);
|
|
bool protocol_receive_int(ProtocolSession* session, int* data);
|
|
bool protocol_send_status(ProtocolSession* session, Status status);
|
|
bool protocol_receive_status(ProtocolSession* session, Status* status);
|
|
/* As protocol_receive_status, but with an explicit per-message deadline
|
|
* (seconds) instead of the session's configured io_timeout_sec. */
|
|
bool protocol_receive_status_timed(ProtocolSession* session, Status* status, int timeout_sec);
|
|
bool send_n_data(int file_descriptor, const void* data, size_t data_size);
|
|
bool receive_n_data(int file_descriptor, void* data, size_t data_size);
|
|
|
|
bool send_str(int file_descriptor, const char* data);
|
|
char* receive_str(int file_descriptor);
|
|
/* Redacted fd-level string variants (see protocol_send_str_redacted). */
|
|
bool send_str_redacted(int file_descriptor, const char* data);
|
|
char* receive_str_redacted(int file_descriptor);
|
|
bool send_data(int file_descriptor, const Data* data);
|
|
Data* receive_data(int file_descriptor);
|
|
Data* receive_data_limited(int file_descriptor, unsigned long long maximum_size);
|
|
/* Read exactly `size` bytes as a charged Data body. The length-prefixed
|
|
* receive_data_limited() reads the header itself; this variant is for callers
|
|
* that must inspect the declared size (and possibly stream the body instead)
|
|
* before allocating. `size` must already be within MAX_DATA_PAYLOAD_SIZE. */
|
|
Data* receive_data_body(int file_descriptor, unsigned long long size);
|
|
/* Allocate (and charge) a `size`-byte Data body without reading it; the caller
|
|
* fills `result->data` itself. Used when a frame's leading bytes must be
|
|
* inspected before the rest of the body is read. */
|
|
Data* receive_data_alloc(int file_descriptor, unsigned long long size);
|
|
bool send_int(int file_descriptor, int data);
|
|
bool receive_int(int file_descriptor, int* data);
|
|
bool send_status(int file_descriptor, Status status);
|
|
bool receive_status(int file_descriptor, Status* status);
|
|
/* Send STATUS_ERROR_DETAIL followed by a bounded (<= MAX_ERROR_DETAIL_BYTES)
|
|
* length-prefixed string. Over-long messages are sliced and NULL is treated
|
|
* as "". Returns false if the status or the string could not be sent. */
|
|
bool send_error_detail(int file_descriptor, const char* message);
|
|
/* Send STATUS_CLIENT_MSG followed by a bounded (<= MAX_CLIENT_MSG_BYTES)
|
|
* length-prefixed string carrying a client diagnostic. Over-long messages are
|
|
* sliced and NULL is treated as "". Returns false if the status or the string
|
|
* could not be sent. */
|
|
bool send_client_message(int file_descriptor, const char* message);
|
|
/* Human-readable reason captured from the most recent STATUS_ERROR_DETAIL
|
|
* received on this thread, or "" when the last status was a bare STATUS_ERROR
|
|
* (or no detail was seen). Thread-local, and valid until the next non-keepalive
|
|
* status read on the same thread; a later STATUS_KEEPALIVE does NOT clear it.
|
|
* The detail body is bounded by MAX_ERROR_DETAIL_BYTES: an over-cap declared
|
|
* length is drained and yields "" (so the stream never desyncs), while an
|
|
* absurd length is a fatal framing error that fails the status read. */
|
|
const char* protocol_last_error(void);
|
|
/* Clear the thread-local last-error buffer. */
|
|
void protocol_clear_last_error(void);
|
|
/* receive_status with an explicit per-message deadline in seconds, instead of
|
|
the default RECEIVE_TIMEOUT_SEC. A reply that may legitimately take longer
|
|
(e.g. the early-delete ACK after a large receiver-side deletion) must use
|
|
this so the sender does not abort after the deletion already committed. */
|
|
bool receive_status_timed(int file_descriptor, Status* status, int timeout_sec);
|
|
|
|
/* Callback polled by protocol_receive_status_keepalive once per keepalive
|
|
interval. Return true to stop waiting (e.g. a SIGINT/SIGTERM abort flag was
|
|
set). Kept as a function pointer so the protocol layer does not depend on
|
|
client signal state. */
|
|
typedef bool (*ProtocolWaitAbort)(void);
|
|
|
|
/* Like receive_status_timed, but while the peer is silent it emits
|
|
STATUS_KEEPALIVE every keepalive_interval_sec (the receiver answers each with
|
|
STATUS_KEEPALIVE, which this function consumes and skips) so a long
|
|
server-side operation does not look like a dead connection. The total wait
|
|
is still bounded by timeout_sec; abort_check (may be NULL) is polled every
|
|
interval and, when it returns true, ends the wait immediately with false.
|
|
Runs entirely on the calling thread: the protocol send path is NOT safe for
|
|
concurrent writers, so this must not be paired with a helper thread. */
|
|
bool receive_status_keepalive(int file_descriptor, Status* status, int timeout_sec,
|
|
int keepalive_interval_sec, ProtocolWaitAbort abort_check);
|
|
bool protocol_receive_status_keepalive(ProtocolSession* session, Status* status, int timeout_sec,
|
|
int keepalive_interval_sec, ProtocolWaitAbort abort_check);
|
|
|
|
#endif
|