src/cx/buffer.h

Fri, 23 May 2025 12:44:24 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 23 May 2025 12:44:24 +0200
changeset 1327
ed75dc1db503
parent 1318
12fa1d37fe48
permissions
-rw-r--r--

make test-compile depend on both static and shared

the shared lib is not needed for the tests,
but when run with coverage, gcov will be confused
when outdated line information is available from
a previous shared build

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file buffer.h
 *
 * @brief Advanced buffer implementation.
 *
 * Instances of CxBuffer can be used to read from or to write to like one
 * would do with a stream.
 *
 * Some features for convenient use of the buffer
 * can be enabled. See the documentation of the macro constants for more
 * information.
 *
 * @author Mike Becker
 * @author Olaf Wintermann
 * @copyright 2-Clause BSD License
 */

#ifndef UCX_BUFFER_H
#define UCX_BUFFER_H

#include "common.h"
#include "allocator.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * No buffer features enabled (all flags cleared).
 */
#define CX_BUFFER_DEFAULT 0x00

/**
 * If this flag is enabled, the buffer will automatically free its contents when destroyed.
 *
 * Do NOT set this flag together with #CX_BUFFER_COPY_ON_WRITE. It will be automatically
 * set when the copy-on-write operations is performed.
 */
#define CX_BUFFER_FREE_CONTENTS 0x01

/**
 * If this flag is enabled, the buffer will automatically extend its capacity.
 */
#define CX_BUFFER_AUTO_EXTEND 0x02

/**
 * If this flag is enabled, the buffer will allocate new memory when written to.
 *
 * The current contents of the buffer will be copied to the new memory and the flag
 * will be cleared while the #CX_BUFFER_FREE_CONTENTS flag will be set automatically.
 */
#define CX_BUFFER_COPY_ON_WRITE 0x04

/**
 * If this flag is enabled, the buffer will copy its contents to a new memory area on reallocation.
 *
 * After performing the copy, the flag is automatically cleared.
 * This flag has no effect on buffers which do not have #CX_BUFFER_AUTO_EXTEND set, which is why
 * buffers automatically admit the auto-extend flag when initialized with copy-on-extend enabled.
 */
#define CX_BUFFER_COPY_ON_EXTEND 0x08

/**
 * Function pointer for cxBufferWrite that is compatible with cx_write_func.
 * @see cx_write_func
 */
#define cxBufferWriteFunc  ((cx_write_func) cxBufferWrite)
/**
 * Function pointer for cxBufferRead that is compatible with cx_read_func.
 * @see cx_read_func
 */
#define cxBufferReadFunc  ((cx_read_func) cxBufferRead)

/**
 * Configuration for automatic flushing.
 */
struct cx_buffer_flush_config_s {
    /**
     * The buffer may not extend beyond this threshold before starting to flush.
     *
     * Only used when the buffer uses #CX_BUFFER_AUTO_EXTEND.
     * The threshold will be the maximum capacity the buffer is extended to
     * before flushing.
     */
    size_t threshold;
    /**
     * The block size for the elements to flush.
     */
    size_t blksize;
    /**
     * The maximum number of blocks to flush in one cycle.
     *
     * @attention while it is guaranteed that cxBufferFlush() will not flush
     * more blocks, this is not necessarily the case for cxBufferWrite().
     * After performing a flush cycle, cxBufferWrite() will retry the write
     * operation and potentially trigger another flush cycle, until the
     * flush target accepts no more data.
     */
    size_t blkmax;

    /**
     * The target for write function.
     */
    void *target;

    /**
     * The write-function used for flushing.
     * If NULL, the flushed content gets discarded.
     */
    cx_write_func wfunc;
};

/**
 * Type alias for the flush configuration struct.
 *
 * @code
 * struct cx_buffer_flush_config_s {
 *     size_t threshold;
 *     size_t blksize;
 *     size_t blkmax;
 *     void *target;
 *     cx_write_func wfunc;
 * };
 * @endcode
 */
typedef struct cx_buffer_flush_config_s CxBufferFlushConfig;

/** Structure for the UCX buffer data. */
struct cx_buffer_s {
    /** A pointer to the buffer contents. */
    union {
        /**
         * Data is interpreted as text.
         */
        char *space;
        /**
         * Data is interpreted as binary.
         */
        unsigned char *bytes;
    };
    /** The allocator to use for automatic memory management. */
    const CxAllocator *allocator;
    /**
     * Optional flush configuration
     *
     * @see cxBufferEnableFlushing()
     */
    CxBufferFlushConfig *flush;
    /** Current position of the buffer. */
    size_t pos;
    /** Current capacity (i.e. maximum size) of the buffer. */
    size_t capacity;
    /** Current size of the buffer content. */
    size_t size;
    /**
     * Flag register for buffer features.
     * @see #CX_BUFFER_DEFAULT
     * @see #CX_BUFFER_FREE_CONTENTS
     * @see #CX_BUFFER_AUTO_EXTEND
     * @see #CX_BUFFER_COPY_ON_WRITE
     */
    int flags;
};

