/**
* \file lwrb.c
* \brief Lightweight ring buffer
*/
/*
* Copyright (c) 2024 Tilen MAJERLE
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
* AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* This file is part of LwRB - Lightweight ring buffer library.
*
* Author: Tilen MAJERLE <tilen@majerle.eu>
* Version: v3.1.0
*/
#include "lwrb.h"
/* Memory set and copy functions */
#define BUF_MEMSET memset
#define BUF_MEMCPY memcpy
#define BUF_IS_VALID(b) ((b) != NULL && (b)->buff != NULL && (b)->size > 0)
#define BUF_MIN(x, y) ((x) < (y) ? (x) : (y))
#define BUF_MAX(x, y) ((x) > (y) ? (x) : (y))
#define BUF_SEND_EVT(b, type, bp) \
do { \
if ((b)->evt_fn != NULL) { \
(b)->evt_fn((void*)(b), (type), (bp)); \
} \
} while (0)
/* Optional atomic opeartions */
#ifdef LWRB_DISABLE_ATOMIC
#define LWRB_INIT(var, val) (var) = (val)
#define LWRB_LOAD(var, type) (var)
#define LWRB_STORE(var, val, type) (var) = (val)
#else
#define LWRB_INIT(var, val) atomic_init(&(var), (val))
#define LWRB_LOAD(var, type) atomic_load_explicit(&(var), (type))
#define LWRB_STORE(var, val, type) atomic_store_explicit(&(var), (val), (type))
#endif
/**
* \brief Initialize buffer handle to default values with size and buffer data array
* \param[in] buff: Ring buffer instance
* \param[in] buffdata: Pointer to memory to use as buffer data
* \param[in] size: Size of `buffdata` in units of bytes
* Maximum number of bytes buffer can hold is `size - 1`
* \return `1` on success, `0` otherwise
*/
uint8_t
lwrb_init(lwrb_t* buff, void* buffdata, lwrb_sz_t size) {
if (buff == NULL || buffdata == NULL || size == 0) {
return 0;
}
buff->evt_fn = NULL;
buff->size = size;
buff->buff = buffdata;
LWRB_INIT(buff->w, 0);
LWRB_INIT(buff->r, 0);
return 1;
}
/**
* \brief Check if buff is initialized and ready to use
* \param[in] buff: Ring buffer instance
* \return `1` if ready, `0` otherwise
*/
uint8_t
lwrb_is_ready(lwrb_t* buff) {
return BUF_IS_VALID(buff);
}
/**
* \brief Free buffer memory
* \note Since implementation does not use dynamic allocation,
* it just sets buffer handle to `NULL`
* \param[in] buff: Ring buffer instance
*/
void
lwrb_free(lwrb_t* buff) {
if (BUF_IS_VALID(buff)) {
buff->buff = NULL;
}
}
/**
* \brief Set event function callback for different buffer operations
* \param[in] buff: Ring buffer instance
* \param[in] evt_fn: Callback function
*/
void
lwrb_set_evt_fn(lwrb_t* buff, lwrb_evt_fn evt_fn) {
if (BUF_IS_VALID(buff)) {
buff->evt_fn = evt_fn;
}
}
/**
* \brief Write data to buffer.
* Copies data from `data` array to buffer and marks buffer as full for maximum `btw` number of bytes
*
* \param[in] buff: Ring buffer instance
* \param[in] data: Pointer to data to write into buffer
* \param[in] btw: Number of bytes to write
* \return Number of bytes written to buffer.
* When returned value is less than `btw`, there was no enough memory available
* to copy full data array.
*/
lwrb_sz_t
lwrb_write(lwrb_t* buff, const void* data, lwrb_sz_t btw) {
lwrb_sz_t written = 0;
if (lwrb_write_ex(buff, data, btw, &written, 0)) {
return written;
}
return 0;
}
/**
* \brief Write extended functionality
*
* \param buff: Ring buffer instance
* \param data: Pointer to data to write into buffer
* \param btw: Number of bytes to write
* \param bw: Output pointer to write number of bytes written
* \param flags: Optional flags.
* \ref LWRB_FLAG_WRITE_ALL: Request to write all data (up to btw).
* Will early return if no memory available
* \return `1` if write operation OK, `0` otherwise
*/
uint8_t
lwrb_write_ex(lwrb_t* buff, const void* data, lwrb_sz_t btw, lwrb_sz_t* bw, uint16_t flags) {
lwrb_sz_t tocopy, free, buff_w_ptr;
const uint8_t* d = data;
if (!BUF_IS_VALID(buff) || data == NULL || btw == 0) {
return 0;
}
/* Calculate maximum number of bytes available to write */
free = lwrb_get_free(buff);
/* If no memory, or if user wants to write ALL data but no enough space, exit early */
if (free == 0 || (free < btw && flags & LWRB_FLAG_WRITE_ALL)) {
return 0;
}
btw = BUF_MIN(free, btw);
buff_w_ptr = LWRB_LOAD(buff->w, memory_order_acquire);
/* Step 1: Write data to linear part of buffer */
tocopy = BUF_MIN(buff->size - buff_w_ptr, btw);
BUF_MEMCPY(&buff->buff[buff_w_ptr], d, tocopy);
buff_w_ptr += tocopy;
btw -= tocopy;
/* Step 2: Write data to beginning of buffer (overflow part) */
if (btw > 0) {
BUF_MEMCPY(buff->buff, &d[tocopy], btw);
buff_w_ptr = btw;
}
/* Step 3: Check end of buffer */
if (buff_w_ptr >= buff->size) {
buff_w_ptr = 0;
}
/*
* Write final value to the actual running variable.
* This is to ensure no read operation can access intermediate data
*/
LWRB_STORE(buff->w, buff_w_ptr, memory_order_release);
BUF_SEND_EVT(buff, LWRB_EVT_WRITE, tocopy + btw);
if (bw != NULL) {
*bw = tocopy + btw;
}
return 1;
}
/**
* \brief Read data from buffer.
* Copies data from buffer to `data` array and marks buffer as free for maximum `btr` number of bytes
*
* \param[in] buff: Ring buffer instance
* \param[out] data: Pointer to output memory to copy buffer data to
* \param[in] btr: Number of bytes to read
* \return Number of bytes read and copied to data array
*/
lwrb_sz_t
lwrb_read(lwrb_t* buff, void* data, lwrb_sz_t btr) {
lwrb_sz_t read = 0;
if (lwrb_read_ex(buff, data, btr, &read, 0)) {
return read;
}
return 0;
}
/**
* \brief Write extended functionality
*
* \param buff: Ring buffer instance
* \param data: Pointer to memory to write read data from buffer
* \param btr: Number of bytes to read
* \param br: Output pointer to write number of bytes read
* \param flags: Optional flags
* \ref LWRB_FLAG_READ_ALL: Request to read all data (up to btr).
*