diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml new file mode 100644 index 0000000..8b6d3cc --- /dev/null +++ b/.github/workflows/c-cpp.yml @@ -0,0 +1,29 @@ +name: C/C++ CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Create build directory + run: mkdir -p build + + - name: Run CMake + working-directory: build + run: cmake .. -DC_BUFFER_TEST=ON + + - name: Build the project + working-directory: build + run: make + + - name: Run tests + working-directory: build + run: ./test_c_buffer diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..45654f4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.13) + +project(c_buffer C ASM) + +add_library(c_buffer INTERFACE) + +target_sources(c_buffer INTERFACE + src/c_buffer.c +) + +target_include_directories(c_buffer INTERFACE + src +) + +# Option to build standalone executable for testing +option(C_BUFFER_TEST "Build standalone executable for c_buffer" OFF) + +if(C_BUFFER_TEST) + set(CMAKE_C_COMPILER gcc) + + # Add standalone executable for testing c_buffer + add_executable(test_c_buffer test/test_c_buffer.c) + + # Link the c_buffer library to the standalone executable + target_link_libraries(test_c_buffer PRIVATE c_buffer) + + # Optionally, add any specific compiler options for testing + target_compile_options(test_c_buffer PRIVATE -Wall -Wextra -pedantic) +endif() \ No newline at end of file diff --git a/README.md b/README.md index e69de29..9821e06 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,8 @@ +# Module used to create a circular buffer for bytes +Use this module to store data in a circular buffer + +## Build this module standalone for testing +mkdir build +cd build +cmake .. -DC_BUFFER_TEST=ON +make \ No newline at end of file diff --git a/src/c_buffer.c b/src/c_buffer.c index b3eed73..655f27b 100644 --- a/src/c_buffer.c +++ b/src/c_buffer.c @@ -26,10 +26,13 @@ * SOFTWARE. */ #include "c_buffer.h" -#include "common.h" #include "string.h" +#include #define MODULO_INC(value, increment, modulus) (((value) + (increment)) % (modulus)) +#ifndef LOG +#define LOG(f_, ...) printf((f_), ##__VA_ARGS__) +#endif static inline size_t MODULO_DEC(size_t value, size_t decrement, size_t modulus) { @@ -106,7 +109,7 @@ int32_t cBufferPrepend(cBuffer_t *inst, uint8_t *data, size_t data_size) { // This cast is safe as the inst null check is allready done if ((size_t)cBufferAvailableForWrite(inst) < data_size) { - return C_BUFFER_INSUFFICENT; + return C_BUFFER_INSUFFICIENT; } // Look for the special case were the buffer is empty @@ -189,7 +192,7 @@ int32_t cBufferPrependByte(cBuffer_t *inst, uint8_t data) { // This cast is safe as the inst null check is allready done if ((size_t)cBufferAvailableForWrite(inst) < 1) { - return C_BUFFER_INSUFFICENT; + return C_BUFFER_INSUFFICIENT; } // Look for the special case were the buffer is empty @@ -225,7 +228,7 @@ int32_t cBufferAppend(cBuffer_t *inst, uint8_t *data, size_t data_size) { // This cast is safe as the inst null check is allready done if ((size_t)cBufferAvailableForWrite(inst) < data_size) { - return C_BUFFER_INSUFFICENT; + return C_BUFFER_INSUFFICIENT; } // Check if we need to do a wrap copy @@ -277,8 +280,12 @@ int32_t cBufferReadAll(cBuffer_t *inst, uint8_t *data, size_t max_read_size) { int32_t num_bytes_in_buffer = cBufferAvailableForRead(inst); - if (num_bytes_in_buffer > max_read_size) { - return C_BUFFER_INSUFFICENT; + if (num_bytes_in_buffer < C_BUFFER_SUCCESS) { + return num_bytes_in_buffer; + } + + if ((size_t)num_bytes_in_buffer > max_read_size) { + return C_BUFFER_INSUFFICIENT; } // Check if there is a wrap in buffer @@ -344,6 +351,79 @@ uint8_t cBufferReadByte(cBuffer_t *inst) { return data; } +int32_t cBufferReadBytes(cBuffer_t *inst, uint8_t *data, size_t read_size) { + if (inst == NULL || data == NULL) { + return C_BUFFER_NULL_ERROR; + } + + int32_t num_bytes_in_buffer = cBufferAvailableForRead(inst); + + if (num_bytes_in_buffer < C_BUFFER_SUCCESS) { + return num_bytes_in_buffer; + } + + if (read_size > (size_t)num_bytes_in_buffer) { + return C_BUFFER_MISMATCH; + } + + + // Check if there is a wrap in buffer + if (inst->head < inst->tail) { + size_t bytes_in_first = inst->size - inst->tail; + if (read_size <= bytes_in_first) { + // All requested data is in the first block. + // First read the data up to the wrap +#ifdef NO_MEMCPY + size_t data_ind = 0; + for (size_t ind = inst->tail; ind < inst->size; ind++) { + data[data_ind] = inst->data[ind]; + data_ind++; + } +#else + memcpy(data, inst->data + inst->tail, bytes_in_first); +#endif + } else { + // Data is divided before and after wrap +#ifdef NO_MEMCPY + size_t data_ind = 0; + for (size_t ind = inst->tail; ind < inst->size; ind++) { + data[data_ind] = inst->data[ind]; + data_ind++; + } +#else + memcpy(data, inst->data + inst->tail, bytes_in_first); +#endif + + // Then read the remaining data after the wrap +#ifdef NO_MEMCPY + for (size_t ind = 0; ind < read_size - bytes_in_first; ind++) { + data[data_ind] = inst->data[ind]; + data_ind++; + } +#else + memcpy(data + bytes_in_first, inst->data, read_size - bytes_in_first); +#endif + } + } else { + // No data wrap, just read the data into the buffer +#ifdef NO_MEMCPY + size_t buffer_ind = inst->tail; + for (size_t ind = 0; ind < read_size ; ind++) { + data[ind] = inst->data[buffer_ind]; + buffer_ind++; + } +#else + // Faster memcpy version + memcpy(data, inst->data + inst->tail, read_size); +#endif + } + + + inst->tail = MODULO_INC(inst->tail, read_size, inst->size); + + return read_size; +} + int32_t cBufferClear(cBuffer_t *inst) { if (inst == NULL) { return C_BUFFER_NULL_ERROR; diff --git a/src/c_buffer.h b/src/c_buffer.h index b54bb1e..1822c18 100644 --- a/src/c_buffer.h +++ b/src/c_buffer.h @@ -29,7 +29,8 @@ #ifndef C_BUFFER_H #define C_BUFFER_H -#include "pico/stdlib.h" +#include +#include // The available number of bytes in the C buffer will be one less than the size of the array #define C_BUFFER_ARRAY_OVERHEAD 1 @@ -40,9 +41,9 @@ typedef enum { C_BUFFER_SUCCESS, - C_BUFFER_NULL_ERROR = -301, - C_BUFFER_INSUFFICENT = -302, - C_BUFFER_MISSMATCH = -303, + C_BUFFER_NULL_ERROR = -301, + C_BUFFER_INSUFFICIENT = -302, + C_BUFFER_MISMATCH = -303, } cBufferErr_t; typedef struct { @@ -133,6 +134,16 @@ int32_t cBufferReadAll(cBuffer_t *inst, uint8_t *data, size_t max_read_size); */ uint8_t cBufferReadByte(cBuffer_t *inst); +/** + * Read the next byte from the buffer + * Note: Using this function on emtpy buffers will return 0 + * Input: Pointer to buffer instance + * Input: Pointer to data to read into + * Input: Number of bytes to read + * Returns: cBufferErr_t or num of bytes read + */ +int32_t cBufferReadBytes(cBuffer_t *inst, uint8_t *data, size_t read_size); + /** * Clear a buffer, this resets the head and tail to first element of buffer * Input: Pointer to buffer instance @@ -142,14 +153,14 @@ int32_t cBufferClear(cBuffer_t *inst); /** * Rotate the buffer to make sure the data is available in continous memory - * Input: Pointer to buffe instance + * Input: Pointer to buffer instance * Returns: cBufferErr_t */ int32_t cBufferContiguate(cBuffer_t* inst); /** * Get pointer to the current first element in the buffer - * Input: Pointer to buffe instance + * Input: Pointer to buffer instance * Returns: Pointer to element or null if invalid or wrap in buffer */ uint8_t *cBufferGetReadPointer(cBuffer_t* inst); @@ -157,7 +168,7 @@ uint8_t *cBufferGetReadPointer(cBuffer_t* inst); /** * Get pointer to the current next write element in the buffer * Note: There is no garantuee that the data is continous - * Input: Pointer to buffe instance + * Input: Pointer to buffer instance * Returns: Pointer to element */ uint8_t *cBufferGetWritePointer(cBuffer_t* inst); @@ -165,7 +176,7 @@ uint8_t *cBufferGetWritePointer(cBuffer_t* inst); /** * Increment the amount of data in the buffer without writing anything to the buffer * Note: There is no garantuee that the data is continous - * Input: Pointer to buffe instance + * Input: Pointer to buffer instance * Input: Number of bytes to append to tail * Returns: Pointer to element */ diff --git a/test/test_c_buffer.c b/test/test_c_buffer.c new file mode 100644 index 0000000..3ad2b8c --- /dev/null +++ b/test/test_c_buffer.c @@ -0,0 +1,163 @@ +#include +#include +#include +#include "c_buffer.h" + +#define MAIN_BUFFER_SIZE 16 +#define SMALL_BUFFER_SIZE 10 + +int main(void) { + int32_t ret; + int32_t available; + uint8_t out[MAIN_BUFFER_SIZE]; + cBuffer_t cb; + uint8_t buffer[MAIN_BUFFER_SIZE]; + + printf("=== Circular Buffer Test Suite ===\n"); + + /********* Test 1: Initialization and Append/ReadAll *********/ + ret = cBufferInit(&cb, buffer, MAIN_BUFFER_SIZE); + assert(ret == C_BUFFER_SUCCESS); + printf("Test 1: Initialization successful.\n"); + + const char *testStr = "Hello"; + size_t testStrLen = strlen(testStr); + + ret = cBufferAppend(&cb, (uint8_t*)testStr, testStrLen); + assert(ret == (int32_t)testStrLen); + available = cBufferAvailableForRead(&cb); + assert(available == (int32_t)testStrLen); + printf("Test 1: Appended \"%s\" (%zu bytes).\n", testStr, testStrLen); + + ret = cBufferReadAll(&cb, out, MAIN_BUFFER_SIZE); + assert(ret == (int32_t)testStrLen); + out[ret] = '\0'; + printf("Test 1: ReadAll returned \"%s\".\n", out); + assert(strcmp((char*)out, testStr) == 0); + + /********* Test 2: Prepend *********/ + ret = cBufferClear(&cb); + assert(ret == C_BUFFER_SUCCESS); + + const char *testStr2 = "World"; + size_t testStr2Len = strlen(testStr2); + ret = cBufferPrepend(&cb, (uint8_t*)testStr2, testStr2Len); + assert(ret == (int32_t)testStr2Len); + available = cBufferAvailableForRead(&cb); + assert(available == (int32_t)testStr2Len); + printf("Test 2: Prepend \"%s\" successful.\n", testStr2); + + ret = cBufferReadAll(&cb, out, MAIN_BUFFER_SIZE); + assert(ret == (int32_t)testStr2Len); + out[ret] = '\0'; + printf("Test 2: After Prepend, ReadAll returned \"%s\".\n", out); + assert(strcmp((char*)out, testStr2) == 0); + + /********* Test 3: Wrap-Around Append and ReadAll *********/ + // Use a smaller buffer to force wrap-around. + cBuffer_t cb_small; + uint8_t smallBuffer[SMALL_BUFFER_SIZE]; + ret = cBufferInit(&cb_small, smallBuffer, SMALL_BUFFER_SIZE); + assert(ret == C_BUFFER_SUCCESS); + printf("Test 3: Initialized small buffer (%d bytes).\n", SMALL_BUFFER_SIZE); + + const char *wrapStr1 = "ABCDE"; // 5 bytes + ret = cBufferAppend(&cb_small, (uint8_t*)wrapStr1, strlen(wrapStr1)); + assert(ret == (int32_t)strlen(wrapStr1)); + + // Read 2 bytes to move the tail forward. + uint8_t byte; + byte = cBufferReadByte(&cb_small); + assert(byte == 'A'); + byte = cBufferReadByte(&cb_small); + assert(byte == 'B'); + + // Now the available bytes are 3 ("CDE") + const char *wrapStr2 = "XYZ"; // 3 bytes; this should wrap to the beginning. + ret = cBufferAppend(&cb_small, (uint8_t*)wrapStr2, strlen(wrapStr2)); + assert(ret == (int32_t)strlen(wrapStr2)); + + available = cBufferAvailableForRead(&cb_small); + // Expected available = remaining from first append (3) + newly appended (3) = 6. + assert(available == 6); + printf("Test 3: After wrap-around append, available bytes: %d.\n", available); + + uint8_t smallOut[SMALL_BUFFER_SIZE]; + ret = cBufferReadAll(&cb_small, smallOut, SMALL_BUFFER_SIZE); + assert(ret == 6); + smallOut[ret] = '\0'; + printf("Test 3: ReadAll returned \"%s\" (expected \"CDEXYZ\").\n", smallOut); + assert(strcmp((char*)smallOut, "CDEXYZ") == 0); + + /********* Test 4: ReadBytes (Partial Read) *********/ + ret = cBufferClear(&cb); + const char *pattern = "123456789"; // 9 bytes + ret = cBufferAppend(&cb, (uint8_t*)pattern, strlen(pattern)); + assert(ret == (int32_t)strlen(pattern)); + printf("Test 4: Appended pattern \"%s\".\n", pattern); + + // Read first 4 bytes using ReadBytes. + uint8_t subset[10]; + ret = cBufferReadBytes(&cb, subset, 4); + assert(ret == 4); + subset[4] = '\0'; + printf("Test 4: ReadBytes (4 bytes) returned \"%s\".\n", subset); + assert(strcmp((char*)subset, "1234") == 0); + + available = cBufferAvailableForRead(&cb); + assert(available == 5); // 9 - 4 = 5 remaining. + ret = cBufferReadAll(&cb, out, MAIN_BUFFER_SIZE); + assert(ret == 5); + out[ret] = '\0'; + printf("Test 4: After ReadBytes, ReadAll returned \"%s\" (expected \"56789\").\n", out); + assert(strcmp((char*)out, "56789") == 0); + + /********* Test 5: Contiguate *********/ + { + cBuffer_t cb_small; + uint8_t smallBuffer[SMALL_BUFFER_SIZE]; + ret = cBufferInit(&cb_small, smallBuffer, SMALL_BUFFER_SIZE); + assert(ret == C_BUFFER_SUCCESS); + printf("Test 5: Initialized small buffer for contiguate test.\n"); + + // Append 7 bytes so that we fill the buffer partially. + const char *data1 = "ABCDEFG"; // 7 bytes. + ret = cBufferAppend(&cb_small, (uint8_t*)data1, strlen(data1)); + assert(ret == (int32_t)strlen(data1)); + + // Read 4 bytes to move the tail forward. + for (int i = 0; i < 4; i++) { + byte = cBufferReadByte(&cb_small); + } + // Now available should be 3 bytes. + // Append 3 more bytes to force a wrap-around. + const char *data2 = "XYZ"; + ret = cBufferAppend(&cb_small, (uint8_t*)data2, strlen(data2)); + assert(ret == (int32_t)strlen(data2)); + + available = cBufferAvailableForRead(&cb_small); + printf("Test 5: Before contiguate, available bytes: %d.\n", available); + + ret = cBufferContiguate(&cb_small); + assert(ret == C_BUFFER_SUCCESS); + uint8_t *read_ptr = cBufferGetReadPointer(&cb_small); + assert(read_ptr != NULL); + printf("Test 5: After contiguate, data is: "); + for (size_t i = 0; i < (size_t)available; i++) { + printf("%c", read_ptr[i]); + } + printf(" (expected \"EFGXYZ\")\n"); + + /* Optionally, read the contiguous data into an output buffer and compare. + The expected result here is a concatenation of the unread data from the + original data ("ABCDEFG") after reading 4 bytes ("ABCD" read), which leaves + "EFG", then appended "XYZ" => "EFGXYZ". */ + ret = cBufferReadAll(&cb_small, smallOut, SMALL_BUFFER_SIZE); + smallOut[ret] = '\0'; + assert(strcmp((char*)smallOut, "EFGXYZ") == 0); + printf("Test 5: ReadAll after contiguate returned \"%s\".\n", smallOut); + } + + printf("=== All tests passed! ===\n"); + return 0; +} \ No newline at end of file