/**
 * UCX buffer.
 */
typedef struct cx_buffer_s CxBuffer;

/**
 * Initializes a fresh buffer.
 *
 * You may also provide a read-only @p space, in which case
 * you will need to cast the pointer, and you should set the
 * #CX_BUFFER_COPY_ON_WRITE flag.
 *
 * You need to set the size manually after initialization, if
 * you provide @p space which already contains data.
 *
 * When you specify stack memory as @p space and decide to use
 * the auto-extension feature, you @em must use the
 * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the
 * #CX_BUFFER_AUTO_EXTEND flag.
 *
 * @note You may provide @c NULL as argument for @p space.
 * Then this function will allocate the space and enforce
 * the #CX_BUFFER_FREE_CONTENTS flag. In that case, specifying
 * copy-on-write should be avoided, because the allocated
 * space will be leaking after the copy-on-write operation.
 *
 * @param buffer the buffer to initialize
 * @param space pointer to the memory area, or @c NULL to allocate
 * new memory
 * @param capacity the capacity of the buffer
 * @param allocator the allocator this buffer shall use for automatic
 * memory management
 * (if @c NULL, the cxDefaultAllocator will be used)
 * @param flags buffer features (see cx_buffer_s.flags)
 * @return zero on success, non-zero if a required allocation failed
 */
cx_attr_nonnull_arg(1)
cx_attr_export
int cxBufferInit(
        CxBuffer *buffer,
        void *space,
        size_t capacity,
        const CxAllocator *allocator,
        int flags
);

/**
 * Configures the buffer for flushing.
 *
 * Flushing can happen automatically when data is written
 * to the buffer (see cxBufferWrite()) or manually when
 * cxBufferFlush() is called.
 *
 * @param buffer the buffer
 * @param config the flush configuration
 * @retval zero success
 * @retval non-zero failure
 * @see cxBufferFlush()
 * @see cxBufferWrite()
 */
cx_attr_nonnull
cx_attr_export
int cxBufferEnableFlushing(
    CxBuffer *buffer,
    CxBufferFlushConfig config
);

/**
 * Destroys the buffer contents.
 *
 * Has no effect if the #CX_BUFFER_FREE_CONTENTS feature is not enabled.
 * If you want to free the memory of the entire buffer, use cxBufferFree().
 *
 * @param buffer the buffer which contents shall be destroyed
 * @see cxBufferInit()
 */
cx_attr_nonnull
cx_attr_export
void cxBufferDestroy(CxBuffer *buffer);

/**
 * Deallocates the buffer.
 *
 * If the #CX_BUFFER_FREE_CONTENTS feature is enabled, this function also destroys
 * the contents. If you @em only want to destroy the contents, use cxBufferDestroy().
 *
 * @remark As with all free() functions, this accepts @c NULL arguments in which
 * case it does nothing.
 *
 * @param buffer the buffer to deallocate
 * @see cxBufferCreate()
 */
cx_attr_export
void cxBufferFree(CxBuffer *buffer);

/**
 * Allocates and initializes a fresh buffer.
 *
 * You may also provide a read-only @p space, in which case
 * you will need to cast the pointer, and you should set the
 * #CX_BUFFER_COPY_ON_WRITE flag.
 * When you specify stack memory as @p space and decide to use
 * the auto-extension feature, you @em must use the
 * #CX_BUFFER_COPY_ON_EXTEND flag, instead of the
 * #CX_BUFFER_AUTO_EXTEND flag.
 *
 * @note You may provide @c NULL as argument for @p space.
 * Then this function will allocate the space and enforce
 * the #CX_BUFFER_FREE_CONTENTS flag.
 *
 * @param space pointer to the memory area, or @c NULL to allocate
 * new memory
 * @param capacity the capacity of the buffer
 * @param allocator the allocator to use for allocating the structure and the automatic
 * memory management within the buffer
 * (if @c NULL, the cxDefaultAllocator will be used)
 * @param flags buffer features (see cx_buffer_s.flags)
 * @return a pointer to the buffer on success, @c NULL if a required allocation failed
 */
