From patchwork Sat Aug 8 07:23:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 5217 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:21cf:b0:87d:ab56:3700 with SMTP id t15csp466274mae; Sat, 8 Aug 2026 00:24:01 -0700 (PDT) X-Forwarded-Encrypted: i=2; AHgh+RoqHO4kZESoWaol7rfZY3E6BGK8dPTAPPgJFczSEEod65L4clJlOi/6IFJIgJFzbYA+w2rpj5I2/es=@openvpn.net X-Received: by 2002:a05:6870:e89:b0:44c:ebe0:f50 with SMTP id 586e51a60fabf-459ffab3134mr4868890fac.14.1786173841369; Sat, 08 Aug 2026 00:24:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1786173841; cv=none; d=google.com; s=arc-20260327; b=Ex9webEfhXh4HPmaAj/BTnSKdWUy170sxoE0+fMXIHPzI6ib4x4yvqGUSoc2/X5EJM qd+GKf5xUHSYyLXyVwxRmqd2hiTLsfNaor+T+y/G54xdZoFmfT5Esm87axOSmEY3NsnH 9BlxseDOxMooRlGlmYYnIWIQER0fqTInLoOtrljKWGBuvP+Y2av+tKsybLwwhXsyAmZe 93whCFxkLIis2/oE19rdTkTw0ttF4KCfHiZ4pYao8m2gBFg5hwYndOLZjSHP+cXCFQQj QYM32WRE1n8PmRaagkmvzzvonWVYT2MkdWESaIxCWvZpYBuIKUs0e8HALVifIEbE8J9d 7WgA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature:dkim-signature; bh=og8UVWYyaH19Owm7/c8wlREb+TFiRKnfiJ+4s5b1qSw=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=goXPPpmwcpHAwNnrGDILrBqKOdOOVndReqdico0p+dVp4166zirPY2riO74ADBoADH ohTUJvNpCzfYqKrqpa8bVvu0NmDjwQXW2tsjkoLEf2Qo64eVBbOUaFspfIRD74lxlIoL lo3GCUN7MdCY6B+Uzejm8qMFFx5vPGaHyFH5+KG8XJTGspwVHtrIwEfXwAXmKJ20Co28 iZMli3YzdEv+LeJEMhmvQbfuOTRdwldwVafQNDMl2XTG6/FYCuoBJku9ygCMu6p/OCAs KaucoyQIWsJ/awjALSSU0T1Ts/BPOynGYiunSUVseyDJJ0RJXHP9kdjq3BKmfMwUqnAm SO7A==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=CFXaEGlF; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=EJRnPvmR; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=FUjS6MjG; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 586e51a60fabf-459f1a767bdsi3608581fac.112.2026.08.08.00.24.00 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sat, 08 Aug 2026 00:24:01 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=pass header.i=@lists.sourceforge.net header.s=beta header.b=CFXaEGlF; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=EJRnPvmR; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=FUjS6MjG; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.sourceforge.net; s=beta; h=Content-Transfer-Encoding:Content-Type: List-Subscribe:List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id: Subject:MIME-Version:References:In-Reply-To:Message-ID:Date:To:From:Sender: Reply-To:Cc:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Owner; bh=og8UVWYyaH19Owm7/c8wlREb+TFiRKnfiJ+4s5b1qSw=; b=CFXaEGlFzJpxnFlg8CbMA16PKh xBWHYqDoWKylMa0KrozSZuX+g8h3epNG95H8acUm6ht3ys46iEJGQfV6ynqRa/XiQTALdJ7OZrpkr WjFD2ODPEQY375lJaJoYMdq4aJuXKkeg3UzECQYNPgKwurgZgVuqwWu95ZRV9UiHlY/s=; Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1wsbPP-00052U-7C; Sat, 08 Aug 2026 07:23:55 +0000 Received: from [172.30.29.66] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1wsbP8-00051X-HN for openvpn-devel@lists.sourceforge.net; Sat, 08 Aug 2026 07:23:38 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:Content-Type:MIME-Version :References:In-Reply-To:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=MFi9by0E0Ok8v76buFewGhRkjX3AIeR1CN2qHZrJz+s=; b=EJRnPvmRXDouhWyxhzty4BPCah jHoD/MTmVm1SbWaRJuDiYR2bhWGM203sLrc0PUVOCmt8lI4MBHaLaVHJm89ZUFWaLgBL2VNuJWwqK 0j6T7Khhn2LPkpPZqEYknJxtusikskRrGctRNMyRq+UTCM3RalC5azmtqC12UzNvTWAg=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:Content-Type:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=MFi9by0E0Ok8v76buFewGhRkjX3AIeR1CN2qHZrJz+s=; b=FUjS6MjGo8ljOIW3kzHjs0lh8e cGcFAQ3Rx6F4fwMaTMvkJKivLgPH5lYWvkq2A5pY8YNGlAn3ZbmejkOkYjvFGvvpyTFdrbmDY7LLA ZClbB1gVy/WPSOdq6W36zczNmfDsnyvRd/3jNAWvJwd24W88TF59WD+0zkRTvjdBsicg=; Received: from [193.149.48.129] (helo=blue.greenie.muc.de) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1wsbP0-0004pI-Qc for openvpn-devel@lists.sourceforge.net; Sat, 08 Aug 2026 07:23:31 +0000 Received: from blue.greenie.muc.de (localhost [127.0.0.1]) by blue.greenie.muc.de (8.18.1/8.18.1) with ESMTP id 6787NKug006273 for ; Sat, 8 Aug 2026 09:23:20 +0200 Received: (from gert@localhost) by blue.greenie.muc.de (8.18.2/8.18.1/Submit) id 6787NKNk006272 for openvpn-devel@lists.sourceforge.net; Sat, 8 Aug 2026 09:23:20 +0200 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Sat, 8 Aug 2026 09:23:11 +0200 Message-ID: <20260808072319.6228-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.53.0 In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: 1.3 (+) X-Spam-Report: Spam detection software, running on the system "sfi-spamd-2.hosts.colo.sdot.me", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: From: Frank Lichtenheld Document all previously undocumented elements. Github: Fixes #864 Change-Id: I350ca5b08379ede1480b81055d35dd9a33e30cff Signed-off-by: Frank Lichtenheld Acked-by: Arne Schwabe Gerrit URL: https://g [...] Content analysis details: (1.3 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#DnsBlocklists-dnsbl-block for more information. [193.149.48.129 listed in list.dnswl.org] 1.3 RDNS_NONE Delivered to internal network by a host with no rDNS X-Headers-End: 1wsbP0-0004pI-Qc Subject: [Openvpn-devel] [PATCH v3] doc: add Doxygen documentation to buffer.h X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: 1872939021656826565 X-GMAIL-MSGID: 1872939021656826565 From: Frank Lichtenheld Document all previously undocumented elements. Github: Fixes #864 Change-Id: I350ca5b08379ede1480b81055d35dd9a33e30cff Signed-off-by: Frank Lichtenheld Acked-by: Arne Schwabe Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1581 --- This change was reviewed on Gerrit and approved by at least one developer. I request to merge it to master. Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/1581 This mail reflects revision 3 of this Change. Acked-by according to Gerrit (reflected above): Arne Schwabe diff --git a/src/openvpn/buffer.h b/src/openvpn/buffer.h index 22e045c..924cc4f 100644 --- a/src/openvpn/buffer.h +++ b/src/openvpn/buffer.h @@ -20,6 +20,15 @@ * with this program; if not, see . */ +/** + * @file + * @brief Buffer management functions and garbage collection. + * + * This module provides the core \c buffer type used throughout OpenVPN to + * hold packet data, as well as a simple memory management system + * (\c gc_arena / \c gc_malloc()) and various string/memory utility functions. + */ + #ifndef BUFFER_H #define BUFFER_H @@ -27,6 +36,7 @@ #include "error.h" #include "integer.h" +/** Maximum allowed size (in bytes) for a single buffer allocation. */ #define BUF_SIZE_MAX 1000000 /* @@ -115,51 +125,213 @@ */ struct gc_arena { - struct gc_entry *list; /**< First element of the linked list of - * \c gc_entry structures. */ - struct gc_entry_special *list_special; + struct gc_entry *list; /**< First element of the linked list of + * \c gc_entry structures. */ + struct gc_entry_special *list_special; /**< First element of the linked + * list of \c gc_entry_special + * structures for allocations + * requiring a custom free + * function. */ }; +/** Return a pointer to the start of the buffer content. @see buf_bptr() */ #define BPTR(buf) (buf_bptr(buf)) +/** Return a pointer one past the end of the buffer content. @see buf_bend() */ #define BEND(buf) (buf_bend(buf)) +/** Return a pointer to the last byte of the buffer content, or NULL if empty. @see buf_blast() */ #define BLAST(buf) (buf_blast(buf)) +/** Return the length of the buffer content in bytes. @see buf_len() */ #define BLEN(buf) (buf_len(buf)) +/** Return the length of the buffer content as a \c size_t. @see buf_len() */ #define BLENZ(buf) ((size_t)buf_len(buf)) +/** Return true iff the buffer is defined (has non-NULL data pointer). @see buf_defined() */ #define BDEF(buf) (buf_defined(buf)) +/** Return the buffer content pointer cast to \c char *. @see buf_str() */ #define BSTR(buf) (buf_str(buf)) +/** Return the number of bytes available for appending to the buffer. @see buf_forward_capacity() */ #define BCAP(buf) (buf_forward_capacity(buf)) +/** + * Zeroise and reset a buffer. + * + * Sets all allocated memory to zero and resets \c offset and \c len to zero, + * but does not free the underlying memory. + * + * @param buf The buffer to clear. + */ void buf_clear(struct buffer *buf); +/** + * Free the memory allocated for a buffer. + * + * Frees \c buf->data and resets the buffer to an undefined state. + * + * @param buf The buffer whose memory is to be freed. + */ void free_buf(struct buffer *buf); +/** + * Assign the content of one buffer to another. + * + * Copies the content of \c src into \c dest. The destination buffer must + * already be allocated with sufficient capacity. + * + * @param dest Destination buffer to write into. + * @param src Source buffer to read from. + * + * @return true on success, false if \c dest has insufficient capacity. + */ bool buf_assign(struct buffer *dest, const struct buffer *src); +/** + * Securely clear a null-terminated string. + * + * Overwrites all characters of \c str with zeroes. + * + * @param str The string to clear. + */ void string_clear(char *str); +/** + * Return the number of elements in a NULL-terminated array of strings. + * + * @param array NULL-terminated array of string pointers. + * + * @return Number of non-NULL elements. + */ int string_array_len(const char **array); +/** + * Safely compute the product of two sizes plus an extra amount. + * + * Each of \c m1, \c m2, and \c extra, as well as the final result, are + * checked against \c ALLOC_SIZE_MAX. If any value exceeds the limit the + * function calls \c msg(M_FATAL) and does not return. + * + * @param m1 First multiplicand. + * @param m2 Second multiplicand. + * @param extra Value to add to the product. + * + * @return \c m1 * \c m2 + \c extra. + */ size_t array_mult_safe(const size_t m1, const size_t m2, const size_t extra); +/** Flag for print_argv(): wrap each argument in square brackets. */ #define PA_BRACKET (1 << 0) +/** + * Format a NULL-terminated argument vector as a single string. + * + * @param p NULL-terminated array of string pointers to format. + * @param gc Garbage collection arena for the returned string. + * @param flags Formatting flags (e.g. \c PA_BRACKET). + * + * @return Newly allocated string representation of \c p. + */ char *print_argv(const char **p, struct gc_arena *gc, const unsigned int flags); +/** + * Report a buffer size error and abort. + * + * Called when a requested buffer size exceeds \c BUF_SIZE_MAX or is otherwise + * invalid. Calls \c msg(M_FATAL) and does not return. + * + * @param size The invalid size that was requested. + */ void buf_size_error(const size_t size); +/** + * Allocate a buffer of the given size. + * + * Calls \c out_of_memory() if the allocation fails. The caller is responsible + * for freeing the memory. + * + * @param size Number of bytes to allocate. + * + * @return Newly allocated buffer of capacity \c size. + */ struct buffer alloc_buf(size_t size); -struct buffer alloc_buf_gc(size_t size, - struct gc_arena *gc); /* allocate buffer with garbage collection */ +/** + * Allocate a buffer of the given size under garbage collection. + * + * The allocated memory is registered with \c gc so that it is freed when + * \c gc_free() is called on the arena. Calls \c out_of_memory() if the + * allocation fails. + * + * @param size Number of bytes to allocate. + * @param gc Garbage collection arena to register the allocation with. + * + * @return Newly allocated buffer of capacity \c size. + */ +struct buffer alloc_buf_gc(size_t size, struct gc_arena *gc); +/** + * Duplicate a buffer, including its content. + * + * Allocates a new buffer with the same capacity as \c buf and copies its + * content. Calls \c out_of_memory() if the allocation fails. + * + * @param buf The buffer to duplicate. + * + * @return A newly allocated copy of \c buf. + */ struct buffer clone_buf(const struct buffer *buf); +/** + * Allocate memory and, optionally, zero it. + * + * The allocation is registered with \c a so that it is freed when + * \c gc_free() is called on the arena. Calls \c out_of_memory() if the + * allocation fails. + * + * @param size Number of bytes to allocate. + * @param clear If true, zeroise the allocated memory before returning. + * @param a Garbage collection arena to register the allocation with. + * + * @return Pointer to the newly allocated memory. + */ void *gc_malloc(size_t size, bool clear, struct gc_arena *a); +/** + * Duplicate a string, allocating memory under garbage collection. + * + * @param str The string to duplicate. May be NULL, in which case NULL + * is returned. + * @param gc Garbage collection arena for the returned string, or NULL + * to use plain \c malloc(). + * + * @return A newly allocated copy of \c str, or NULL if \c str is NULL. + */ char *string_alloc(const char *str, struct gc_arena *gc); +/** + * Allocate a buffer containing a copy of the given string. + * + * Wraps \c string_alloc(), so \c gc may be NULL (plain \c malloc() is used + * in that case). The buffer \c len reflects the string length, excluding + * the null terminator, even though the terminator is present in the + * allocated memory. + * + * @param str The string to copy into the buffer. Must not be NULL. + * @param gc Garbage collection arena for the allocation, or NULL. + * + * @return A buffer whose content is a copy of \c str. + */ struct buffer string_alloc_buf(const char *str, struct gc_arena *gc); +/** + * Register an address with a custom free function in a garbage collection + * arena. + * + * When \c gc_free() is called on \c a, \c free_function(addr) will be + * invoked to release the memory. Use this for allocations that require + * something other than plain \c free(), such as \c freeaddrinfo(). + * + * @param addr Pointer to the memory to register. + * @param free_function Function to call to free \c addr. + * @param a Garbage collection arena to register with. + */ void gc_addspecial(void *addr, void (*free_function)(void *), struct gc_arena *a); /** @@ -177,6 +349,7 @@ #ifdef BUF_INIT_TRACKING #define buf_init(buf, offset) buf_init_debug(buf, offset, __FILE__, __LINE__) +/** Debug variant of \c buf_init_dowork() that records the call site for alignment verification. */ bool buf_init_debug(struct buffer *buf, int offset, const char *file, int line); #else @@ -185,31 +358,62 @@ /* inline functions */ + +/** + * Callback to free a \c struct \c addrinfo, suitable for use with + * \c gc_addspecial(). + * + * @param addr Pointer to the \c struct \c addrinfo to free. + */ static inline void gc_freeaddrinfo_callback(void *addr) { freeaddrinfo((struct addrinfo *)addr); } -/** Return an empty struct buffer */ +/** Return an empty, undefined \c struct \c buffer (all fields zero). */ static inline struct buffer clear_buf(void) { return (struct buffer){ 0 }; } +/** + * Return true iff \c buf has a non-NULL data pointer. + * + * A defined buffer has been allocated but may have zero length or negative + * len (i.e. it is not necessarily valid). + * + * @param buf The buffer to test. + */ static inline bool buf_defined(const struct buffer *buf) { return buf->data != NULL; } +/** + * Return true iff \c buf is valid. + * + * A buffer is valid when its data pointer is non-NULL and its \c len is + * non-negative. + * + * @param buf The buffer to test. + */ static inline bool buf_valid(const struct buffer *buf) { return likely(buf->data != NULL) && likely(buf->len >= 0); } +/** + * Return a pointer to the start of the buffer content. + * + * @param buf The buffer to query. + * + * @return Pointer to \c buf->data + \c buf->offset, or NULL if \c buf is + * not valid. + */ static inline uint8_t * buf_bptr(const struct buffer *buf) { @@ -223,6 +427,13 @@ } } +/** + * Return the length of the buffer content. + * + * @param buf The buffer to query. + * + * @return \c buf->len if \c buf is valid, otherwise 0. + */ static int buf_len(const struct buffer *buf) { @@ -236,12 +447,27 @@ } } +/** + * Return a pointer one past the end of the buffer content. + * + * @param buf The buffer to query. + * + * @return Pointer to the byte immediately after the last content byte. + */ static inline uint8_t * buf_bend(const struct buffer *buf) { return buf_bptr(buf) + buf_len(buf); } +/** + * Return a pointer to the last byte of the buffer content. + * + * @param buf The buffer to query. + * + * @return Pointer to the last byte, or NULL if the buffer is empty or + * invalid. + */ static inline uint8_t * buf_blast(const struct buffer *buf) { @@ -255,24 +481,56 @@ } } +/** + * Return true iff \c size is within the allowed buffer size range. + * + * @param size The size to check. + * + * @return true if \c size < \c BUF_SIZE_MAX. + */ static inline bool buf_size_valid(const size_t size) { return likely(size < BUF_SIZE_MAX); } +/** + * Return true iff a signed \c size is within the allowed buffer size range. + * + * Accepts negative values (used for bidirectional length adjustments) as + * long as the absolute value is below \c BUF_SIZE_MAX. + * + * @param size The signed size to check. + * + * @return true if \c -BUF_SIZE_MAX <= size < \c BUF_SIZE_MAX. + */ static inline bool buf_size_valid_signed(const int size) { return likely(size >= -BUF_SIZE_MAX) && likely(size < BUF_SIZE_MAX); } +/** + * Return the buffer content pointer cast to \c char *. + * + * @param buf The buffer to query. + * + * @return The content pointer as a \c char *, or NULL if \c buf is invalid. + */ static inline char * buf_str(const struct buffer *buf) { return (char *)buf_bptr(buf); } +/** + * Reset a buffer to an undefined (unallocated) state. + * + * Sets all fields to zero/NULL without freeing any memory. Use \c free_buf() + * first if the buffer owns allocated memory. + * + * @param buf The buffer to reset. + */ static inline void buf_reset(struct buffer *buf) { @@ -282,6 +540,14 @@ buf->data = NULL; } +/** + * Reset the length and offset of a buffer to zero. + * + * The underlying allocation is preserved and \c capacity is unchanged. + * Equivalent to rewinding the buffer to the beginning for fresh writing. + * + * @param buf The buffer to reset. + */ static inline void buf_reset_len(struct buffer *buf) { @@ -289,6 +555,18 @@ buf->offset = 0; } +/** + * Initialise a buffer with a given initial offset. + * + * Sets \c buf->len to zero and \c buf->offset to \c offset. The buffer must + * already be allocated. Returns false (without modifying \c buf) if \c offset + * is negative, exceeds the buffer capacity, or the data pointer is NULL. + * + * @param buf The buffer to initialise. + * @param offset Initial byte offset from the start of the allocated memory. + * + * @return true on success, false if the parameters are invalid. + */ static inline bool buf_init_dowork(struct buffer *buf, int offset) { @@ -301,6 +579,17 @@ return true; } +/** + * Initialise a buffer with an externally provided writable memory region. + * + * Sets up \c buf to use \c data as its backing store for writing. The first + * byte of \c data is set to zero. Calls \c buf_size_error() (which does not + * return) if \c size exceeds \c BUF_SIZE_MAX. + * + * @param buf The buffer to initialise. + * @param data Pointer to the memory region to use. + * @param size Size of the memory region in bytes. + */ static inline void buf_set_write(struct buffer *buf, uint8_t *data, int size) { @@ -318,6 +607,18 @@ } } +/** + * Initialise a buffer with an externally provided read-only memory region. + * + * Sets up \c buf so that the entire \c data region is the buffer content + * (offset is 0, len and capacity are both set to \c size). The data pointer + * is cast away from const. Calls \c buf_size_error() (which does not return) + * if \c size exceeds \c BUF_SIZE_MAX. + * + * @param buf The buffer to initialise. + * @param data Pointer to the read-only memory region. + * @param size Size of the memory region in bytes. + */ static inline void buf_set_read(struct buffer *buf, const uint8_t *data, size_t size) { @@ -330,7 +631,17 @@ buf->data = (uint8_t *)data; } -/* Like strncpy but makes sure dest is always null terminated */ +/** + * Like \c strncpy() but always null-terminates the destination. + * + * Copies at most \c maxlen - 1 characters from \c src into \c dest and + * writes a null terminator at \c dest[\c maxlen - 1]. Does nothing if + * \c maxlen is zero. + * + * @param dest Destination buffer. + * @param src Source string. + * @param maxlen Size of the destination buffer in bytes. + */ static inline void strncpynt(char *dest, const char *src, size_t maxlen) { @@ -341,7 +652,13 @@ } } -/* return true if string contains at least one numerical digit */ +/** + * Return true if the string contains at least one decimal digit. + * + * @param src The null-terminated string to scan. + * + * @return true if any character in \c src satisfies \c isdigit(). + */ static inline bool has_digit(const char *src) { @@ -401,11 +718,20 @@ #endif } -/* - * printf append to a buffer with overflow check, - * due to usage of vsnprintf, it will leave space for - * a final null character and thus use only - * capacity - 1 +/** + * printf-style append to a buffer with overflow check. + * + * Formats a string and appends it to \c buf. Due to the use of + * \c vsnprintf(), one byte of forward capacity is reserved for a null + * terminator, so at most \c buf_forward_capacity(buf) - 1 bytes of + * formatted output are written. + * + * @param buf The buffer to append to. + * @param format printf-style format string. + * @param ... Format arguments. + * + * @return true if the entire formatted string fit in the buffer, false if + * it was truncated. */ bool buf_printf(struct buffer *buf, const char *format, ...) #ifdef __GNUC__ @@ -417,8 +743,13 @@ #endif ; -/* - * puts append to a buffer with overflow check +/** + * Append a string to a buffer with overflow check. + * + * @param buf The buffer to append to. + * @param str The null-terminated string to append. + * + * @return true if the string fit in the buffer, false if it was truncated. */ bool buf_puts(struct buffer *buf, const char *str); @@ -427,22 +758,76 @@ * remove/add trailing characters */ +/** + * Force a null terminator at the end of the buffer content. + * + * If there is free capacity, a '\\0' byte is appended. If the buffer is + * full, the last content byte is overwritten with '\\0' (i.e. the last + * character is truncated to make room). The function has no effect on an + * empty or invalid buffer. + * + * @param buf The buffer to null-terminate. + */ void buf_null_terminate(struct buffer *buf); +/** + * Remove trailing newline and carriage-return characters from a buffer. + * + * @param buf The buffer to chomp. + */ void buf_chomp(struct buffer *buf); +/** + * Remove all occurrences of a specific byte from the end of a buffer. + * + * @param buf The buffer to modify. + * @param remove The byte value to strip from the tail. + */ void buf_rmtail(struct buffer *buf, uint8_t remove); -/* - * non-buffer string functions +/** @name String Utility Functions + * @brief Non-buffer string functions + */ +/**@{*/ + +/** + * Remove trailing newline and carriage-return characters from a string. + * + * @param str The null-terminated string to chomp. */ void chomp(char *str); +/** + * Remove all trailing characters that appear in a given set. + * + * @param str The null-terminated string to modify in place. + * @param what_to_delete Null-terminated set of characters to strip. + */ void rm_trailing_chars(char *str, const char *what_to_delete); +/** + * Return a pointer past any leading whitespace in a string. + * + * @param str The string to skip whitespace in. + * + * @return Pointer to the first non-whitespace character, or to the null + * terminator if \c str is all whitespace. + */ const char *skip_leading_whitespace(const char *str); +/** + * Null-terminate a fixed-length string buffer. + * + * Ensures that \c str[\c len] is '\\0', clamping \c len to \c capacity - 1 + * if necessary. + * + * @param str The character buffer to null-terminate. + * @param len Current string length. + * @param capacity Total size of the character buffer in bytes. + */ void string_null_terminate(char *str, int len, int capacity); +/**@}*/ +/* End of string utility functions */ /** * Write buffer contents to file. @@ -454,40 +839,114 @@ */ bool buffer_write_file(const char *filename, const struct buffer *buf); -/* - * write a string to the end of a buffer that was - * truncated by buf_printf +/** + * Append a string to the physical end of a buffer that was truncated by + * \c buf_printf(). + * + * If the buffer's forward capacity is one byte or less (i.e. it is full), + * \c str is written at the very end of the allocated memory to act as a + * truncation marker, provided it fits within the total buffer capacity. + * This is used to append a "[more...]" suffix after hex-dump truncation. + * + * @param buf The buffer to append the truncation marker to. + * @param str The null-terminated marker string to write. */ void buf_catrunc(struct buffer *buf, const char *str); -/* - * Parse a string based on a given delimiter char +/** + * Extract the next token from a buffer, delimited by a given character. + * + * Reads bytes from \c buf one at a time until \c delim or end-of-buffer is + * encountered. The token (without the delimiter) is written into \c line, + * which is always null-terminated. The delimiter byte is consumed from + * \c buf but not included in \c line. + * + * @param buf Source buffer to parse. The consumed portion is advanced. + * @param delim Delimiter character. + * @param line Output buffer for the extracted token. + * @param size Size of \c line in bytes (must be > 0). + * + * @return false only when end-of-buffer is reached and no characters were + * extracted; true otherwise (including when a delimiter was found + * or \c line was truncated). */ bool buf_parse(struct buffer *buf, const int delim, char *line, const int size); -/* - * Hex dump -- Output a binary buffer to a hex string and return it. +/** @name Hex Dump + * @brief Output a binary buffer to a hex string and return it. */ -#define FHE_SPACE_BREAK_MASK 0xFF /* space_break parameter in lower 8 bits */ -#define FHE_CAPS 0x100 /* output hex in caps */ +/**@{*/ + +/** Mask for the space_break_flags field of \c format_hex_ex(): number of + * bytes between separators (lower 8 bits). */ +#define FHE_SPACE_BREAK_MASK 0xFF +/** Flag for \c format_hex_ex(): output hex digits in upper case. */ +#define FHE_CAPS 0x100 +/** + * Format a binary buffer as a hex string. + * + * @param data Pointer to the binary data to format. + * @param size Number of bytes to format. + * @param maxoutput Maximum number of output characters (0 = unlimited). + * If the output is truncated, a "[more...]" suffix is + * appended via \c buf_catrunc(). + * @param space_break_flags Lower 8 bits (\c FHE_SPACE_BREAK_MASK) give the + * number of bytes between \c separator insertions. + * Use \c FHE_CAPS to output upper-case hex digits. + * @param separator String to insert between groups of bytes. + * @param gc Garbage collection arena for the returned string. + * + * @return Null-terminated hex string allocated from \c gc. + */ char *format_hex_ex(const uint8_t *data, size_t size, size_t maxoutput, unsigned int space_break_flags, const char *separator, struct gc_arena *gc); +/** + * Format a binary buffer as a hex string with spaces every 4 bytes. + * + * Convenience wrapper around \c format_hex_ex(). + * + * @param data Pointer to the binary data to format. + * @param size Number of bytes to format. + * @param maxoutput Maximum number of output characters (0 = unlimited). + * @param gc Garbage collection arena for the returned string. + * + * @return Null-terminated hex string allocated from \c gc. + */ static inline char * format_hex(const uint8_t *data, size_t size, size_t maxoutput, struct gc_arena *gc) { return format_hex_ex(data, size, maxoutput, 4, " ", gc); } +/**@}*/ +/* End of Hex Dump */ -/* - * Return a buffer that is a subset of another buffer. +/** + * Return a sub-buffer of another buffer. + * + * Either allocates \c size bytes from the front or back of \c buf, + * shrinking \c buf accordingly, and returns a buffer pointing into the + * same memory. + * + * @param buf Source buffer to carve the sub-buffer from. + * @param size Number of bytes to include in the sub-buffer. + * @param prepend If true, take bytes from the front of \c buf (prepend + * space); if false, take bytes from the end (append space). + * + * @return A buffer referencing the allocated region, or an undefined buffer + * if \c buf has insufficient capacity. */ struct buffer buf_sub(struct buffer *buf, int size, bool prepend); -/* - * Check if sufficient space to append to buffer. +/** + * Check whether \c len bytes can be appended to a buffer. + * + * @param buf The buffer to check. + * @param len Number of bytes to append. + * + * @return true if \c buf is valid, \c len is within the allowed range, and + * there is sufficient capacity after the current content. */ - static inline bool buf_safe(const struct buffer *buf, size_t len) { @@ -495,6 +954,18 @@ && buf->offset + buf->len + (int)len <= buf->capacity; } +/** + * Check whether \c len bytes can be added to or removed from a buffer. + * + * Accepts negative \c len to verify that bytes can be removed (length + * decreased). + * + * @param buf The buffer to check. + * @param len Number of bytes to add (positive) or remove (negative). + * + * @return true if the resulting length would remain non-negative and within + * the allocated capacity. + */ static inline bool buf_safe_bidir(const struct buffer *buf, int len) { @@ -509,6 +980,17 @@ } } +/** + * Return the number of bytes that can still be appended to the buffer. + * + * This is the space between the end of the current content and the end of + * the allocated memory. + * + * @param buf The buffer to query. + * + * @return Number of bytes available for appending, or 0 if \c buf is + * invalid. + */ static inline int buf_forward_capacity(const struct buffer *buf) { @@ -527,6 +1009,17 @@ } } +/** + * Return the total number of bytes available from the current offset to the + * end of the allocated memory. + * + * Unlike \c buf_forward_capacity(), this includes the bytes already occupied + * by the current content. + * + * @param buf The buffer to query. + * + * @return Capacity minus offset, or 0 if \c buf is invalid. + */ static inline int buf_forward_capacity_total(const struct buffer *buf) { @@ -545,6 +1038,16 @@ } } +/** + * Return the number of bytes available for prepending to the buffer. + * + * This is the number of bytes between the start of the allocated memory and + * the current content start (i.e. \c buf->offset). + * + * @param buf The buffer to query. + * + * @return \c buf->offset, or 0 if \c buf is invalid. + */ static inline int buf_reverse_capacity(const struct buffer *buf) { @@ -558,6 +1061,17 @@ } } +/** + * Increase or decrease the length of a buffer. + * + * Adjusts \c buf->len by \c inc bytes. A negative \c inc shrinks the + * buffer. The operation is bounds-checked via \c buf_safe_bidir(). + * + * @param buf The buffer to modify. + * @param inc Number of bytes to add to the length (may be negative). + * + * @return true on success, false if the adjustment would violate bounds. + */ static inline bool buf_inc_len(struct buffer *buf, int inc) { @@ -569,11 +1083,18 @@ return true; } -/* - * Make space to prepend to a buffer. - * Return NULL if no space. +/** + * Make space at the front of a buffer for prepending data. + * + * Moves the content start backwards by \c size bytes, increasing \c len + * and decreasing \c offset by the same amount. + * + * @param buf The buffer to modify. + * @param size Number of bytes to reserve for prepending. + * + * @return Pointer to the newly reserved space (new content start), or NULL + * if \c buf is invalid or there is insufficient prepend capacity. */ - static inline uint8_t * buf_prepend(struct buffer *buf, ssize_t size) { @@ -586,6 +1107,17 @@ return BPTR(buf); } +/** + * Advance the content start of a buffer, consuming bytes from the front. + * + * Increases \c offset and decreases \c len by \c size bytes. + * + * @param buf The buffer to advance. + * @param size Number of bytes to skip. + * + * @return true on success, false if \c buf is invalid or \c size exceeds + * the current length. + */ static inline bool buf_advance(struct buffer *buf, ssize_t size) { @@ -598,11 +1130,18 @@ return true; } -/* - * Return a pointer to allocated space inside a buffer. - * Return NULL if no space. +/** + * Reserve space at the end of a buffer for writing. + * + * Increases \c buf->len by \c size and returns a pointer to the start of + * the newly reserved region. + * + * @param buf The buffer to allocate space in. + * @param size Number of bytes to reserve. + * + * @return Pointer to the start of the reserved space, or NULL if there is + * insufficient capacity. */ - static inline uint8_t * buf_write_alloc(struct buffer *buf, size_t size) { @@ -616,6 +1155,18 @@ return ret; } +/** + * Consume bytes from the front of a buffer for reading. + * + * Returns a pointer to the current content start and advances the buffer + * by \c size bytes. + * + * @param buf The buffer to read from. + * @param size Number of bytes to consume. + * + * @return Pointer to the start of the consumed region, or NULL if \c size + * is negative or exceeds the current length. + */ static inline uint8_t * buf_read_alloc(struct buffer *buf, int size) { @@ -630,6 +1181,17 @@ return ret; } +/** + * Append data to a buffer. + * + * Copies \c size bytes from \c src to the end of \c dest. + * + * @param dest Destination buffer. + * @param src Data to append. + * @param size Number of bytes to append. + * + * @return true on success, false if there is insufficient capacity. + */ static inline bool buf_write(struct buffer *dest, const void *src, size_t size) { @@ -642,6 +1204,18 @@ return true; } +/** + * Prepend data to a buffer. + * + * Copies \c size bytes from \c src into the prepend space at the front of + * \c dest. + * + * @param dest Destination buffer. + * @param src Data to prepend. + * @param size Number of bytes to prepend. + * + * @return true on success, false if there is insufficient prepend capacity. + */ static inline bool buf_write_prepend(struct buffer *dest, const void *src, int size) { @@ -654,12 +1228,28 @@ return true; } +/** + * Append a uint8_t to a buffer. + * + * @param dest Destination buffer. + * @param data Byte to append. + * + * @return true on success, false if there is insufficient capacity. + */ static inline bool buf_write_u8(struct buffer *dest, uint8_t data) { return buf_write(dest, &data, sizeof(uint8_t)); } +/** + * Append a uint16_t to a buffer in network byte order. + * + * @param dest Destination buffer. + * @param data Value to append (converted with \c htons()). + * + * @return true on success, false if there is insufficient capacity. + */ static inline bool buf_write_u16(struct buffer *dest, uint16_t data) { @@ -667,6 +1257,14 @@ return buf_write(dest, &u16, sizeof(uint16_t)); } +/** + * Append a uint32_t to a buffer in network byte order. + * + * @param dest Destination buffer. + * @param data Value to append (converted with \c htonl()). + * + * @return true on success, false if there is insufficient capacity. + */ static inline bool buf_write_u32(struct buffer *dest, uint32_t data) { @@ -674,6 +1272,14 @@ return buf_write(dest, &u32, sizeof(uint32_t)); } +/** + * Append a uint64_t to a buffer in network byte order. + * + * @param dest Destination buffer. + * @param data Value to append (converted with \c htonll()). + * + * @return true on success, false if there is insufficient capacity. + */ static inline bool buf_write_u64(struct buffer *dest, uint64_t data) { @@ -681,12 +1287,32 @@ return buf_write(dest, &u64, sizeof(uint64_t)); } +/** + * Copy the content of one buffer to the end of another. + * + * @param dest Destination buffer. + * @param src Source buffer. + * + * @return true on success, false if \c dest has insufficient capacity. + */ static inline bool buf_copy(struct buffer *dest, const struct buffer *src) { return buf_write(dest, BPTR(src), BLENZ(src)); } +/** + * Read \c n bytes from \c src and append them to \c dest. + * + * Consumes the bytes from \c src. + * + * @param dest Destination buffer. + * @param src Source buffer (modified). + * @param n Number of bytes to copy. + * + * @return true on success, false if \c src has fewer than \c n bytes or + * \c dest has insufficient capacity. + */ static inline bool buf_copy_n(struct buffer *dest, struct buffer *src, int n) { @@ -698,6 +1324,22 @@ return buf_write(dest, cp, n); } +/** + * Copy a range of bytes from one buffer into a specific position in another. + * + * Copies \c src_len bytes starting at \c src_index within \c src content + * into \c dest at \c dest_index relative to the content start. Updates + * \c dest->len if the write extends beyond the current content. Does not + * advance either buffer's offset. + * + * @param dest Destination buffer. + * @param dest_index Byte offset within \c dest content to write at. + * @param src Source buffer. + * @param src_index Byte offset within \c src content to read from. + * @param src_len Number of bytes to copy. + * + * @return true on success, false if any index or length is out of range. + */ static inline bool buf_copy_range(struct buffer *dest, int dest_index, const struct buffer *src, int src_index, int src_len) @@ -715,7 +1357,19 @@ return true; } -/* truncate src to len, copy excess data beyond len to dest */ +/** + * Truncate \c src to \c len bytes and copy any excess to \c dest. + * + * If \c src->len > \c len, \c src is truncated to \c len bytes and the + * remaining bytes are appended to \c dest. If \c src->len <= \c len, + * nothing is copied and true is returned. + * + * @param dest Destination buffer for the excess data. + * @param src Source buffer to truncate. + * @param len Maximum number of bytes to keep in \c src. + * + * @return true on success, false if \c len is negative or copying fails. + */ static inline bool buf_copy_excess(struct buffer *dest, struct buffer *src, int len) { @@ -739,6 +1393,17 @@ } } +/** + * Read bytes from the front of a buffer into a caller-supplied destination. + * + * Consumes \c size bytes from \c src and copies them to \c dest. + * + * @param src Source buffer (modified). + * @param dest Destination memory. + * @param size Number of bytes to read. + * + * @return true on success, false if \c src has fewer than \c size bytes. + */ static inline bool buf_read(struct buffer *src, void *dest, int size) { @@ -751,6 +1416,14 @@ return true; } +/** + * Return the first byte of the buffer without consuming it. + * + * @param buf The buffer to peek at. + * + * @return The first byte as an unsigned value (0–255), or -1 if the buffer + * is empty. + */ static inline int buf_peek_u8(struct buffer *buf) { @@ -763,6 +1436,13 @@ return ret; } +/** + * Read and consume a uint8_t from the front of a buffer. + * + * @param buf The buffer to read from. + * + * @return The byte value (0–255), or -1 if the buffer is empty. + */ static inline int buf_read_u8(struct buffer *buf) { @@ -774,6 +1454,16 @@ return ret; } +/** + * Read and consume a uint16_t from the front of a buffer. + * + * The value is converted from network byte order via \c ntohs(). + * + * @param buf The buffer to read from. + * + * @return The host-byte-order value, or -1 if the buffer has fewer than 2 + * bytes. + */ static inline int buf_read_u16(struct buffer *buf) { @@ -785,6 +1475,17 @@ return ntohs(ret); } +/** + * Read and consume a uint32_t from the front of a buffer. + * + * The value is converted from network byte order via \c ntohl(). + * + * @param buf The buffer to read from. + * @param good If non-NULL, set to true on success or false on failure. + * + * @return The host-byte-order value, or 0 if the buffer has fewer than 4 + * bytes (check \c *good to distinguish from a legitimate zero). + */ static inline uint32_t buf_read_u32(struct buffer *buf, bool *good) { @@ -807,8 +1508,17 @@ } } -/* Read a 64-bit big-endian value (see buf_write_u64()). Sets *good to indicate - * success, like buf_read_u32(). */ +/** + * Read and consume a uint64_t from the front of a buffer. + * + * The value is converted from network byte order via \c ntohll(). + * + * @param buf The buffer to read from. + * @param good If non-NULL, set to true on success or false on failure. + * + * @return The host-byte-order value, or 0 if the buffer has fewer than 8 + * bytes (check \c *good to distinguish from a legitimate zero). + */ static inline uint64_t buf_read_u64(struct buffer *buf, bool *good) { @@ -866,31 +1576,73 @@ return memcmp(BPTR(src), match, size) == 0; } +/** + * Return true if the head of \c src matches the string \c match. + * + * Compares the first \c strlen(match) bytes of \c src content with \c match. + * *NOT* constant time. Do not use when comparing HMACs. + * + * @param src Buffer whose head is compared. + * @param match Null-terminated string to compare against. + * + * @return true if the buffer starts with \c match. + */ bool buf_string_match_head_str(const struct buffer *src, const char *match); +/** + * Compare the head of \c src with \c match and advance past it if equal. + * + * If the buffer starts with \c match, the matched bytes are consumed from + * \c src. *NOT* constant time. Do not use when comparing HMACs. + * + * @param src Buffer to compare and advance. + * @param match Null-terminated string to compare against. + * + * @return true if the buffer started with \c match (and was advanced). + */ bool buf_string_compare_advance(struct buffer *src, const char *match); +/** + * Return the length of the next token in a buffer up to a delimiter. + * + * Scans the buffer content for \c delim and returns the number of bytes + * up to (but not including) the delimiter, or the full buffer length if + * the delimiter is not found. + * + * @param buf The buffer to scan. + * @param delim Delimiter byte to search for. + * + * @return Number of bytes before the first occurrence of \c delim, or -1 + * if the buffer is empty. + */ int buf_substring_len(const struct buffer *buf, int delim); -/* - * Print a string which might be NULL +/** + * Return a printable representation of a string that might be NULL. + * + * @param str String to print, or NULL. + * + * @return \c str if non-NULL, otherwise the string \c "[NULL]". */ const char *np(const char *str); -/* character classes */ - +/** @name Character classes + * @brief Check or modify strings based on classes of allowed + * or forbidden characters. + */ +/**@{*/ #define CC_ANY (1 << 0) /**< any character */ #define CC_NULL (1 << 1) /**< null character \0 */ -#define CC_ALNUM (1 << 2) /**< alphanumeric isalnum() */ -#define CC_ALPHA (1 << 3) /**< alphabetic isalpha() */ +#define CC_ALNUM (1 << 2) /**< alphanumeric \c isalnum() */ +#define CC_ALPHA (1 << 3) /**< alphabetic \c isalpha() */ #define CC_ASCII (1 << 4) /**< ASCII character */ -#define CC_CNTRL (1 << 5) /**< control character iscntrl() */ -#define CC_DIGIT (1 << 6) /**< digit isdigit() */ +#define CC_CNTRL (1 << 5) /**< control character \c iscntrl() */ +#define CC_DIGIT (1 << 6) /**< digit \c isdigit() */ #define CC_PRINT (1 << 7) /**< printable (>= 32, != 127) */ -#define CC_PUNCT (1 << 8) /**< punctuation ispunct() */ -#define CC_SPACE (1 << 9) /**< whitespace isspace() */ -#define CC_XDIGIT (1 << 10) /**< hex digit isxdigit() */ +#define CC_PUNCT (1 << 8) /**< punctuation \c ispunct() */ +#define CC_SPACE (1 << 9) /**< whitespace \c isspace() */ +#define CC_XDIGIT (1 << 10) /**< hex digit \c isxdigit() */ #define CC_BLANK (1 << 11) /**< space or tab */ #define CC_NEWLINE (1 << 12) /**< newline */ @@ -918,8 +1670,27 @@ #define CC_NAME (CC_ALNUM | CC_UNDERBAR) /**< alphanumeric plus underscore */ #define CC_CRLF (CC_CR | CC_NEWLINE) /**< carriage return or newline */ +/** + * Test whether a character belongs to one or more character classes. + * + * @param c The character to test. + * @param flags Bitmask of \c CC_* character class flags. + * + * @return true if \c c matches any of the classes specified in \c flags. + */ bool char_class(const unsigned char c, const unsigned int flags); +/** + * Test whether all characters in a string satisfy a character class filter. + * + * @param str The null-terminated string to test. + * @param inclusive Classes that are permitted (characters must match at + * least one of these). + * @param exclusive Classes that are forbidden (characters must not match + * any of these, even if they also match \c inclusive). + * + * @return true if every character in \c str is permitted. + */ bool string_class(const char *str, const unsigned int inclusive, const unsigned int exclusive); /** @@ -966,10 +1737,27 @@ */ const char *string_mod_const(const char *str, const unsigned int inclusive, const unsigned int exclusive, const char replace, struct gc_arena *gc); +/**@}*/ +/** + * Replace all leading occurrences of a character in a string. + * + * Scans from the start of \c str and replaces every consecutive \c match + * character with \c replace, stopping at the first character that does not + * match. + * + * @param str The null-terminated string to modify in place. + * @param match The character to replace. + * @param replace The replacement character. + */ void string_replace_leading(char *str, const char match, const char replace); -/** Return true iff str starts with prefix */ +/** + * Return true iff \c str starts with \c prefix. + * + * @param str The string to test. + * @param prefix The prefix to look for. + */ static inline bool strprefix(const char *str, const char *prefix) { @@ -1004,6 +1792,16 @@ * Verify that a pointer is correctly aligned */ #ifdef VERIFY_ALIGNMENT +/** + * Assert that the content pointer of \c buf is 4-byte aligned. + * + * Only compiled in when \c VERIFY_ALIGNMENT is defined. Called via the + * \c verify_align_4() macro. + * + * @param buf Buffer whose content pointer is to be checked. + * @param file Source file of the call site (for the error message). + * @param line Source line of the call site (for the error message). + */ void valign4(const struct buffer *buf, const char *file, const int line); #define verify_align_4(ptr) valign4(buf, __FILE__, __LINE__) @@ -1011,23 +1809,58 @@ #define verify_align_4(ptr) #endif -/* - * Very basic garbage collection, mostly for routines that return - * char ptrs to malloced strings. +/** @name Garbage Collection + * @brief Basic garbage collection, mostly for routines that return + * char ptrs to malloced strings. */ +/**@{*/ +/** + * Move all allocations from one garbage collection arena to another. + * + * After the call \c src is empty and all entries previously in \c src are + * owned by \c dest. + * + * @param dest Arena to move allocations into. + * @param src Arena to move allocations from (emptied on return). + */ void gc_transfer(struct gc_arena *dest, struct gc_arena *src); +/** + * Free all plain allocations in a garbage collection arena. + * + * Internal implementation called by \c gc_free(). Do not call directly. + * + * @param a The arena whose \c list entries are to be freed. + */ void x_gc_free(struct gc_arena *a); +/** + * Free all specially-allocated entries in a garbage collection arena. + * + * Internal implementation called by \c gc_free(). Do not call directly. + * Invokes the custom free function stored in each \c gc_entry_special. + * + * @param a The arena whose \c list_special entries are to be freed. + */ void x_gc_freespecial(struct gc_arena *a); +/** + * Return true iff the arena contains at least one allocation. + * + * @param a The arena to test. + */ static inline bool gc_defined(struct gc_arena *a) { return a->list != NULL; } +/** + * Initialise a garbage collection arena to an empty state. + * + * @param a The arena to initialise. + */ static inline void gc_init(struct gc_arena *a) { @@ -1035,12 +1868,25 @@ a->list_special = NULL; } +/** + * Detach all allocations from an arena without freeing them. + * + * After this call the arena is empty. The caller takes responsibility for + * freeing the previously registered allocations. + * + * @param a The arena to detach. + */ static inline void gc_detach(struct gc_arena *a) { gc_init(a); } +/** + * Allocate and return a new, empty garbage collection arena. + * + * @return An initialised \c gc_arena with empty lists. + */ static inline struct gc_arena gc_new(void) { @@ -1049,6 +1895,14 @@ return ret; } +/** + * Free all allocations in a garbage collection arena. + * + * Calls \c x_gc_free() and \c x_gc_freespecial() as needed to release all + * registered memory. + * + * @param a The arena to free. + */ static inline void gc_free(struct gc_arena *a) { @@ -1062,6 +1916,13 @@ } } +/** + * Free all allocations in a garbage collection arena and reinitialise it. + * + * Equivalent to calling \c gc_free() followed by \c gc_init(). + * + * @param a The arena to reset. + */ static inline void gc_reset(struct gc_arena *a) { @@ -1079,58 +1940,139 @@ /* 1 GB on 32bit systems, they usually can only allocate 2 GB for the * whole process. */ +/** Maximum size for a single array allocation (checked by \c array_mult_safe()). */ #define ALLOC_SIZE_MAX (1u << 30) #else +/** Maximum size for a single array allocation (checked by \c array_mult_safe()). */ #define ALLOC_SIZE_MAX ((size_t)1 << 32) /* 4 GB */ #endif +/** + * Allocate memory for a single object of the given type. + * + * Calls \c check_malloc_return() to abort on allocation failure. + * + * @param dptr Pointer variable that receives the allocation. + * @param type Type of the object to allocate. + */ #define ALLOC_OBJ(dptr, type) \ { \ check_malloc_return((dptr) = (type *)malloc(sizeof(type))); \ } +/** + * Allocate and zero-initialise memory for a single object of the given type. + * + * @param dptr Pointer variable that receives the allocation. + * @param type Type of the object to allocate. + */ #define ALLOC_OBJ_CLEAR(dptr, type) \ { \ ALLOC_OBJ(dptr, type); \ memset((dptr), 0, sizeof(type)); \ } +/** + * Allocate memory for an array of \c n elements of the given type. + * + * Uses \c array_mult_safe() to guard against size overflow. + * Calls \c check_malloc_return() to abort on allocation failure. + * + * @param dptr Pointer variable that receives the allocation. + * @param type Element type of the array. + * @param n Number of elements. + */ #define ALLOC_ARRAY(dptr, type, n) \ { \ check_malloc_return((dptr) = (type *)malloc(array_mult_safe(sizeof(type), (n), 0))); \ } +/** + * Allocate a garbage-collected array of \c n elements of the given type. + * + * @param dptr Pointer variable that receives the allocation. + * @param type Element type of the array. + * @param n Number of elements. + * @param gc Garbage collection arena to register the allocation with. + */ #define ALLOC_ARRAY_GC(dptr, type, n, gc) \ { \ (dptr) = (type *)gc_malloc(array_mult_safe(sizeof(type), (n), 0), false, (gc)); \ } +/** + * Allocate and zero-initialise an array of \c n elements of the given type. + * + * @param dptr Pointer variable that receives the allocation. + * @param type Element type of the array. + * @param n Number of elements. + */ #define ALLOC_ARRAY_CLEAR(dptr, type, n) \ { \ ALLOC_ARRAY(dptr, type, n); \ memset((dptr), 0, (array_mult_safe(sizeof(type), (n), 0))); \ } +/** + * Allocate and zero-initialise a garbage-collected array of \c n elements. + * + * @param dptr Pointer variable that receives the allocation. + * @param type Element type of the array. + * @param n Number of elements. + * @param gc Garbage collection arena to register the allocation with. + */ #define ALLOC_ARRAY_CLEAR_GC(dptr, type, n, gc) \ { \ (dptr) = (type *)gc_malloc(array_mult_safe(sizeof(type), (n), 0), true, (gc)); \ } +/** + * Allocate and zero-initialise a garbage-collected variable-length structure. + * + * Allocates \c sizeof(type) + \c n * \c sizeof(atype) bytes for a structure + * that embeds a variable-length array of \c atype as its last member. + * + * @param dptr Pointer variable that receives the allocation. + * @param type Type of the enclosing structure. + * @param atype Element type of the variable-length array member. + * @param n Number of array elements. + * @param gc Garbage collection arena to register the allocation with. + */ #define ALLOC_VAR_ARRAY_CLEAR_GC(dptr, type, atype, n, gc) \ { \ (dptr) = (type *)gc_malloc(array_mult_safe(sizeof(atype), (n), sizeof(type)), true, (gc)); \ } +/** + * Allocate a garbage-collected object of the given type (uninitialised). + * + * @param dptr Pointer variable that receives the allocation. + * @param type Type of the object to allocate. + * @param gc Garbage collection arena to register the allocation with. + */ #define ALLOC_OBJ_GC(dptr, type, gc) \ { \ (dptr) = (type *)gc_malloc(sizeof(type), false, (gc)); \ } +/** + * Allocate and zero-initialise a garbage-collected object of the given type. + * + * @param dptr Pointer variable that receives the allocation. + * @param type Type of the object to allocate. + * @param gc Garbage collection arena to register the allocation with. + */ #define ALLOC_OBJ_CLEAR_GC(dptr, type, gc) \ { \ (dptr) = (type *)gc_malloc(sizeof(type), true, (gc)); \ } +/** + * Abort if a memory allocation returned NULL. + * + * @param p Return value from \c malloc() or similar; calls \c out_of_memory() + * if \c p is NULL. + */ static inline void check_malloc_return(void *p) { @@ -1139,22 +2081,28 @@ out_of_memory(); } } +/**@}*/ +/* End of GC */ -/* - * Manage lists of buffers +/** @name Buffer Lists + * @brief Manage lists of buffers */ +/**@{*/ + +/** One node in a \c buffer_list linked list. */ struct buffer_entry { - struct buffer buf; - struct buffer_entry *next; + struct buffer buf; /**< The buffer stored in this list node. */ + struct buffer_entry *next; /**< Pointer to the next node, or NULL. */ }; +/** A singly-linked list of buffers, with head/tail pointers for O(1) push. */ struct buffer_list { - struct buffer_entry *head; /* next item to pop/peek */ - struct buffer_entry *tail; /* last item pushed */ - size_t size; /* current number of entries */ - size_t max_size; /* maximum size list should grow to */ + struct buffer_entry *head; /**< Next item to pop/peek. */ + struct buffer_entry *tail; /**< Last item pushed. */ + size_t size; /**< Current number of entries. */ + size_t max_size; /**< Maximum number of entries allowed. */ }; /** @@ -1215,8 +2163,24 @@ */ struct buffer *buffer_list_peek(struct buffer_list *ol); +/** + * Advance past \c n bytes in the head buffer, popping it if it becomes empty. + * + * \c n must not exceed the length of the head buffer; passing a larger value + * triggers an assertion failure. + * + * @param ol The list whose head buffer is to be advanced. + * @param n Number of bytes to skip in the head buffer. + */ void buffer_list_advance(struct buffer_list *ol, ssize_t n); +/** + * Remove and free the head buffer of the list. + * + * Does nothing if \c ol is NULL or empty. + * + * @param ol The list to pop from. + */ void buffer_list_pop(struct buffer_list *ol); /** @@ -1244,6 +2208,20 @@ */ void buffer_list_aggregate_separator(struct buffer_list *bl, const size_t max_len, const char *sep); +/** + * Read a file into a buffer list, one buffer per line. + * + * Opens \c fn for reading and pushes each line into a newly allocated buffer + * list using \c fgets(). Lines longer than \c max_line_len - 1 bytes are + * split across multiple consecutive buffers. + * + * @param fn Path to the file to read. + * @param max_line_len Maximum number of bytes (including null terminator) + * read per \c fgets() call. + * + * @return Pointer to the new buffer list, or NULL if the file could not be + * opened or memory allocation failed. + */ struct buffer_list *buffer_list_file(const char *fn, int max_line_len); /** @@ -1257,5 +2235,7 @@ * error */ struct buffer buffer_read_from_file(const char *filename, struct gc_arena *gc); +/**@}*/ +/* End of Buffer Lists */ #endif /* BUFFER_H */