cx_attr_malloc
cx_attr_dealloc(cxBufferFree, 1)
cx_attr_nodiscard
cx_attr_export
CxBuffer *cxBufferCreate(
        void *space,
        size_t capacity,
        const CxAllocator *allocator,
        int flags
);

/**
 * Shifts the contents of the buffer by the given offset.
 *
 * If the offset is positive, the contents are shifted to the right.
 * If auto extension is enabled, the buffer grows, if necessary.
 * In case the auto extension fails, this function returns a non-zero value and
 * no contents are changed.
 * If auto extension is disabled, the contents that do not fit into the buffer
 * are discarded.
 *
 * If the offset is negative, the contents are shifted to the left where the
 * first @p shift bytes are discarded.
 * The new size of the buffer is the old size minus the absolute shift value.
 * If this value is larger than the buffer size, the buffer is emptied (but
 * not cleared, see the security note below).
 *
 * The buffer position gets shifted alongside with the content but is kept
 * within the boundaries of the buffer.
 *
 * @note For situations where @c off_t is not large enough, there are specialized cxBufferShiftLeft() and
 * cxBufferShiftRight() functions using a @c size_t as parameter type.
 *
 * @attention
 * Security Note: The shifting operation does @em not erase the previously occupied memory cells.
 * But you can easily do that manually, e.g. by calling
 * <code>memset(buffer->bytes, 0, shift)</code> for a right shift or
 * <code>memset(buffer->bytes + buffer->size, 0, buffer->capacity - buffer->size)</code>
 * for a left shift.
 *
 * @param buffer the buffer
 * @param shift the shift offset (negative means left shift)
 * @retval zero success
 * @retval non-zero if a required auto-extension or copy-on-write fails
 * @see cxBufferShiftLeft()
 * @see cxBufferShiftRight()
 */
cx_attr_nonnull
cx_attr_export
int cxBufferShift(
        CxBuffer *buffer,
        off_t shift
);

/**
 * Shifts the buffer to the right.
 * See cxBufferShift() for details.
 *
 * @param buffer the buffer
 * @param shift the shift offset
 * @retval zero success
 * @retval non-zero if a required auto-extension or copy-on-write fails
 * @see cxBufferShift()
 */
cx_attr_nonnull
cx_attr_export
int cxBufferShiftRight(
        CxBuffer *buffer,
        size_t shift
);

/**
 * Shifts the buffer to the left.
 * See cxBufferShift() for details.
 *
 * @param buffer the buffer
 * @param shift the positive shift offset
 * @retval zero success
 * @retval non-zero if the buffer uses copy-on-write and the allocation fails
 * @see cxBufferShift()
 */
cx_attr_nonnull
cx_attr_export
int cxBufferShiftLeft(
        CxBuffer *buffer,
        size_t shift
);


/**
 * Moves the position of the buffer.
 *
 * The new position is relative to the @p whence argument.
 *
 * @li @c SEEK_SET marks the start of the buffer.
 * @li @c SEEK_CUR marks the current position.
 * @li @c SEEK_END marks the end of the buffer.
 *
 * With an offset of zero, this function sets the buffer position to zero
 * (@c SEEK_SET), the buffer size (@c SEEK_END) or leaves the buffer position
 * unchanged (@c SEEK_CUR).
 *
 * @param buffer the buffer
 * @param offset position offset relative to @p whence
 * @param whence one of @c SEEK_SET, @c SEEK_CUR or @c SEEK_END
 * @retval zero success
 * @retval non-zero if the position is invalid
 *
 */
cx_attr_nonnull
cx_attr_export
int cxBufferSeek(
        CxBuffer *buffer,
        off_t offset,
        int whence
);

/**
 * Clears the buffer by resetting the position and deleting the data.
 *
 * The data is deleted by zeroing it with a call to memset().
 * If you do not need that, you can use the faster cxBufferReset().
 *
 * @note If the #CX_BUFFER_COPY_ON_WRITE flag is set, this function
 * will not erase the data and behave exactly as cxBufferReset().
 *
 * @param buffer the buffer to be cleared
 * @see cxBufferReset()
 */
cx_attr_nonnull
cx_attr_export
void cxBufferClear(CxBuffer *buffer);

/**
 * Resets the buffer by resetting the position and size to zero.
 *
 * The data in the buffer is not deleted. If you need a safe
 * reset of the buffer, use cxBufferClear().
 *
 * @param buffer the buffer to be cleared
 * @see cxBufferClear()
 */
cx_attr_nonnull
cx_attr_export
void cxBufferReset(CxBuffer *buffer);

/**
 * Tests, if the buffer position has exceeded the buffer size.
 *
 * @param buffer the buffer to test
 * @retval true if the current buffer position has exceeded the last
 * byte of the buffer's contents
 * @retval false otherwise
 */
cx_attr_nonnull
cx_attr_nodiscard
cx_attr_export
bool cxBufferEof(const CxBuffer *buffer);


/**
 * Ensures that the buffer has a minimum capacity.
 *
 * If the current capacity is not sufficient, the buffer will be extended.
 *
 * The new capacity will be a power of two until the system's page size is reached.
 * Then, the new capacity will be a multiple of the page size.
 *
 * @param buffer the buffer
 * @param capacity the minimum required capacity for this buffer
 * @retval zero the capacity was already sufficient or successfully increased
 * @retval non-zero on allocation failure
 * @see cxBufferShrink()
 */
cx_attr_nonnull
cx_attr_export
int cxBufferMinimumCapacity(
        CxBuffer *buffer,
        size_t capacity
);

/**
 * Shrinks the capacity of the buffer to fit its current size.
 *
 * If @p reserve is larger than zero, the buffer is shrunk to its size plus
 * the number of reserved bytes.
 *
 * If the current capacity is not larger than the size plus the reserved bytes,
 * nothing happens.
 *
 * If the #CX_BUFFER_COPY_ON_WRITE or #CX_BUFFER_COPY_ON_EXTEND flag is set,
 * this function does nothing.
 *
 * @param buffer the buffer
 * @param reserve the number of bytes that shall remain reserved
 * @see cxBufferMinimumCapacity()
 */
cx_attr_nonnull
cx_attr_export
void cxBufferShrink(
        CxBuffer *buffer,
        size_t reserve
);

/**
 * Writes data to a CxBuffer.
 *
 * If automatic flushing is not enabled, the data is simply written into the
 * buffer at the current position and the position of the buffer is increased
 * by the number of bytes written.
 *
 * If flushing is enabled and the buffer needs to flush, the data is flushed to
 * the target until the target signals that it cannot take more data by
 * returning zero via the respective write function. In that case, the remaining
 * data in this buffer is shifted to the beginning of this buffer so that the
 * newly available space can be used to append as much data as possible.
 *
 * This function only stops writing more elements, when the flush target and this
 * buffer are both incapable of taking more data or all data has been written.
 *
 * If, after flushing, the number of items that shall be written still exceeds
 * the capacity or flush threshold, this function tries to write all items directly
 * to the flush target, if possible.
 *
 * The number returned by this function is the number of elements from
 * @c ptr that could be written to either the flush target or the buffer
 * (so it does not include the number of items that had been already in the buffer
 * in were flushed during the process).
 *
 * @attention
 * When @p size is larger than one and the contents of the buffer are not aligned
 * with @p size, flushing stops after all complete items have been flushed, leaving
 * the mis-aligned part in the buffer.
 * Afterward, this function only writes as many items as possible to the buffer.
 *
 * @note The signature is compatible with the fwrite() family of functions.
 *
 * @param ptr a pointer to the memory area containing the bytes to be written
 * @param size the length of one element
 * @param nitems the element count
 * @param buffer the CxBuffer to write to
 * @return the total count of elements written
 * @see cxBufferAppend()
 * @see cxBufferRead()
 */
cx_attr_nonnull
cx_attr_export
size_t cxBufferWrite(
        const void *ptr,
        size_t size,
        size_t nitems,
        CxBuffer *buffer
);

/**
 * Appends data to a CxBuffer.
 *
 * The data is always appended to current data within the buffer,
 * regardless of the current position.
 * This is especially useful when the buffer is primarily meant for reading
 * while additional data is added to the buffer occasionally.
 * Consequently, the position of the buffer is unchanged after this operation.
 *
 * @note The signature is compatible with the fwrite() family of functions.
 *
 * @param ptr a pointer to the memory area containing the bytes to be written
 * @param size the length of one element
 * @param nitems the element count
 * @param buffer the CxBuffer to write to
 * @return the total count of elements written
 * @see cxBufferWrite()
 * @see cxBufferRead()
 */
cx_attr_nonnull
cx_attr_export
size_t cxBufferAppend(
        const void *ptr,
        size_t size,
        size_t nitems,
        CxBuffer *buffer
);

/**
 * Performs a single flush-run on the specified buffer.
 *
 * Does nothing when the position in the buffer is zero.
 * Otherwise, the data until the current position minus
 * one is considered for flushing.
 * Note carefully that flushing will never exceed the
 * current @em position, even when the size of the
 * buffer is larger than the current position.
 *
 * One flush run will try to flush @c blkmax many
 * blocks of size @c blksize until either the @p buffer
 * has no more data to flush or the write function
 * used for flushing returns zero.
 *
 * The buffer is shifted left for that many bytes
 * the flush operation has successfully flushed.
 *
 * @par Example 1
 * Assume you have a buffer with size 340 and you are
 * at position 200. The flush configuration is
 * @c blkmax=4 and @c blksize=64 .
 * Assume that the entire flush operation is successful.
 * All 200 bytes on the left hand-side from the current
 * position are written.
 * That means, the size of the buffer is now 140 and the
 * position is zero.
 *
 * @par Example 2
 * Same as Example 1, but now the @c blkmax is 1.
 * The size of the buffer is now 276 and the position is 136.
 *
 * @par Example 3
 * Same as Example 1, but now assume the flush target
 * only accepts 100 bytes before returning zero.
 * That means, the flush operations manages to flush
 * one complete block and one partial block, ending
 * up with a buffer with size 240 and position 100.
 *
 * @remark Just returns zero when flushing was not enabled with
 * cxBufferEnableFlushing().
 *
 * @remark When the buffer uses copy-on-write, the memory
 * is copied first, before attempting any flush.
 * This is, however, considered an erroneous use of the
 * buffer, because it does not make much sense to put
 * readonly data into an UCX buffer for flushing, instead
 * of writing it directly to the target.
 *
 * @param buffer the buffer
 * @return the number of successfully flushed bytes
 * @see cxBufferEnableFlushing()
 */
cx_attr_nonnull
cx_attr_export
size_t cxBufferFlush(CxBuffer *buffer);

/**
 * Reads data from a CxBuffer.
 *
 * The position of the buffer is increased by the number of bytes read.
 *
 * @note The signature is compatible with the fread() family of functions.
 *
 * @param ptr a pointer to the memory area where to store the read data
 * @param size the length of one element
 * @param nitems the element count
 * @param buffer the CxBuffer to read from
 * @return the total number of elements read
 * @see cxBufferWrite()
 * @see cxBufferAppend()
 */
cx_attr_nonnull
cx_attr_export
size_t cxBufferRead(
        void *ptr,
        size_t size,
        size_t nitems,
        CxBuffer *buffer
);

/**
 * Writes a character to a buffer.
 *
 * The least significant byte of the argument is written to the buffer. If the
 * end of the buffer is reached and #CX_BUFFER_AUTO_EXTEND feature is enabled,
 * the buffer capacity is extended by cxBufferMinimumCapacity(). If the feature
 * is disabled or buffer extension fails, @c EOF is returned.
 *
 * On successful write, the position of the buffer is increased.
 *
 * If you just want to write a null-terminator at the current position, you
 * should use cxBufferTerminate() instead.
 *
 * @param buffer the buffer to write to
 * @param c the character to write
 * @return the byte that has been written or @c EOF when the end of the stream is
 * reached and automatic extension is not enabled or not possible
 * @see cxBufferTerminate()
 */
cx_attr_nonnull
cx_attr_export
int cxBufferPut(
        CxBuffer *buffer,
        int c
);

/**
 * Writes a terminating zero to a buffer at the current position.
 *
 * If successful, sets the size to the current position and advances the position by one.
 *
 * The purpose of this function is to have the written data ready to be used as
 * a C string with the buffer's size being the length of that string.
 *
 * @param buffer the buffer to write to
 * @return zero, if the terminator could be written, non-zero otherwise
 */
cx_attr_nonnull
cx_attr_export
int cxBufferTerminate(CxBuffer *buffer);

/**
 * Writes a string to a buffer.
 *
 * This is a convenience function for <code>cxBufferWrite(str, 1, strlen(str), buffer)</code>.
 *
 * @param buffer the buffer
 * @param str the zero-terminated string
 * @return the number of bytes written
 */
cx_attr_nonnull
cx_attr_cstr_arg(2)
cx_attr_export
size_t cxBufferPutString(
        CxBuffer *buffer,
        const char *str
);

/**
 * Gets a character from a buffer.
 *
 * The current position of the buffer is increased after a successful read.
 *
 * @param buffer the buffer to read from
 * @return the character or @c EOF, if the end of the buffer is reached
 */
cx_attr_nonnull
cx_attr_export
int cxBufferGet(CxBuffer *buffer);

#ifdef __cplusplus
}
#endif

#endif // UCX_BUFFER_H

mercurial