ByteRingBuffer 接口完善
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
#include <c_ByteRingBuffer.h>
|
||||
#include <c_Memory.h>
|
||||
#include <c_Alignment.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
c_err_t c_ByteRingBuffer_Init(c_ByteRingBuffer_t* self, c_size_t capacity) {
|
||||
if (!self || capacity == 0) return C_ERR_PARAM;
|
||||
@@ -107,3 +111,433 @@ c_bool_t c_ByteRingBuffer_IsFull(const c_ByteRingBuffer_t* self) {
|
||||
if (!self) return C_FALSE;
|
||||
return self->is_full;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
void c_ByteRingBuffer_WriteByteOverwrite(c_ByteRingBuffer_t* self, uint8_t byte) {
|
||||
if (!self || !self->buffer) return;
|
||||
|
||||
if (self->is_full) {
|
||||
// Advance head position to discard the oldest item before placing the new data
|
||||
self->head = (self->head + 1) % self->capacity;
|
||||
}
|
||||
|
||||
self->buffer[self->tail] = byte;
|
||||
self->tail = (self->tail + 1) % self->capacity;
|
||||
|
||||
if (self->tail == self->head) {
|
||||
self->is_full = C_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
c_size_t c_ByteRingBuffer_WriteBufferOverwrite(c_ByteRingBuffer_t* self, const uint8_t* src, c_size_t len) {
|
||||
if (!self || !self->buffer || !src || len == 0) return 0;
|
||||
|
||||
// Boundary edge-case optimization: If incoming payload exceeds entire capacity,
|
||||
// only the absolute last subset window matching 'capacity' will survive anyway.
|
||||
if (len >= self->capacity) {
|
||||
src += (len - self->capacity);
|
||||
len = self->capacity;
|
||||
|
||||
// Blindly overwrite entire buffer matrix instantly
|
||||
memcpy(self->buffer, src, len);
|
||||
self->head = 0;
|
||||
self->tail = 0;
|
||||
self->is_full = C_TRUE;
|
||||
return len;
|
||||
}
|
||||
|
||||
c_size_t size = c_ByteRingBuffer_GetSize(self);
|
||||
c_size_t free_space = self->capacity - size;
|
||||
|
||||
// Track if writing this string length overflows existing configurations
|
||||
if (len > free_space) {
|
||||
c_size_t overwrite_count = len - free_space;
|
||||
self->head = (self->head + overwrite_count) % self->capacity;
|
||||
}
|
||||
|
||||
// Segment 1: Physical top copy action
|
||||
c_size_t space_to_end = self->capacity - self->tail;
|
||||
c_size_t first_chunk = (len < space_to_end) ? len : space_to_end;
|
||||
memcpy(self->buffer + self->tail, src, first_chunk);
|
||||
|
||||
// Segment 2: Physical circular bottom split copy action
|
||||
c_size_t second_chunk = len - first_chunk;
|
||||
if (second_chunk > 0) {
|
||||
memcpy(self->buffer, src + first_chunk, second_chunk);
|
||||
self->tail = second_chunk;
|
||||
} else {
|
||||
self->tail = (self->tail + first_chunk) % self->capacity;
|
||||
}
|
||||
|
||||
if (self->tail == self->head) {
|
||||
self->is_full = C_TRUE;
|
||||
} else {
|
||||
self->is_full = C_FALSE; // Only set false if it didn't completely fill out layout thresholds
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
c_err_t c_ByteRingBuffer_PeekByte(const c_ByteRingBuffer_t* self, uint8_t* out_byte) {
|
||||
if (!self || !self->buffer || !out_byte) return C_ERR_INVALID_PARAM;
|
||||
if (c_ByteRingBuffer_IsEmpty(self)) return -3; // Underflow
|
||||
|
||||
*out_byte = self->buffer[self->head];
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
c_size_t c_ByteRingBuffer_PeekBuffer(const c_ByteRingBuffer_t* self, uint8_t* dest, c_size_t len) {
|
||||
if (!self || !self->buffer || !dest || len == 0) return 0;
|
||||
|
||||
c_size_t available_bytes = c_ByteRingBuffer_GetSize(self);
|
||||
if (len > available_bytes) {
|
||||
len = available_bytes;
|
||||
}
|
||||
|
||||
if (len == 0) return 0;
|
||||
|
||||
// Duplicate standard ReadBuffer chunk copy patterns without changing the 'head' cursor state
|
||||
c_size_t bytes_to_end = self->capacity - self->head;
|
||||
c_size_t first_chunk = (len < bytes_to_end) ? len : bytes_to_end;
|
||||
memcpy(dest, self->buffer + self->head, first_chunk);
|
||||
|
||||
c_size_t second_chunk = len - first_chunk;
|
||||
if (second_chunk > 0) {
|
||||
memcpy(dest + first_chunk, self->buffer, second_chunk);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
c_size_t c_ByteRingBuffer_Discard(c_ByteRingBuffer_t* self, c_size_t len) {
|
||||
if (!self || !self->buffer || len == 0) return 0;
|
||||
|
||||
c_size_t available_bytes = c_ByteRingBuffer_GetSize(self);
|
||||
if (len > available_bytes) {
|
||||
len = available_bytes;
|
||||
}
|
||||
|
||||
if (len == 0) return 0;
|
||||
|
||||
self->head = (self->head + len) % self->capacity;
|
||||
self->is_full = C_FALSE; // Dropping bytes guarantees it is no longer full
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
const uint8_t* c_ByteRingBuffer_GetReadPtr(const c_ByteRingBuffer_t* self, c_size_t* out_contiguous_len) {
|
||||
if (!self || !self->buffer || !out_contiguous_len) return NULL;
|
||||
|
||||
*out_contiguous_len = 0;
|
||||
if (c_ByteRingBuffer_IsEmpty(self)) return NULL;
|
||||
|
||||
if (self->tail > self->head) {
|
||||
// Data path is completely linear up to the tail index position
|
||||
*out_contiguous_len = self->tail - self->head;
|
||||
} else {
|
||||
// Data path wraps around; the first contiguous stretch runs to the array tail boundary
|
||||
*out_contiguous_len = self->capacity - self->head;
|
||||
}
|
||||
|
||||
return self->buffer + self->head;
|
||||
}
|
||||
|
||||
uint8_t* c_ByteRingBuffer_GetWritePtr(const c_ByteRingBuffer_t* self, c_size_t* out_contiguous_len) {
|
||||
if (!self || !self->buffer || !out_contiguous_len) return NULL;
|
||||
|
||||
*out_contiguous_len = 0;
|
||||
if (self->is_full) return NULL;
|
||||
|
||||
if (self->tail >= self->head) {
|
||||
// Free space path runs from tail up to the absolute array wrap boundary
|
||||
// Special case adjustment: If head is exactly at index 0, we can write up to capacity - 1
|
||||
// but since is_full flag tracking isolates capacity limits, write blocks up to standard edge boundaries.
|
||||
*out_contiguous_len = self->capacity - self->tail;
|
||||
|
||||
// Minor modification check: If head index configuration is further up but tail wraps,
|
||||
// don't overlap onto the head index area until the next subsequent hardware layout fetch pass.
|
||||
if (self->head == 0 && *out_contiguous_len == self->capacity) {
|
||||
// Full allocation window available
|
||||
}
|
||||
} else {
|
||||
// Free space path is bounded cleanly between tail position and head position index spaces
|
||||
*out_contiguous_len = self->head - self->tail;
|
||||
}
|
||||
|
||||
return self->buffer + self->tail;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
uint8_t c_ByteRingBuffer_GetAtRelativeInternal(const c_ByteRingBuffer_t* self, c_size_t relative_offset) {
|
||||
c_size_t absolute_index = (self->head + relative_offset) % self->capacity;
|
||||
return self->buffer[absolute_index];
|
||||
}
|
||||
|
||||
c_index_t c_ByteRingBuffer_IndexOfBuffer(const c_ByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) {
|
||||
if (!self || !self->buffer || !pattern || pattern_len == 0) return C_ERR_NOT_FOUND;
|
||||
|
||||
c_size_t total_size = c_ByteRingBuffer_GetSize(self);
|
||||
if (pattern_len > total_size) return C_ERR_NOT_FOUND;
|
||||
|
||||
// Slide search window across the total available size boundary
|
||||
c_size_t max_search_offset = total_size - pattern_len;
|
||||
|
||||
for (c_size_t offset = 0; offset <= max_search_offset; offset++) {
|
||||
c_bool_t match_found = C_TRUE;
|
||||
|
||||
// Perform relative window byte sequence comparison
|
||||
for (c_size_t p_idx = 0; p_idx < pattern_len; p_idx++) {
|
||||
if (c_ByteRingBuffer_GetAtRelativeInternal(self, offset + p_idx) != pattern[p_idx]) {
|
||||
match_found = C_FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match_found) {
|
||||
return (c_index_t)offset; // Returns offset relative to current head position
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
c_index_t c_ByteRingBuffer_IndexOfByte(const c_ByteRingBuffer_t* self, uint8_t target) {
|
||||
if (!self || !self->buffer) return C_ERR_NOT_FOUND;
|
||||
|
||||
c_size_t total_size = c_ByteRingBuffer_GetSize(self);
|
||||
if (total_size == 0) return C_ERR_NOT_FOUND;
|
||||
|
||||
// Linear pass mapping relative pointers natively across internal split layers
|
||||
for (c_size_t offset = 0; offset < total_size; offset++) {
|
||||
if (c_ByteRingBuffer_GetAtRelativeInternal(self, offset) == target) {
|
||||
return (c_index_t)offset;
|
||||
}
|
||||
}
|
||||
|
||||
return C_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
c_index_t c_ByteRingBuffer_LastIndexOfBuffer(const c_ByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) {
|
||||
if (!self || !self->buffer || !pattern || pattern_len == 0) return C_ERR_NOT_FOUND;
|
||||
|
||||
c_size_t total_size = c_ByteRingBuffer_GetSize(self);
|
||||
if (pattern_len > total_size) return C_ERR_NOT_FOUND;
|
||||
|
||||
// Scan backwards from the largest possible relative offset down to index 0
|
||||
c_size_t max_search_offset = total_size - pattern_len;
|
||||
|
||||
for (c_size_t offset = max_search_offset; ; offset--) {
|
||||
c_bool_t match_found = C_TRUE;
|
||||
|
||||
for (c_size_t p_idx = 0; p_idx < pattern_len; p_idx++) {
|
||||
if (c_ByteRingBuffer_GetAtRelativeInternal(self, offset + p_idx) != pattern[p_idx]) {
|
||||
match_found = C_FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match_found) {
|
||||
return (c_index_t)offset;
|
||||
}
|
||||
|
||||
if (offset == 0) break; // Secure unsigned loop breakout guard rail
|
||||
}
|
||||
|
||||
return C_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
c_size_t c_ByteRingBuffer_ReadUntilToken(c_ByteRingBuffer_t* self, const uint8_t* token, c_size_t token_len, uint8_t* dest, c_size_t dest_max_len) {
|
||||
if (!self || !self->buffer || !token || token_len == 0 || !dest || dest_max_len == 0) return 0;
|
||||
|
||||
// Step 1: Scan for the token sequence location relative to the head
|
||||
c_index_t match_offset = c_ByteRingBuffer_IndexOfBuffer(self, token, token_len);
|
||||
if (match_offset == C_ERR_NOT_FOUND) {
|
||||
return 0; // Token sequence not currently hosted inside the window pipeline
|
||||
}
|
||||
|
||||
// Step 2: Compute total structural extraction requirements including the token depth
|
||||
c_size_t aggregate_bytes = (c_size_t)match_offset + token_len;
|
||||
|
||||
// Absolute safety check: Safeguard against destination memory container overflows
|
||||
if (aggregate_bytes > dest_max_len) {
|
||||
return 0; // Reject processing since target container is too small to safely store the complete payload string
|
||||
}
|
||||
|
||||
// Step 3: Perform standard destructive consumption via ReadBuffer
|
||||
c_size_t bytes_extracted = c_ByteRingBuffer_ReadBuffer(self, dest, aggregate_bytes);
|
||||
return bytes_extracted;
|
||||
}
|
||||
|
||||
c_err_t c_ByteRingBuffer_GetAtRelative(const c_ByteRingBuffer_t* self, c_size_t relative_offset, uint8_t* out_byte) {
|
||||
if (!self || !self->buffer || !out_byte) return C_ERR_PARAM;
|
||||
|
||||
// Validate that the request falls inside the populated byte array span
|
||||
c_size_t active_size = c_ByteRingBuffer_GetSize(self);
|
||||
if (relative_offset >= active_size) {
|
||||
return C_ERR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
// Safely fold the relative offset over physical ring buffer wrapping thresholds
|
||||
c_size_t absolute_index = (self->head + relative_offset) % self->capacity;
|
||||
|
||||
*out_byte = self->buffer[absolute_index];
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
c_bool_t c_ByteRingBuffer_Is(const c_ByteRingBuffer_t* self, c_index_t offset, uint8_t value) {
|
||||
// Return false immediately if the buffer is uninitialized or the offset is negative
|
||||
if (!self || !self->buffer || offset < 0) {
|
||||
return C_FALSE;
|
||||
}
|
||||
|
||||
// Verify that the requested relative offset falls within the currently readable window
|
||||
c_size_t active_size = c_ByteRingBuffer_GetSize(self);
|
||||
if ((c_size_t)offset >= active_size) {
|
||||
return C_FALSE;
|
||||
}
|
||||
|
||||
// Map the relative offset to the physical, wrapped array index bounds
|
||||
c_size_t absolute_index = (self->head + (c_size_t)offset) % self->capacity;
|
||||
|
||||
// Evaluate content identity match condition
|
||||
return (self->buffer[absolute_index] == value) ? C_TRUE : C_FALSE;
|
||||
}
|
||||
|
||||
|
||||
int c_ByteRingBuffer_Memcmp(const c_ByteRingBuffer_t* self, c_size_t offset, const uint8_t* buffer, c_size_t len) {
|
||||
if (!self || !self->buffer || !buffer) {
|
||||
return C_ERR_INVALID_PARAM;
|
||||
}
|
||||
if (len == 0) {
|
||||
return 0; // Empty comparison matches instantly
|
||||
}
|
||||
|
||||
c_size_t active_size = c_ByteRingBuffer_GetSize(self);
|
||||
// Boundary check: Verify the comparison window falls entirely within readable buffer constraints
|
||||
if (offset >= active_size || (offset + len) > active_size) {
|
||||
return C_ERR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
// Resolve the real physical starting index inside the memory array
|
||||
c_size_t absolute_start = (self->head + offset) % self->capacity;
|
||||
|
||||
// Calculate how many contiguous bytes run in a straight line up to the array boundary edge
|
||||
c_size_t bytes_to_end = self->capacity - absolute_start;
|
||||
|
||||
if (len <= bytes_to_end) {
|
||||
// Scenario 1: The target verification window is completely contiguous
|
||||
return memcmp(self->buffer + absolute_start, buffer, len);
|
||||
} else {
|
||||
// Scenario 2: The window wraps around the physical edge bounds. Execute a split-block comparison.
|
||||
|
||||
// Pass A: Compare up to the wrapping array edge boundary
|
||||
int first_segment_match = memcmp(self->buffer + absolute_start, buffer, bytes_to_end);
|
||||
if (first_segment_match != 0) {
|
||||
return first_segment_match; // Return mismatch direction immediately
|
||||
}
|
||||
|
||||
// Pass B: Wrap around to index 0 and compare the remainder sequence
|
||||
c_size_t remaining_bytes = len - bytes_to_end;
|
||||
return memcmp(self->buffer, buffer + bytes_to_end, remaining_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
c_err_t c_ByteRingBuffer_Strtoul(const c_ByteRingBuffer_t* self, c_size_t offset, int base, unsigned long* out_value, c_size_t* out_end_offset) {
|
||||
if (!self || !self->buffer || !out_value) return C_ERR_INVALID_PARAM;
|
||||
|
||||
c_size_t total_size = c_ByteRingBuffer_GetSize(self);
|
||||
if (offset >= total_size) return C_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
// 1. Skip leading whitespace using the internal helper function
|
||||
c_size_t scan_idx = offset;
|
||||
while (scan_idx < total_size) {
|
||||
uint8_t byte = c_ByteRingBuffer_GetAtRelativeInternal(self, scan_idx);
|
||||
if (!isspace(byte)) break;
|
||||
scan_idx++;
|
||||
}
|
||||
|
||||
if (scan_idx == total_size) return C_ERR_INVALID_PARAM; // Buffer contains only whitespace
|
||||
|
||||
// 2. Measure the exact alphanumeric chunk layout width boundary
|
||||
c_size_t start_numeric_offset = scan_idx;
|
||||
c_size_t numeric_len = 0;
|
||||
while (scan_idx < total_size) {
|
||||
uint8_t byte = c_ByteRingBuffer_GetAtRelativeInternal(self, scan_idx);
|
||||
// Track hex modifiers (x, X), signs, and alphanumeric digits
|
||||
if (!isalnum(byte) && byte != '+' && byte != '-') {
|
||||
break;
|
||||
}
|
||||
numeric_len++;
|
||||
scan_idx++;
|
||||
}
|
||||
|
||||
if (numeric_len == 0) return C_ERR_INVALID_PARAM;
|
||||
|
||||
// 3. Compute absolute pointers and optimize memory operations based on wrap layouts
|
||||
c_size_t absolute_start = (self->head + start_numeric_offset) % self->capacity;
|
||||
c_size_t bytes_to_end = self->capacity - absolute_start;
|
||||
|
||||
unsigned long result = 0;
|
||||
char* parse_end = NULL;
|
||||
int current_errno = errno;
|
||||
errno = 0;
|
||||
|
||||
if (numeric_len <= bytes_to_end) {
|
||||
// Linear path optimization: Parse directly out of the contiguous array space
|
||||
const char* flat_ptr = (const char*)(self->buffer + absolute_start);
|
||||
result = strtoul(flat_ptr, &parse_end, base);
|
||||
|
||||
c_size_t parsed_bytes = (c_size_t)(parse_end - flat_ptr);
|
||||
if (parsed_bytes == 0 || parse_end == flat_ptr) {
|
||||
errno = current_errno;
|
||||
return C_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (errno == ERANGE) return C_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
*out_value = result;
|
||||
if (out_end_offset) {
|
||||
*out_end_offset = start_numeric_offset + parsed_bytes;
|
||||
}
|
||||
} else {
|
||||
// Fragmented Wrap handling path: Copy across loop slices onto a small stack array
|
||||
if (numeric_len >= 64) return C_ERR_OUT_OF_BOUNDS; // Enforce safe parsing limits
|
||||
|
||||
char stack_scratch[64];
|
||||
for (c_size_t i = 0; i < numeric_len; i++) {
|
||||
stack_scratch[i] = (char)c_ByteRingBuffer_GetAtRelativeInternal(self, start_numeric_offset + i);
|
||||
}
|
||||
stack_scratch[numeric_len] = '\0'; // Guarantee safe string boundary termination
|
||||
|
||||
result = strtoul(stack_scratch, &parse_end, base);
|
||||
c_size_t parsed_bytes = (c_size_t)(parse_end - stack_scratch);
|
||||
|
||||
if (parsed_bytes == 0 || parse_end == stack_scratch) {
|
||||
errno = current_errno;
|
||||
return C_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (errno == ERANGE) return C_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
*out_value = result;
|
||||
if (out_end_offset) {
|
||||
*out_end_offset = start_numeric_offset + parsed_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
errno = current_errno; // Restore system state integrity flags cleanly
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -30,5 +30,111 @@ c_size_t c_ByteRingBuffer_GetSize(const c_ByteRingBuffer_t* self);
|
||||
c_bool_t c_ByteRingBuffer_IsEmpty(const c_ByteRingBuffer_t* self);
|
||||
c_bool_t c_ByteRingBuffer_IsFull(const c_ByteRingBuffer_t* self);
|
||||
|
||||
/*
|
||||
* Writes a single byte, overwriting the oldest byte if the buffer is full.
|
||||
*/
|
||||
void c_ByteRingBuffer_WriteByteOverwrite(c_ByteRingBuffer_t* self, uint8_t byte);
|
||||
|
||||
/*
|
||||
* Writes a buffer span, overwriting the oldest bytes continuously if capacity is exceeded.
|
||||
*/
|
||||
c_size_t c_ByteRingBuffer_WriteBufferOverwrite(c_ByteRingBuffer_t* self, const uint8_t* src, c_size_t len);
|
||||
|
||||
/*
|
||||
* Inspects a single byte at the head position without removing it.
|
||||
*/
|
||||
c_err_t c_ByteRingBuffer_PeekByte(const c_ByteRingBuffer_t* self, uint8_t* out_byte);
|
||||
|
||||
/*
|
||||
* Inspects up to 'len' bytes starting from the head position without removing them.
|
||||
* Returns the actual number of bytes peeked.
|
||||
*/
|
||||
c_size_t c_ByteRingBuffer_PeekBuffer(const c_ByteRingBuffer_t* self, uint8_t* dest, c_size_t len);
|
||||
|
||||
|
||||
/*
|
||||
* Advances the head pointer to drop up to 'len' bytes without copying data.
|
||||
* Returns the actual number of bytes dropped.
|
||||
*/
|
||||
c_size_t c_ByteRingBuffer_Discard(c_ByteRingBuffer_t* self, c_size_t len);
|
||||
|
||||
/*
|
||||
* Returns the direct linear address to read the first available contiguous memory block.
|
||||
* @param out_contiguous_len: Populated with the byte depth length of the straight chunk line.
|
||||
* @return Pointer into the structural array channel, or NULL if buffer is empty.
|
||||
*/
|
||||
const uint8_t* c_ByteRingBuffer_GetReadPtr(const c_ByteRingBuffer_t* self, c_size_t* out_contiguous_len);
|
||||
|
||||
/*
|
||||
* Returns the direct linear address to write into the first available contiguous free memory block.
|
||||
* @param out_contiguous_len: Populated with the space depth length of the straight chunk line.
|
||||
* @return Pointer into the structural array channel, or NULL if buffer is full.
|
||||
*/
|
||||
uint8_t* c_ByteRingBuffer_GetWritePtr(const c_ByteRingBuffer_t* self, c_size_t* out_contiguous_len);
|
||||
|
||||
/*
|
||||
* Searches for the first occurrence of a byte sequence (pattern) within the ring buffer.
|
||||
* Returns the relative offset from the current head pointer (0 to size-1), or C_ERR_NOT_FOUND.
|
||||
*/
|
||||
c_index_t c_ByteRingBuffer_IndexOfBuffer(const c_ByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len);
|
||||
|
||||
/*
|
||||
* Searches for the first occurrence of a single byte within the ring buffer.
|
||||
* Returns the relative offset from the current head pointer (0 to size-1), or C_ERR_NOT_FOUND.
|
||||
*/
|
||||
c_index_t c_ByteRingBuffer_IndexOfByte(const c_ByteRingBuffer_t* self, uint8_t target);
|
||||
|
||||
/*
|
||||
* Searches for the last occurrence of a byte sequence within the ring buffer.
|
||||
* Returns the relative offset from the current head pointer (0 to size-1), or C_ERR_NOT_FOUND.
|
||||
*/
|
||||
c_index_t c_ByteRingBuffer_LastIndexOfBuffer(const c_ByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len);
|
||||
|
||||
/*
|
||||
* Consumes and extracts data into 'dest' up to and including the specified token sequence.
|
||||
* Returns the total number of bytes read and placed into dest, or 0 if token is not found.
|
||||
*/
|
||||
c_size_t c_ByteRingBuffer_ReadUntilToken(c_ByteRingBuffer_t* self, const uint8_t* token, c_size_t token_len, uint8_t* dest, c_size_t dest_max_len);
|
||||
|
||||
/*
|
||||
* Retrieves a single byte from the buffer at a relative index position from the head.
|
||||
* @param self: The ring buffer instance.
|
||||
* @param relative_offset: The offset relative to the head pointer (0 = oldest unread byte, size-1 = newest byte).
|
||||
* @param out_byte: Destination pointer for the extracted byte.
|
||||
* @return C_SUCCESS on clean execution, C_ERR_INVALID_PARAM, or C_ERR_OUT_OF_BOUNDS.
|
||||
*/
|
||||
c_err_t c_ByteRingBuffer_GetAtRelative(const c_ByteRingBuffer_t* self, c_size_t relative_offset, uint8_t* out_byte);
|
||||
|
||||
/*
|
||||
* Checks if the byte at a specific relative offset from the head matches the given value.
|
||||
* @param self: The ring buffer instance.
|
||||
* @param offset: The relative offset from the current head pointer (0 = oldest unread byte).
|
||||
* @param value: The expected byte value to compare against.
|
||||
* @return C_TRUE (1) if it matches perfectly, C_FALSE (0) if it mismatches, is empty, or out of bounds.
|
||||
*/
|
||||
c_bool_t c_ByteRingBuffer_Is(const c_ByteRingBuffer_t* self, c_index_t offset, uint8_t value);
|
||||
|
||||
/*
|
||||
* Compares the contents of the ring buffer starting at a relative offset with an external flat buffer.
|
||||
* @param self: The ring buffer instance.
|
||||
* @param offset: The relative offset from the current head pointer to start comparing from.
|
||||
* @param buffer: The external memory array to compare against.
|
||||
* @param len: The number of bytes to compare.
|
||||
* @return 0 if the memory blocks match exactly, < 0 if the ring buffer data is lexicographically smaller,
|
||||
* > 0 if it is larger. Returns (C_ERR_INVALID_PARAM) or (C_ERR_OUT_OF_BOUNDS) on range violations.
|
||||
*/
|
||||
int c_ByteRingBuffer_Memcmp(const c_ByteRingBuffer_t* self, c_size_t offset, const uint8_t* buffer, c_size_t len);
|
||||
|
||||
/*
|
||||
* Parses an unsigned long value from the ring buffer starting at a specific relative offset.
|
||||
* @param self: The ring buffer instance.
|
||||
* @param offset: The relative offset from the current head pointer to start parsing from.
|
||||
* @param base: The number base system to parse (0 for auto-detection, 2-36).
|
||||
* @param out_value: Destination pointer for the parsed unsigned long.
|
||||
* @param out_end_offset: Optional destination pointer for the relative offset immediately following the parsed number.
|
||||
* @return C_SUCCESS on clean execution, C_ERR_INVALID_PARAM, or C_ERR_OUT_OF_BOUNDS.
|
||||
*/
|
||||
c_err_t c_ByteRingBuffer_Strtoul(const c_ByteRingBuffer_t* self, c_size_t offset, int base, unsigned long* out_value, c_size_t* out_end_offset);
|
||||
|
||||
|
||||
#endif /*INCLUDED_C_BYTERINGBUFFER_H*/
|
||||
|
||||
@@ -2,61 +2,341 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define RUN_TEST_CASE(test_func) \
|
||||
do { \
|
||||
printf("[RUNNING] %-40s ... ", #test_func); \
|
||||
fflush(stdout); \
|
||||
test_func(); \
|
||||
printf("[PASSED]\n"); \
|
||||
} while (0)
|
||||
|
||||
void test_log(const char* test_name) {
|
||||
printf("[PASS] %s\n", test_name);
|
||||
void test_ring_buffer_behavior(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 5) == C_SUCCESS);
|
||||
assert(c_ByteRingBuffer_IsEmpty(&ring) == C_TRUE);
|
||||
|
||||
// Fill to capacity bounds
|
||||
assert(c_ByteRingBuffer_WriteByte(&ring, 0xAA) == C_SUCCESS);
|
||||
assert(c_ByteRingBuffer_WriteByte(&ring, 0xBB) == C_SUCCESS);
|
||||
assert(c_ByteRingBuffer_WriteByte(&ring, 0xCC) == C_SUCCESS);
|
||||
assert(c_ByteRingBuffer_GetSize(&ring) == 3);
|
||||
|
||||
uint8_t batch_src[3] = {0x11, 0x22, 0x33};
|
||||
// Should write only 2 bytes because total capacity limit is 5
|
||||
assert(c_ByteRingBuffer_WriteBuffer(&ring, batch_src, 3) == 2);
|
||||
assert(c_ByteRingBuffer_IsFull(&ring) == C_TRUE);
|
||||
|
||||
// Verify retrieval matches FIFO sequencing rules
|
||||
uint8_t read_byte = 0;
|
||||
assert(c_ByteRingBuffer_ReadByte(&ring, &read_byte) == C_SUCCESS);
|
||||
assert(read_byte == 0xAA);
|
||||
assert(c_ByteRingBuffer_IsFull(&ring) == C_FALSE);
|
||||
|
||||
// Bulk wrap-around extraction
|
||||
uint8_t batch_dest[4];
|
||||
assert(c_ByteRingBuffer_ReadBuffer(&ring, batch_dest, 4) == 4);
|
||||
assert(batch_dest[0] == 0xBB);
|
||||
assert(batch_dest[1] == 0xCC);
|
||||
assert(batch_dest[2] == 0x11);
|
||||
assert(batch_dest[3] == 0x22);
|
||||
|
||||
assert(c_ByteRingBuffer_IsEmpty(&ring) == C_TRUE);
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_overwrite_and_peek_mechanics(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 4) == C_SUCCESS); // Capacity = 4
|
||||
|
||||
// 1. Validate Single Overwrite
|
||||
c_ByteRingBuffer_WriteByte(&ring, 0x01);
|
||||
c_ByteRingBuffer_WriteByte(&ring, 0x02);
|
||||
c_ByteRingBuffer_WriteByte(&ring, 0x03);
|
||||
c_ByteRingBuffer_WriteByte(&ring, 0x04); // Buffer now full: [0x01, 0x02, 0x03, 0x04]
|
||||
assert(c_ByteRingBuffer_IsFull(&ring) == C_TRUE);
|
||||
|
||||
c_ByteRingBuffer_WriteByteOverwrite(&ring, 0x05); // 0x01 gets evicted. Head shifts to 0x02
|
||||
|
||||
uint8_t peek_check = 0;
|
||||
assert(c_ByteRingBuffer_PeekByte(&ring, &peek_check) == C_SUCCESS);
|
||||
assert(peek_check == 0x02); // FIFO rules dictate oldest remaining byte is 0x02
|
||||
|
||||
// 2. Validate Bulk Overwrite Loops
|
||||
uint8_t incoming_stream[3] = {0x06, 0x07, 0x08};
|
||||
// Ring has 4 bytes capacity. Writing 3 bytes over an already full buffer evicts [0x02, 0x03, 0x04]
|
||||
assert(c_ByteRingBuffer_WriteBufferOverwrite(&ring, incoming_stream, 3) == 3);
|
||||
|
||||
uint8_t verification_dump[4] = {0};
|
||||
c_size_t read_out = c_ByteRingBuffer_PeekBuffer(&ring, verification_dump, 4);
|
||||
assert(read_out == 4);
|
||||
assert(verification_dump[0] == 0x05); // Preserved from previous transaction
|
||||
assert(verification_dump[1] == 0x06);
|
||||
assert(verification_dump[2] == 0x07);
|
||||
assert(verification_dump[3] == 0x08);
|
||||
|
||||
// 3. Confirm Peek leaves data sequence entirely untouched
|
||||
assert(c_ByteRingBuffer_GetSize(&ring) == 4);
|
||||
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_dma_and_discard_mechanics(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8
|
||||
|
||||
// Force initialization of data that loops around the internal ring memory map
|
||||
uint8_t payload[] = {0xA1, 0xA2, 0xA3, 0xA4, 0xA5};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, payload, 5);
|
||||
|
||||
// Discard oldest 2 items: drops 0xA1, 0xA2. Cursors shift.
|
||||
assert(c_ByteRingBuffer_Discard(&ring, 2) == 2);
|
||||
assert(c_ByteRingBuffer_GetSize(&ring) == 3);
|
||||
|
||||
// Write some more to force a wrap-around layout
|
||||
uint8_t wrap_payload[] = {0xB1, 0xB2, 0xB3, 0xB4};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, wrap_payload, 4); // Total size now = 7 bytes
|
||||
|
||||
// Validate GetReadPtr isolates Block Segment 1 cleanly
|
||||
c_size_t read_chunk_len = 0;
|
||||
const uint8_t* read_ptr = c_ByteRingBuffer_GetReadPtr(&ring, &read_chunk_len);
|
||||
|
||||
assert(read_ptr != NULL);
|
||||
// Head was shifted to index 2. 8 - 2 = 6 available linearly up to memory array edge
|
||||
assert(read_chunk_len == 6);
|
||||
assert(read_ptr[0] == 0xA3); // First remaining item
|
||||
|
||||
// Clear those processed items via direct execution tracking
|
||||
c_ByteRingBuffer_Discard(&ring, read_chunk_len);
|
||||
|
||||
// Call secondary pass to capture remaining wrapped bytes
|
||||
read_ptr = c_ByteRingBuffer_GetReadPtr(&ring, &read_chunk_len);
|
||||
assert(read_chunk_len == 1);
|
||||
assert(read_ptr[0] == 0xB4); // Wrapped character check
|
||||
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_ring_buffer_index_searching(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8
|
||||
|
||||
// Force a data write footprint that wraps around the buffer margins
|
||||
uint8_t standard_fill[] = {0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, standard_fill, 8);
|
||||
|
||||
// Discard 4 elements to position head cursor at absolute index 4
|
||||
c_ByteRingBuffer_Discard(&ring, 4);
|
||||
|
||||
// Append sequence to cross edge wrap boundaries cleanly
|
||||
uint8_t wrap_fill[] = {0x55, 0x66, 0x77};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, wrap_fill, 3);
|
||||
// Dynamic ring content layout from head: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]
|
||||
|
||||
// 1. Single byte search lookup match
|
||||
assert(c_ByteRingBuffer_IndexOfByte(&ring, 0x33) == 2); // Relative offset index 2 from head
|
||||
assert(c_ByteRingBuffer_IndexOfByte(&ring, 0x99) == C_ERR_NOT_FOUND);
|
||||
|
||||
// 2. Pattern buffer sequence scan (testing across structural wrap-around edge boundaries)
|
||||
uint8_t search_pattern[] = {0x44, 0x55, 0x66};
|
||||
c_index_t match_offset = c_ByteRingBuffer_IndexOfBuffer(&ring, search_pattern, 3);
|
||||
|
||||
assert(match_offset == 3); // 0x44 sits exactly at relative offset position 3
|
||||
|
||||
// Application Workflow Integration: Cleanly discard up to token match prefix point
|
||||
c_ByteRingBuffer_Discard(&ring, (c_size_t)match_offset);
|
||||
uint8_t current_head_byte = 0;
|
||||
c_ByteRingBuffer_PeekByte(&ring, ¤t_head_byte);
|
||||
assert(current_head_byte == 0x44); // The buffer head has been successfully synchronized to the token location
|
||||
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_reverse_search_and_token_stream(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8
|
||||
|
||||
uint8_t raw_payload[] = {0xAA, 0x11, 0x22, 0xBB, 0x11, 0x22, 0xCC, 0xDD};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, raw_payload, 8);
|
||||
|
||||
uint8_t pattern[] = {0x11, 0x22};
|
||||
|
||||
// 1. Verify Reverse Pattern Detection Matches Latest Occurrence
|
||||
assert(c_ByteRingBuffer_IndexOfBuffer(&ring, pattern, 2) == 1); // First pair starts at offset 1
|
||||
assert(c_ByteRingBuffer_LastIndexOfBuffer(&ring, pattern, 2) == 4); // Latest pair starts at offset 4
|
||||
|
||||
// 2. Clear out buffer to run frame serialization test
|
||||
c_ByteRingBuffer_Discard(&ring, 8);
|
||||
|
||||
uint8_t stream_data[] = {'P', 'a', 'c', 'k', 'e', 't', '\r', '\n'};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, stream_data, 8);
|
||||
|
||||
uint8_t frame_terminator[] = {'\r', '\n'};
|
||||
uint8_t output_staging[16] = {0};
|
||||
|
||||
// Fail Case: Pass a staging array that is structurally too cramped to hold the payload safely
|
||||
assert(c_ByteRingBuffer_ReadUntilToken(&ring, frame_terminator, 2, output_staging, 5) == 0);
|
||||
assert(c_ByteRingBuffer_GetSize(&ring) == 8); // Data remains locked safely inside the ring
|
||||
|
||||
// Success Case: Pass a valid container size to execute frame extraction
|
||||
c_size_t read_bytes = c_ByteRingBuffer_ReadUntilToken(&ring, frame_terminator, 2, output_staging, 16);
|
||||
assert(read_bytes == 8);
|
||||
assert(memcmp(output_staging, "Packet\r\n", 8) == 0);
|
||||
assert(c_ByteRingBuffer_IsEmpty(&ring) == C_TRUE); // Frame has been cleanly consumed from the queue
|
||||
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_relative_random_access(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 4) == C_SUCCESS); // Capacity = 4
|
||||
|
||||
uint8_t seed_data[] = {0x10, 0x20, 0x30};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, seed_data, 3);
|
||||
|
||||
// Shift the head pointer downstream via a single byte consumption
|
||||
uint8_t discard_sink = 0;
|
||||
c_ByteRingBuffer_ReadByte(&ring, &discard_sink); // 0x10 dropped. Head points to 0x20
|
||||
|
||||
// Append items to cross physical wrapping thresholds cleanly
|
||||
c_ByteRingBuffer_WriteByte(&ring, 0x40);
|
||||
c_ByteRingBuffer_WriteByte(&ring, 0x50); // Buffer contents: [0x20, 0x30, 0x40, 0x50]
|
||||
|
||||
uint8_t extracted_byte = 0;
|
||||
|
||||
// 1. Verify standard random read coordinates relative to the head position
|
||||
assert(c_ByteRingBuffer_GetAtRelative(&ring, 0, &extracted_byte) == C_SUCCESS);
|
||||
assert(extracted_byte == 0x20); // Relative 0 points directly to the current head
|
||||
|
||||
assert(c_ByteRingBuffer_GetAtRelative(&ring, 2, &extracted_byte) == C_SUCCESS);
|
||||
assert(extracted_byte == 0x40); // Wrapped index check
|
||||
|
||||
assert(c_ByteRingBuffer_GetAtRelative(&ring, 3, &extracted_byte) == C_SUCCESS);
|
||||
assert(extracted_byte == 0x50); // Newest unread byte check
|
||||
|
||||
// 2. Validate bounds-checking flags
|
||||
assert(c_ByteRingBuffer_GetAtRelative(&ring, 4, &extracted_byte) == C_ERR_OUT_OF_BOUNDS);
|
||||
assert(c_ByteRingBuffer_GetAtRelative(&ring, 99, &extracted_byte) == C_ERR_OUT_OF_BOUNDS);
|
||||
assert(c_ByteRingBuffer_GetAtRelative(NULL, 0, &extracted_byte) == C_ERR_INVALID_PARAM);
|
||||
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_conditional_is_validator(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 4) == C_SUCCESS);
|
||||
|
||||
uint8_t input_stream[] = {0xAA, 0xBB, 0xCC};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, input_stream, 3);
|
||||
|
||||
// 1. Validate clean true/false matches relative to the head
|
||||
assert(c_ByteRingBuffer_Is(&ring, 0, 0xAA) == C_TRUE); // Oldest byte at head matches
|
||||
assert(c_ByteRingBuffer_Is(&ring, 1, 0xBB) == C_TRUE); // Next element matches
|
||||
assert(c_ByteRingBuffer_Is(&ring, 1, 0x99) == C_FALSE); // Mismatch returns false
|
||||
|
||||
// Consume 1 byte to advance the head pointer and test wrapping boundaries
|
||||
uint8_t sink = 0;
|
||||
c_ByteRingBuffer_ReadByte(&ring, &sink); // Head now points to 0xBB
|
||||
c_ByteRingBuffer_WriteByte(&ring, 0xDD); // Layout: [..., 0xBB, 0xCC, 0xDD]
|
||||
|
||||
// 2. Re-verify offsets after structural pointer wrap shifts
|
||||
assert(c_ByteRingBuffer_Is(&ring, 0, 0xBB) == C_TRUE); // Head index position 0 is now 0xBB
|
||||
assert(c_ByteRingBuffer_Is(&ring, 2, 0xDD) == C_TRUE); // Wrapped index element match
|
||||
|
||||
// 3. Confirm error/bounds conditions return false instead of blowing up memory layout boundaries
|
||||
assert(c_ByteRingBuffer_Is(&ring, 3, 0x00) == C_FALSE); // Out of bounds index
|
||||
assert(c_ByteRingBuffer_Is(&ring, -5, 0xBB) == C_FALSE); // Negative index handling protection
|
||||
assert(c_ByteRingBuffer_Is(NULL, 0, 0xBB) == C_FALSE); // NULL safety check
|
||||
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_ring_buffer_memcmp(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 6) == C_SUCCESS); // Capacity = 6
|
||||
|
||||
uint8_t payload[] = {0x00, 0x11, 0x22, 0x33};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, payload, 4);
|
||||
|
||||
// Consume 2 bytes to step the head pointer forward to absolute index 2
|
||||
uint8_t sink = 0;
|
||||
c_ByteRingBuffer_ReadByte(&ring, &sink);
|
||||
c_ByteRingBuffer_ReadByte(&ring, &sink); // Buffer active layout from head: [0x22, 0x33]
|
||||
|
||||
// Append data to trigger an explicit physical wrap-around edge split layout
|
||||
uint8_t wrap_payload[] = {0x44, 0x55, 0x66};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, wrap_payload, 3);
|
||||
// Buffer dynamic content path tracking from head: [0x22, 0x33, 0x44, 0x55, 0x66]
|
||||
// Physical layout behind indices inside array: [0x55, 0x66, 0x22, 0x33, 0x44, ...]
|
||||
|
||||
// 1. Validate contiguous segment match comparisons
|
||||
uint8_t check_a[] = {0x22, 0x33};
|
||||
assert(c_ByteRingBuffer_Memcmp(&ring, 0, check_a, 2) == 0); // Perfect contiguous match
|
||||
|
||||
// 2. Validate multi-segment wrap-around comparison mechanics
|
||||
uint8_t check_b[] = {0x33, 0x44, 0x55, 0x66};
|
||||
assert(c_ByteRingBuffer_Memcmp(&ring, 1, check_b, 4) == 0); // Perfect split wrap-around match
|
||||
|
||||
// 3. Mismatch checks
|
||||
uint8_t check_mismatch[] = {0x33, 0x44, 0x99, 0x66};
|
||||
assert(c_ByteRingBuffer_Memcmp(&ring, 1, check_mismatch, 4) != 0); // Identifies internal divergence
|
||||
|
||||
// 4. Bounds and parameter checks
|
||||
assert(c_ByteRingBuffer_Memcmp(&ring, 0, check_b, 100) == C_ERR_OUT_OF_BOUNDS); // Request width overflows content
|
||||
assert(c_ByteRingBuffer_Memcmp(&ring, 99, check_b, 1) == C_ERR_OUT_OF_BOUNDS); // Start pointer invalid
|
||||
assert(c_ByteRingBuffer_Memcmp(NULL, 0, check_b, 1) == C_ERR_INVALID_PARAM);
|
||||
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
static void test_ring_buffer_strtoul(void) {
|
||||
c_ByteRingBuffer_t ring;
|
||||
assert(c_ByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8
|
||||
|
||||
// 1. Standard Linear Base-10 Parsing
|
||||
uint8_t input_a[] = {'1', '2', '3', '4', ' ', 'A', 'B', 'C'};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, input_a, 8);
|
||||
|
||||
unsigned long parsed_val = 0;
|
||||
c_size_t end_offset = 0;
|
||||
|
||||
assert(c_ByteRingBuffer_Strtoul(&ring, 0, 10, &parsed_val, &end_offset) == C_SUCCESS);
|
||||
assert(parsed_val == 1234);
|
||||
assert(end_offset == 4); // Points exactly to the trailing space character offset
|
||||
|
||||
// Reset buffer tracking lines
|
||||
c_ByteRingBuffer_Discard(&ring, 8);
|
||||
|
||||
// 2. Fragmented Wrap Hex Parsing
|
||||
// Pre-fill 5 elements to push cursor indices near wrapping layout boundaries
|
||||
uint8_t pre_fill[] = {0, 0, 0, 0, 0};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, pre_fill, 5);
|
||||
c_ByteRingBuffer_Discard(&ring, 5); // Head pointer sits at physical index 5
|
||||
|
||||
// Write Hex parameter payload string ("0x2F") across memory boundaries
|
||||
uint8_t input_hex[] = {'0', 'x', '2', 'F'};
|
||||
c_ByteRingBuffer_WriteBuffer(&ring, input_hex, 4);
|
||||
|
||||
assert(c_ByteRingBuffer_Strtoul(&ring, 0, 16, &parsed_val, &end_offset) == C_SUCCESS);
|
||||
assert(parsed_val == 47); // 0x2F translates to decimal 47
|
||||
assert(end_offset == 4);
|
||||
|
||||
c_ByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
printf("==================================================\n");
|
||||
printf(" Starting c_ByteRingBuffer Unit Testing Suite\n");
|
||||
printf("==================================================\n\n");
|
||||
|
||||
c_ByteRingBuffer_t rb;
|
||||
// Set fixed byte size capacity to 4
|
||||
c_err_t err = c_ByteRingBuffer_Init(&rb, 4);
|
||||
assert(err == C_ERR_SUCCESS);
|
||||
assert(c_ByteRingBuffer_IsEmpty(&rb) == C_TRUE);
|
||||
test_log("1. Raw byte buffer initialization verified");
|
||||
|
||||
// ==========================================
|
||||
// 2. Testing Single Byte Operations
|
||||
// ==========================================
|
||||
err = c_ByteRingBuffer_WriteByte(&rb, 0xAA); assert(err == C_ERR_SUCCESS);
|
||||
err = c_ByteRingBuffer_WriteByte(&rb, 0xBB); assert(err == C_ERR_SUCCESS);
|
||||
assert(c_ByteRingBuffer_GetSize(&rb) == 2);
|
||||
|
||||
uint8_t byte_out = 0;
|
||||
err = c_ByteRingBuffer_ReadByte(&rb, &byte_out);
|
||||
assert(err == C_ERR_SUCCESS);
|
||||
assert(byte_out == 0xAA);
|
||||
assert(c_ByteRingBuffer_GetSize(&rb) == 1);
|
||||
test_log("2. Single byte discrete FIFO verified");
|
||||
|
||||
// Clear buffer out
|
||||
c_ByteRingBuffer_ReadByte(&rb, &byte_out);
|
||||
assert(c_ByteRingBuffer_IsEmpty(&rb) == C_TRUE);
|
||||
|
||||
// ==========================================
|
||||
// 3. Testing Streaming Buffer Operations
|
||||
// ==========================================
|
||||
uint8_t stream_in[5] = {0x11, 0x22, 0x33, 0x44, 0x55};
|
||||
|
||||
// Total capacity is 4. Passing a length of 5 should stream up to max boundary
|
||||
c_size_t written = c_ByteRingBuffer_WriteBuffer(&rb, stream_in, 5);
|
||||
assert(written == 4);
|
||||
assert(c_ByteRingBuffer_IsFull(&rb) == C_TRUE);
|
||||
|
||||
uint8_t stream_out[4] = {0};
|
||||
c_size_t read = c_ByteRingBuffer_ReadBuffer(&rb, stream_out, 4);
|
||||
assert(read == 4);
|
||||
assert(stream_out[0] == 0x11);
|
||||
assert(stream_out[3] == 0x44);
|
||||
assert(c_ByteRingBuffer_IsEmpty(&rb) == C_TRUE);
|
||||
test_log("3. Bulk string/buffer array streams chunk-read verified");
|
||||
|
||||
c_ByteRingBuffer_Destroy(&rb);
|
||||
test_log("4. Clean resource teardown verified");
|
||||
RUN_TEST_CASE(test_ring_buffer_behavior);
|
||||
RUN_TEST_CASE(test_overwrite_and_peek_mechanics);
|
||||
RUN_TEST_CASE(test_dma_and_discard_mechanics);
|
||||
RUN_TEST_CASE(test_ring_buffer_index_searching);
|
||||
RUN_TEST_CASE(test_reverse_search_and_token_stream);
|
||||
RUN_TEST_CASE(test_relative_random_access);
|
||||
RUN_TEST_CASE(test_conditional_is_validator);
|
||||
RUN_TEST_CASE(test_ring_buffer_memcmp);
|
||||
RUN_TEST_CASE(test_ring_buffer_strtoul);
|
||||
|
||||
printf("\n==================================================\n");
|
||||
printf(" Success! Byte RingBuffer tests passed!\n");
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#include <c_FastByteRingBuffer.h>
|
||||
#include <c_Memory.h>
|
||||
#include <c_Alignment.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
c_size_t round_up_to_pow2(c_size_t v) {
|
||||
@@ -82,23 +87,46 @@ c_err_t c_FastByteRingBuffer_ReadByte(c_FastByteRingBuffer_t* self, uint8_t* out
|
||||
return C_ERR_SUCCESS;
|
||||
}
|
||||
|
||||
// 區塊寫入
|
||||
|
||||
c_size_t c_FastByteRingBuffer_WriteBuffer(c_FastByteRingBuffer_t* self, const uint8_t* src, c_size_t len) {
|
||||
if (!self || !self->buffer || !src || len == 0) return 0;
|
||||
if (self->is_full) return 0;
|
||||
|
||||
c_size_t bytes_written = 0;
|
||||
while (bytes_written < len && !self->is_full) {
|
||||
self->buffer[self->tail] = src[bytes_written];
|
||||
self->tail = (self->tail + 1) & self->mask;
|
||||
// Calculate free space directly using fast size validation tracking
|
||||
c_size_t current_size = c_FastByteRingBuffer_GetSize(self);
|
||||
c_size_t free_space = self->capacity - current_size;
|
||||
|
||||
// Clamp write operations to avoid overrunning existing readable elements
|
||||
if (len > free_space) {
|
||||
len = free_space;
|
||||
}
|
||||
|
||||
if (len == 0) return 0;
|
||||
|
||||
// Segment 1: Write from tail index up to the physical end boundary of the backing array
|
||||
c_size_t space_to_end = self->capacity - self->tail;
|
||||
c_size_t first_chunk = (len < space_to_end) ? len : space_to_end;
|
||||
memcpy(self->buffer + self->tail, src, first_chunk);
|
||||
|
||||
// Segment 2: Wrap around to index 0 using bitwise optimizations if a split block configuration is required
|
||||
c_size_t second_chunk = len - first_chunk;
|
||||
if (second_chunk > 0) {
|
||||
memcpy(self->buffer, src + first_chunk, second_chunk);
|
||||
self->tail = second_chunk; // The wrapped tail calculation reduces cleanly to second_chunk
|
||||
} else {
|
||||
// Fast tail stepping path utilizing the mask constant
|
||||
self->tail = (self->tail + first_chunk) & self->mask;
|
||||
}
|
||||
|
||||
// Set the full status flag if the cursors perfectly intersect
|
||||
if (self->tail == self->head) {
|
||||
self->is_full = C_TRUE;
|
||||
}
|
||||
bytes_written++;
|
||||
}
|
||||
return bytes_written;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
// 區塊讀取
|
||||
c_size_t c_FastByteRingBuffer_ReadBuffer(c_FastByteRingBuffer_t* self, uint8_t* dest, c_size_t len) {
|
||||
if (!self || !self->buffer || !dest || len == 0) return 0;
|
||||
@@ -133,3 +161,370 @@ c_bool_t c_FastByteRingBuffer_IsFull(const c_FastByteRingBuffer_t* self) {
|
||||
if (!self) return C_FALSE;
|
||||
return self->is_full;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
|
||||
C_STATIC_FORCE_INLINE
|
||||
uint8_t c_FastByteRingBuffer_GetAtRelativeInternal(const c_FastByteRingBuffer_t* self, c_size_t relative_offset) {
|
||||
c_size_t absolute_index = (self->head + relative_offset) & self->mask;
|
||||
return self->buffer[absolute_index];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
void c_FastByteRingBuffer_WriteByteOverwrite(c_FastByteRingBuffer_t* self, uint8_t byte) {
|
||||
if (!self || !self->buffer) return;
|
||||
|
||||
if (self->is_full) {
|
||||
// 環形陣列已滿時,強制將讀取指標向前推一格,拋棄最舊數據
|
||||
self->head = (self->head + 1) & self->mask;
|
||||
}
|
||||
|
||||
self->buffer[self->tail] = byte;
|
||||
self->tail = (self->tail + 1) & self->mask;
|
||||
|
||||
if (self->tail == self->head) {
|
||||
self->is_full = C_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
c_size_t c_FastByteRingBuffer_WriteBufferOverwrite(c_FastByteRingBuffer_t* self, const uint8_t* src, c_size_t len) {
|
||||
if (!self || !self->buffer || !src || len == 0) return 0;
|
||||
|
||||
// 邊界極端優化:如果寫入長度超過總容量,只有最後符合容量大小的數據能存活
|
||||
if (len >= self->capacity) {
|
||||
src += (len - self->capacity);
|
||||
len = self->capacity;
|
||||
|
||||
memcpy(self->buffer, src, len);
|
||||
self->head = 0;
|
||||
self->tail = 0;
|
||||
self->is_full = C_TRUE;
|
||||
return len;
|
||||
}
|
||||
|
||||
c_size_t size = c_FastByteRingBuffer_GetSize(self);
|
||||
c_size_t free_space = self->capacity - size;
|
||||
|
||||
// 若寫入長度大於剩餘空間,計算溢出量並自動同步前推 head 指標
|
||||
if (len > free_space) {
|
||||
c_size_t overwrite_count = len - free_space;
|
||||
self->head = (self->head + overwrite_count) & self->mask;
|
||||
}
|
||||
|
||||
// 分段一:從 tail 寫入到物理內存陣列末尾
|
||||
c_size_t space_to_end = self->capacity - self->tail;
|
||||
c_size_t first_chunk = (len < space_to_end) ? len : space_to_end;
|
||||
memcpy(self->buffer + self->tail, src, first_chunk);
|
||||
|
||||
// 分段二:折返到內存陣列開頭寫入剩餘數據
|
||||
c_size_t second_chunk = len - first_chunk;
|
||||
if (second_chunk > 0) {
|
||||
memcpy(self->buffer, src + first_chunk, second_chunk);
|
||||
self->tail = second_chunk;
|
||||
} else {
|
||||
self->tail = (self->tail + first_chunk) & self->mask;
|
||||
}
|
||||
|
||||
if (self->tail == self->head) {
|
||||
self->is_full = C_TRUE;
|
||||
} else {
|
||||
self->is_full = C_FALSE;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
c_err_t c_FastByteRingBuffer_PeekByte(const c_FastByteRingBuffer_t* self, uint8_t* out_byte) {
|
||||
if (!self || !self->buffer || !out_byte) return C_ERR_INVALID_PARAM;
|
||||
if (c_FastByteRingBuffer_IsEmpty(self)) return C_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
*out_byte = self->buffer[self->head];
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
c_size_t c_FastByteRingBuffer_PeekBuffer(const c_FastByteRingBuffer_t* self, uint8_t* dest, c_size_t len) {
|
||||
if (!self || !self->buffer || !dest || len == 0) return 0;
|
||||
|
||||
c_size_t available_bytes = c_FastByteRingBuffer_GetSize(self);
|
||||
if (len > available_bytes) {
|
||||
len = available_bytes;
|
||||
}
|
||||
|
||||
if (len == 0) return 0;
|
||||
|
||||
// 複製標準讀取邏輯,但完全不變動真實核心 head 指標狀態
|
||||
c_size_t bytes_to_end = self->capacity - self->head;
|
||||
c_size_t first_chunk = (len < bytes_to_end) ? len : bytes_to_end;
|
||||
memcpy(dest, self->buffer + self->head, first_chunk);
|
||||
|
||||
c_size_t second_chunk = len - first_chunk;
|
||||
if (second_chunk > 0) {
|
||||
memcpy(dest + first_chunk, self->buffer, second_chunk);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
c_size_t c_FastByteRingBuffer_Discard(c_FastByteRingBuffer_t* self, c_size_t len) {
|
||||
if (!self || !self->buffer || len == 0) return 0;
|
||||
|
||||
c_size_t available_bytes = c_FastByteRingBuffer_GetSize(self);
|
||||
if (len > available_bytes) {
|
||||
len = available_bytes;
|
||||
}
|
||||
|
||||
if (len == 0) return 0;
|
||||
|
||||
self->head = (self->head + len) & self->mask;
|
||||
self->is_full = C_FALSE;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
const uint8_t* c_FastByteRingBuffer_GetReadPtr(const c_FastByteRingBuffer_t* self, c_size_t* out_contiguous_len) {
|
||||
if (!self || !self->buffer || !out_contiguous_len) return NULL;
|
||||
|
||||
*out_contiguous_len = 0;
|
||||
if (c_FastByteRingBuffer_IsEmpty(self)) return NULL;
|
||||
|
||||
if (self->tail > self->head) {
|
||||
*out_contiguous_len = self->tail - self->head;
|
||||
} else {
|
||||
*out_contiguous_len = self->capacity - self->head;
|
||||
}
|
||||
|
||||
return self->buffer + self->head;
|
||||
}
|
||||
|
||||
uint8_t* c_FastByteRingBuffer_GetWritePtr(const c_FastByteRingBuffer_t* self, c_size_t* out_contiguous_len) {
|
||||
if (!self || !self->buffer || !out_contiguous_len) return NULL;
|
||||
|
||||
*out_contiguous_len = 0;
|
||||
if (self->is_full) return NULL;
|
||||
|
||||
if (self->tail >= self->head) {
|
||||
*out_contiguous_len = self->capacity - self->tail;
|
||||
} else {
|
||||
*out_contiguous_len = self->head - self->tail;
|
||||
}
|
||||
|
||||
return self->buffer + self->tail;
|
||||
}
|
||||
|
||||
c_index_t c_FastByteRingBuffer_IndexOfByte(const c_FastByteRingBuffer_t* self, uint8_t target) {
|
||||
if (!self || !self->buffer) return C_ERR_NOT_FOUND;
|
||||
|
||||
c_size_t total_size = c_FastByteRingBuffer_GetSize(self);
|
||||
for (c_size_t offset = 0; offset < total_size; offset++) {
|
||||
if (c_FastByteRingBuffer_GetAtRelativeInternal(self, offset) == target) {
|
||||
return (c_index_t)offset;
|
||||
}
|
||||
}
|
||||
return C_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
c_index_t c_FastByteRingBuffer_IndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) {
|
||||
if (!self || !self->buffer || !pattern || pattern_len == 0) return C_ERR_NOT_FOUND;
|
||||
|
||||
c_size_t total_size = c_FastByteRingBuffer_GetSize(self);
|
||||
if (pattern_len > total_size) return C_ERR_NOT_FOUND;
|
||||
|
||||
c_size_t max_search_offset = total_size - pattern_len;
|
||||
|
||||
for (c_size_t offset = 0; offset <= max_search_offset; offset++) {
|
||||
c_bool_t match_found = C_TRUE;
|
||||
|
||||
for (c_size_t p_idx = 0; p_idx < pattern_len; p_idx++) {
|
||||
if (c_FastByteRingBuffer_GetAtRelativeInternal(self, offset + p_idx) != pattern[p_idx]) {
|
||||
match_found = C_FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match_found) {
|
||||
return (c_index_t)offset;
|
||||
}
|
||||
}
|
||||
return C_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
c_index_t c_FastByteRingBuffer_LastIndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len) {
|
||||
if (!self || !self->buffer || !pattern || pattern_len == 0) return C_ERR_NOT_FOUND;
|
||||
|
||||
c_size_t total_size = c_FastByteRingBuffer_GetSize(self);
|
||||
if (pattern_len > total_size) return C_ERR_NOT_FOUND;
|
||||
|
||||
c_size_t max_search_offset = total_size - pattern_len;
|
||||
|
||||
for (c_size_t offset = max_search_offset; ; offset--) {
|
||||
c_bool_t match_found = C_TRUE;
|
||||
|
||||
for (c_size_t p_idx = 0; p_idx < pattern_len; p_idx++) {
|
||||
if (c_FastByteRingBuffer_GetAtRelativeInternal(self, offset + p_idx) != pattern[p_idx]) {
|
||||
match_found = C_FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match_found) {
|
||||
return (c_index_t)offset;
|
||||
}
|
||||
|
||||
if (offset == 0) break;
|
||||
}
|
||||
return C_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
c_size_t c_FastByteRingBuffer_ReadUntilToken(c_FastByteRingBuffer_t* self, const uint8_t* token, c_size_t token_len, uint8_t* dest, c_size_t dest_max_len) {
|
||||
if (!self || !self->buffer || !token || token_len == 0 || !dest || dest_max_len == 0) return 0;
|
||||
|
||||
c_index_t match_offset = c_FastByteRingBuffer_IndexOfBuffer(self, token, token_len);
|
||||
if (match_offset == C_ERR_NOT_FOUND) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
c_size_t aggregate_bytes = (c_size_t)match_offset + token_len;
|
||||
if (aggregate_bytes > dest_max_len) {
|
||||
return 0; // 避免目標緩衝區溢出
|
||||
}
|
||||
|
||||
return c_FastByteRingBuffer_ReadBuffer(self, dest, aggregate_bytes);
|
||||
}
|
||||
|
||||
c_err_t c_FastByteRingBuffer_GetAtRelative(const c_FastByteRingBuffer_t* self, c_size_t relative_offset, uint8_t* out_byte) {
|
||||
if (!self || !self->buffer || !out_byte) return C_ERR_INVALID_PARAM;
|
||||
|
||||
c_size_t active_size = c_FastByteRingBuffer_GetSize(self);
|
||||
if (relative_offset >= active_size) {
|
||||
return C_ERR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
*out_byte = c_FastByteRingBuffer_GetAtRelativeInternal(self, relative_offset);
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
c_bool_t c_FastByteRingBuffer_Is(const c_FastByteRingBuffer_t* self, c_index_t offset, uint8_t value) {
|
||||
if (!self || !self->buffer || offset < 0) return C_FALSE;
|
||||
|
||||
c_size_t active_size = c_FastByteRingBuffer_GetSize(self);
|
||||
if ((c_size_t)offset >= active_size) return C_FALSE;
|
||||
|
||||
return (c_FastByteRingBuffer_GetAtRelativeInternal(self, (c_size_t)offset) == value) ? C_TRUE : C_FALSE;
|
||||
}
|
||||
|
||||
int c_FastByteRingBuffer_Memcmp(const c_FastByteRingBuffer_t* self, c_size_t offset, const uint8_t* buffer, c_size_t len) {
|
||||
if (!self || !self->buffer || !buffer) return C_ERR_INVALID_PARAM;
|
||||
if (len == 0) return 0;
|
||||
|
||||
c_size_t active_size = c_FastByteRingBuffer_GetSize(self);
|
||||
if (offset >= active_size || (offset + len) > active_size) {
|
||||
return C_ERR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
c_size_t absolute_start = (self->head + offset) & self->mask;
|
||||
c_size_t bytes_to_end = self->capacity - absolute_start;
|
||||
|
||||
if (len <= bytes_to_end) {
|
||||
return memcmp(self->buffer + absolute_start, buffer, len);
|
||||
} else {
|
||||
int first_segment_match = memcmp(self->buffer + absolute_start, buffer, bytes_to_end);
|
||||
if (first_segment_match != 0) {
|
||||
return first_segment_match;
|
||||
}
|
||||
return memcmp(self->buffer, buffer + bytes_to_end, len - bytes_to_end);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
c_err_t c_FastByteRingBuffer_Strtoul(const c_FastByteRingBuffer_t* self, c_size_t offset, int base, unsigned long* out_value, c_size_t* out_end_offset) {
|
||||
if (!self || !self->buffer || !out_value) return C_ERR_INVALID_PARAM;
|
||||
|
||||
c_size_t total_size = c_FastByteRingBuffer_GetSize(self);
|
||||
if (offset >= total_size) return C_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
// 1. Skip leading spaces safely using fast inline relative scans
|
||||
c_size_t scan_idx = offset;
|
||||
while (scan_idx < total_size) {
|
||||
uint8_t byte = c_FastByteRingBuffer_GetAtRelativeInternal(self, scan_idx);
|
||||
if (!isspace(byte)) break;
|
||||
scan_idx++;
|
||||
}
|
||||
|
||||
if (scan_idx == total_size) return C_ERR_INVALID_PARAM; // Contains only whitespace
|
||||
|
||||
// 2. Locate trailing delimiters to calculate numeric chunk span width
|
||||
c_size_t start_numeric_offset = scan_idx;
|
||||
c_size_t numeric_len = 0;
|
||||
while (scan_idx < total_size) {
|
||||
uint8_t byte = c_FastByteRingBuffer_GetAtRelativeInternal(self, scan_idx);
|
||||
// Include numerical modifiers (+, -), hex identifiers (x, X) and alphanumeric bases
|
||||
if (!isalnum(byte) && byte != '+' && byte != '-') {
|
||||
break;
|
||||
}
|
||||
numeric_len++;
|
||||
scan_idx++;
|
||||
}
|
||||
|
||||
if (numeric_len == 0) return C_ERR_INVALID_PARAM;
|
||||
|
||||
// 3. Resolve the starting index using bitwise mask instead of slow % operators
|
||||
c_size_t absolute_start = (self->head + start_numeric_offset) & self->mask;
|
||||
c_size_t bytes_to_end = self->capacity - absolute_start;
|
||||
|
||||
unsigned long result = 0;
|
||||
char* parse_end = NULL;
|
||||
int current_errno = errno;
|
||||
errno = 0;
|
||||
|
||||
// Fast-path: Segment runs contiguous without wrapping lines
|
||||
if (numeric_len <= bytes_to_end) {
|
||||
const char* flat_ptr = (const char*)(self->buffer + absolute_start);
|
||||
result = strtoul(flat_ptr, &parse_end, base);
|
||||
|
||||
c_size_t parsed_bytes = (c_size_t)(parse_end - flat_ptr);
|
||||
if (parsed_bytes == 0 || parse_end == flat_ptr) {
|
||||
errno = current_errno;
|
||||
return C_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (errno == ERANGE) return C_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
*out_value = result;
|
||||
if (out_end_offset) {
|
||||
*out_end_offset = start_numeric_offset + parsed_bytes;
|
||||
}
|
||||
} else {
|
||||
// Slow-path: Structural wrap configuration requires localized stack flattening
|
||||
if (numeric_len >= 64) return C_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
char stack_scratch[64];
|
||||
for (c_size_t i = 0; i < numeric_len; i++) {
|
||||
stack_scratch[i] = (char)c_FastByteRingBuffer_GetAtRelativeInternal(self, start_numeric_offset + i);
|
||||
}
|
||||
stack_scratch[numeric_len] = '\0'; // Guarantee absolute zero string termination
|
||||
|
||||
result = strtoul(stack_scratch, &parse_end, base);
|
||||
c_size_t parsed_bytes = (c_size_t)(parse_end - stack_scratch);
|
||||
|
||||
if (parsed_bytes == 0 || parse_end == stack_scratch) {
|
||||
errno = current_errno;
|
||||
return C_ERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (errno == ERANGE) return C_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
*out_value = result;
|
||||
if (out_end_offset) {
|
||||
*out_end_offset = start_numeric_offset + parsed_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
errno = current_errno; // Preserve runtime environment variables smoothly
|
||||
return C_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -24,11 +24,43 @@ void c_FastByteRingBuffer_Destroy(c_FastByteRingBuffer_t* self);
|
||||
c_err_t c_FastByteRingBuffer_WriteByte(c_FastByteRingBuffer_t* self, uint8_t byte);
|
||||
c_err_t c_FastByteRingBuffer_ReadByte(c_FastByteRingBuffer_t* self, uint8_t* out_byte);
|
||||
|
||||
/*
|
||||
* Writes a bulk buffer span into the fast ring buffer without overwriting existing data.
|
||||
* @param self: The fast ring buffer instance.
|
||||
* @param src: Source byte array pointer.
|
||||
* @param len: Number of bytes to transfer.
|
||||
* @return The actual number of bytes written into the buffer.
|
||||
*/
|
||||
c_size_t c_FastByteRingBuffer_WriteBuffer(c_FastByteRingBuffer_t* self, const uint8_t* src, c_size_t len);
|
||||
|
||||
c_size_t c_FastByteRingBuffer_ReadBuffer(c_FastByteRingBuffer_t* self, uint8_t* dest, c_size_t len);
|
||||
|
||||
c_size_t c_FastByteRingBuffer_GetSize(const c_FastByteRingBuffer_t* self);
|
||||
c_bool_t c_FastByteRingBuffer_IsEmpty(const c_FastByteRingBuffer_t* self);
|
||||
c_bool_t c_FastByteRingBuffer_IsFull(const c_FastByteRingBuffer_t* self);
|
||||
|
||||
|
||||
/* 新增的進階控制接口 */
|
||||
void c_FastByteRingBuffer_WriteByteOverwrite(c_FastByteRingBuffer_t* self, uint8_t byte);
|
||||
c_size_t c_FastByteRingBuffer_WriteBufferOverwrite(c_FastByteRingBuffer_t* self, const uint8_t* src, c_size_t len);
|
||||
|
||||
c_err_t c_FastByteRingBuffer_PeekByte(const c_FastByteRingBuffer_t* self, uint8_t* out_byte);
|
||||
c_size_t c_FastByteRingBuffer_PeekBuffer(const c_FastByteRingBuffer_t* self, uint8_t* dest, c_size_t len);
|
||||
|
||||
c_size_t c_FastByteRingBuffer_Discard(c_FastByteRingBuffer_t* self, c_size_t len);
|
||||
const uint8_t* c_FastByteRingBuffer_GetReadPtr(const c_FastByteRingBuffer_t* self, c_size_t* out_contiguous_len);
|
||||
uint8_t* c_FastByteRingBuffer_GetWritePtr(const c_FastByteRingBuffer_t* self, c_size_t* out_contiguous_len);
|
||||
|
||||
|
||||
c_index_t c_FastByteRingBuffer_IndexOfByte(const c_FastByteRingBuffer_t* self, uint8_t target);
|
||||
c_index_t c_FastByteRingBuffer_IndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len);
|
||||
c_index_t c_FastByteRingBuffer_LastIndexOfBuffer(const c_FastByteRingBuffer_t* self, const uint8_t* pattern, c_size_t pattern_len);
|
||||
|
||||
c_size_t c_FastByteRingBuffer_ReadUntilToken(c_FastByteRingBuffer_t* self, const uint8_t* token, c_size_t token_len, uint8_t* dest, c_size_t dest_max_len);
|
||||
c_err_t c_FastByteRingBuffer_GetAtRelative(const c_FastByteRingBuffer_t* self, c_size_t relative_offset, uint8_t* out_byte);
|
||||
c_bool_t c_FastByteRingBuffer_Is(const c_FastByteRingBuffer_t* self, c_index_t offset, uint8_t value);
|
||||
int c_FastByteRingBuffer_Memcmp(const c_FastByteRingBuffer_t* self, c_size_t offset, const uint8_t* buffer, c_size_t len);
|
||||
c_err_t c_FastByteRingBuffer_Strtoul(const c_FastByteRingBuffer_t* self, c_size_t offset, int base, unsigned long* out_value, c_size_t* out_end_offset);
|
||||
|
||||
|
||||
#endif /*INCLUDED_C_FASTBYTERINGBUFFER_H*/
|
||||
|
||||
@@ -2,72 +2,336 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define RUN_TEST_CASE(test_func) \
|
||||
do { \
|
||||
printf("[RUNNING] %-40s ... ", #test_func); \
|
||||
fflush(stdout); \
|
||||
test_func(); \
|
||||
printf("[PASSED]\n"); \
|
||||
} while (0)
|
||||
|
||||
void test_log(const char* test_name) {
|
||||
printf("[PASS] %s\n", test_name);
|
||||
void test_ring_buffer_behavior(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
// Capacity initialization must be a power of two
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 4) == C_SUCCESS);
|
||||
|
||||
uint8_t input_stream[] = {0x11, 0x22, 0x33};
|
||||
|
||||
// 1. Verify standard incremental writing actions
|
||||
assert(c_FastByteRingBuffer_WriteBuffer(&ring, input_stream, 3) == 3);
|
||||
assert(c_FastByteRingBuffer_GetSize(&ring) == 3);
|
||||
|
||||
// 2. Verify overflow rejection clamping protection
|
||||
uint8_t flood_stream[] = {0x44, 0x55};
|
||||
// Only 1 byte of free space remains out of total capacity 4
|
||||
assert(c_FastByteRingBuffer_WriteBuffer(&ring, flood_stream, 2) == 1);
|
||||
assert(c_FastByteRingBuffer_IsFull(&ring) == C_TRUE);
|
||||
|
||||
// Check data integrity via peek operations
|
||||
uint8_t output_peek[4] = {0};
|
||||
c_FastByteRingBuffer_PeekBuffer(&ring, output_peek, 4);
|
||||
assert(output_peek[0] == 0x11);
|
||||
assert(output_peek[3] == 0x44); // 0x44 filled the last slot; 0x55 was cleanly rejected
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_overwrite_and_peek_mechanics(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 4) == C_SUCCESS); // Capacity = 4
|
||||
|
||||
// 1. Validate Single Overwrite
|
||||
c_FastByteRingBuffer_WriteByte(&ring, 0x01);
|
||||
c_FastByteRingBuffer_WriteByte(&ring, 0x02);
|
||||
c_FastByteRingBuffer_WriteByte(&ring, 0x03);
|
||||
c_FastByteRingBuffer_WriteByte(&ring, 0x04); // Buffer now full: [0x01, 0x02, 0x03, 0x04]
|
||||
assert(c_FastByteRingBuffer_IsFull(&ring) == C_TRUE);
|
||||
|
||||
c_FastByteRingBuffer_WriteByteOverwrite(&ring, 0x05); // 0x01 gets evicted. Head shifts to 0x02
|
||||
|
||||
uint8_t peek_check = 0;
|
||||
assert(c_FastByteRingBuffer_PeekByte(&ring, &peek_check) == C_SUCCESS);
|
||||
assert(peek_check == 0x02); // FIFO rules dictate oldest remaining byte is 0x02
|
||||
|
||||
// 2. Validate Bulk Overwrite Loops
|
||||
uint8_t incoming_stream[3] = {0x06, 0x07, 0x08};
|
||||
// Ring has 4 bytes capacity. Writing 3 bytes over an already full buffer evicts [0x02, 0x03, 0x04]
|
||||
assert(c_FastByteRingBuffer_WriteBufferOverwrite(&ring, incoming_stream, 3) == 3);
|
||||
|
||||
uint8_t verification_dump[4] = {0};
|
||||
c_size_t read_out = c_FastByteRingBuffer_PeekBuffer(&ring, verification_dump, 4);
|
||||
assert(read_out == 4);
|
||||
assert(verification_dump[0] == 0x05); // Preserved from previous transaction
|
||||
assert(verification_dump[1] == 0x06);
|
||||
assert(verification_dump[2] == 0x07);
|
||||
assert(verification_dump[3] == 0x08);
|
||||
|
||||
// 3. Confirm Peek leaves data sequence entirely untouched
|
||||
assert(c_FastByteRingBuffer_GetSize(&ring) == 4);
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_dma_and_discard_mechanics(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8
|
||||
|
||||
// Force initialization of data that loops around the internal ring memory map
|
||||
uint8_t payload[] = {0xA1, 0xA2, 0xA3, 0xA4, 0xA5};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, payload, 5);
|
||||
|
||||
// Discard oldest 2 items: drops 0xA1, 0xA2. Cursors shift.
|
||||
assert(c_FastByteRingBuffer_Discard(&ring, 2) == 2);
|
||||
assert(c_FastByteRingBuffer_GetSize(&ring) == 3);
|
||||
|
||||
// Write some more to force a wrap-around layout
|
||||
uint8_t wrap_payload[] = {0xB1, 0xB2, 0xB3, 0xB4};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, wrap_payload, 4); // Total size now = 7 bytes
|
||||
|
||||
// Validate GetReadPtr isolates Block Segment 1 cleanly
|
||||
c_size_t read_chunk_len = 0;
|
||||
const uint8_t* read_ptr = c_FastByteRingBuffer_GetReadPtr(&ring, &read_chunk_len);
|
||||
|
||||
assert(read_ptr != NULL);
|
||||
// Head was shifted to index 2. 8 - 2 = 6 available linearly up to memory array edge
|
||||
assert(read_chunk_len == 6);
|
||||
assert(read_ptr[0] == 0xA3); // First remaining item
|
||||
|
||||
// Clear those processed items via direct execution tracking
|
||||
c_FastByteRingBuffer_Discard(&ring, read_chunk_len);
|
||||
|
||||
// Call secondary pass to capture remaining wrapped bytes
|
||||
read_ptr = c_FastByteRingBuffer_GetReadPtr(&ring, &read_chunk_len);
|
||||
assert(read_chunk_len == 1);
|
||||
assert(read_ptr[0] == 0xB4); // Wrapped character check
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_ring_buffer_index_searching(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8
|
||||
|
||||
// Force a data write footprint that wraps around the buffer margins
|
||||
uint8_t standard_fill[] = {0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, standard_fill, 8);
|
||||
|
||||
// Discard 4 elements to position head cursor at absolute index 4
|
||||
c_FastByteRingBuffer_Discard(&ring, 4);
|
||||
|
||||
// Append sequence to cross edge wrap boundaries cleanly
|
||||
uint8_t wrap_fill[] = {0x55, 0x66, 0x77};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, wrap_fill, 3);
|
||||
// Dynamic ring content layout from head: [0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]
|
||||
|
||||
// 1. Single byte search lookup match
|
||||
assert(c_FastByteRingBuffer_IndexOfByte(&ring, 0x33) == 2); // Relative offset index 2 from head
|
||||
assert(c_FastByteRingBuffer_IndexOfByte(&ring, 0x99) == C_ERR_NOT_FOUND);
|
||||
|
||||
// 2. Pattern buffer sequence scan (testing across structural wrap-around edge boundaries)
|
||||
uint8_t search_pattern[] = {0x44, 0x55, 0x66};
|
||||
c_index_t match_offset = c_FastByteRingBuffer_IndexOfBuffer(&ring, search_pattern, 3);
|
||||
|
||||
assert(match_offset == 3); // 0x44 sits exactly at relative offset position 3
|
||||
|
||||
// Application Workflow Integration: Cleanly discard up to token match prefix point
|
||||
c_FastByteRingBuffer_Discard(&ring, (c_size_t)match_offset);
|
||||
uint8_t current_head_byte = 0;
|
||||
c_FastByteRingBuffer_PeekByte(&ring, ¤t_head_byte);
|
||||
assert(current_head_byte == 0x44); // The buffer head has been successfully synchronized to the token location
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_reverse_search_and_token_stream(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8
|
||||
|
||||
uint8_t raw_payload[] = {0xAA, 0x11, 0x22, 0xBB, 0x11, 0x22, 0xCC, 0xDD};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, raw_payload, 8);
|
||||
|
||||
uint8_t pattern[] = {0x11, 0x22};
|
||||
|
||||
// 1. Verify Reverse Pattern Detection Matches Latest Occurrence
|
||||
assert(c_FastByteRingBuffer_IndexOfBuffer(&ring, pattern, 2) == 1); // First pair starts at offset 1
|
||||
assert(c_FastByteRingBuffer_LastIndexOfBuffer(&ring, pattern, 2) == 4); // Latest pair starts at offset 4
|
||||
|
||||
// 2. Clear out buffer to run frame serialization test
|
||||
c_FastByteRingBuffer_Discard(&ring, 8);
|
||||
|
||||
uint8_t stream_data[] = {'P', 'a', 'c', 'k', 'e', 't', '\r', '\n'};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, stream_data, 8);
|
||||
|
||||
uint8_t frame_terminator[] = {'\r', '\n'};
|
||||
uint8_t output_staging[16] = {0};
|
||||
|
||||
// Fail Case: Pass a staging array that is structurally too cramped to hold the payload safely
|
||||
assert(c_FastByteRingBuffer_ReadUntilToken(&ring, frame_terminator, 2, output_staging, 5) == 0);
|
||||
assert(c_FastByteRingBuffer_GetSize(&ring) == 8); // Data remains locked safely inside the ring
|
||||
|
||||
// Success Case: Pass a valid container size to execute frame extraction
|
||||
c_size_t read_bytes = c_FastByteRingBuffer_ReadUntilToken(&ring, frame_terminator, 2, output_staging, 16);
|
||||
assert(read_bytes == 8);
|
||||
assert(memcmp(output_staging, "Packet\r\n", 8) == 0);
|
||||
assert(c_FastByteRingBuffer_IsEmpty(&ring) == C_TRUE); // Frame has been cleanly consumed from the queue
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_relative_random_access(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 4) == C_SUCCESS); // Capacity = 4
|
||||
|
||||
uint8_t seed_data[] = {0x10, 0x20, 0x30};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, seed_data, 3);
|
||||
|
||||
// Shift the head pointer downstream via a single byte consumption
|
||||
uint8_t discard_sink = 0;
|
||||
c_FastByteRingBuffer_ReadByte(&ring, &discard_sink); // 0x10 dropped. Head points to 0x20
|
||||
|
||||
// Append items to cross physical wrapping thresholds cleanly
|
||||
c_FastByteRingBuffer_WriteByte(&ring, 0x40);
|
||||
c_FastByteRingBuffer_WriteByte(&ring, 0x50); // Buffer contents: [0x20, 0x30, 0x40, 0x50]
|
||||
|
||||
uint8_t extracted_byte = 0;
|
||||
|
||||
// 1. Verify standard random read coordinates relative to the head position
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 0, &extracted_byte) == C_SUCCESS);
|
||||
assert(extracted_byte == 0x20); // Relative 0 points directly to the current head
|
||||
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 2, &extracted_byte) == C_SUCCESS);
|
||||
assert(extracted_byte == 0x40); // Wrapped index check
|
||||
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 3, &extracted_byte) == C_SUCCESS);
|
||||
assert(extracted_byte == 0x50); // Newest unread byte check
|
||||
|
||||
// 2. Validate bounds-checking flags
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 4, &extracted_byte) == C_ERR_OUT_OF_BOUNDS);
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(&ring, 99, &extracted_byte) == C_ERR_OUT_OF_BOUNDS);
|
||||
assert(c_FastByteRingBuffer_GetAtRelative(NULL, 0, &extracted_byte) == C_ERR_INVALID_PARAM);
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_conditional_is_validator(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 4) == C_SUCCESS);
|
||||
|
||||
uint8_t input_stream[] = {0xAA, 0xBB, 0xCC};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, input_stream, 3);
|
||||
|
||||
// 1. Validate clean true/false matches relative to the head
|
||||
assert(c_FastByteRingBuffer_Is(&ring, 0, 0xAA) == C_TRUE); // Oldest byte at head matches
|
||||
assert(c_FastByteRingBuffer_Is(&ring, 1, 0xBB) == C_TRUE); // Next element matches
|
||||
assert(c_FastByteRingBuffer_Is(&ring, 1, 0x99) == C_FALSE); // Mismatch returns false
|
||||
|
||||
// Consume 1 byte to advance the head pointer and test wrapping boundaries
|
||||
uint8_t sink = 0;
|
||||
c_FastByteRingBuffer_ReadByte(&ring, &sink); // Head now points to 0xBB
|
||||
c_FastByteRingBuffer_WriteByte(&ring, 0xDD); // Layout: [..., 0xBB, 0xCC, 0xDD]
|
||||
|
||||
// 2. Re-verify offsets after structural pointer wrap shifts
|
||||
assert(c_FastByteRingBuffer_Is(&ring, 0, 0xBB) == C_TRUE); // Head index position 0 is now 0xBB
|
||||
assert(c_FastByteRingBuffer_Is(&ring, 2, 0xDD) == C_TRUE); // Wrapped index element match
|
||||
|
||||
// 3. Confirm error/bounds conditions return false instead of blowing up memory layout boundaries
|
||||
assert(c_FastByteRingBuffer_Is(&ring, 3, 0x00) == C_FALSE); // Out of bounds index
|
||||
assert(c_FastByteRingBuffer_Is(&ring, -5, 0xBB) == C_FALSE); // Negative index handling protection
|
||||
assert(c_FastByteRingBuffer_Is(NULL, 0, 0xBB) == C_FALSE); // NULL safety check
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
static void test_ring_buffer_memcmp(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 6) == C_SUCCESS); // Capacity = 6
|
||||
|
||||
uint8_t payload[] = {0x00, 0x11, 0x22, 0x33};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, payload, 4);
|
||||
|
||||
// Consume 2 bytes to step the head pointer forward to absolute index 2
|
||||
uint8_t sink = 0;
|
||||
c_FastByteRingBuffer_ReadByte(&ring, &sink);
|
||||
c_FastByteRingBuffer_ReadByte(&ring, &sink); // Buffer active layout from head: [0x22, 0x33]
|
||||
|
||||
// Append data to trigger an explicit physical wrap-around edge split layout
|
||||
uint8_t wrap_payload[] = {0x44, 0x55, 0x66};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, wrap_payload, 3);
|
||||
// Buffer dynamic content path tracking from head: [0x22, 0x33, 0x44, 0x55, 0x66]
|
||||
// Physical layout behind indices inside array: [0x55, 0x66, 0x22, 0x33, 0x44, ...]
|
||||
|
||||
// 1. Validate contiguous segment match comparisons
|
||||
uint8_t check_a[] = {0x22, 0x33};
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 0, check_a, 2) == 0); // Perfect contiguous match
|
||||
|
||||
// 2. Validate multi-segment wrap-around comparison mechanics
|
||||
uint8_t check_b[] = {0x33, 0x44, 0x55, 0x66};
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 1, check_b, 4) == 0); // Perfect split wrap-around match
|
||||
|
||||
// 3. Mismatch checks
|
||||
uint8_t check_mismatch[] = {0x33, 0x44, 0x99, 0x66};
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 1, check_mismatch, 4) != 0); // Identifies internal divergence
|
||||
|
||||
// 4. Bounds and parameter checks
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 0, check_b, 100) == C_ERR_OUT_OF_BOUNDS); // Request width overflows content
|
||||
assert(c_FastByteRingBuffer_Memcmp(&ring, 99, check_b, 1) == C_ERR_OUT_OF_BOUNDS); // Start pointer invalid
|
||||
assert(c_FastByteRingBuffer_Memcmp(NULL, 0, check_b, 1) == C_ERR_INVALID_PARAM);
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
static void test_ring_buffer_strtoul(void) {
|
||||
c_FastByteRingBuffer_t ring;
|
||||
assert(c_FastByteRingBuffer_Init(&ring, 8) == C_SUCCESS); // Capacity = 8
|
||||
|
||||
// 1. Standard Linear Base-10 Parsing
|
||||
uint8_t input_a[] = {'1', '2', '3', '4', ' ', 'A', 'B', 'C'};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, input_a, 8);
|
||||
|
||||
unsigned long parsed_val = 0;
|
||||
c_size_t end_offset = 0;
|
||||
|
||||
assert(c_FastByteRingBuffer_Strtoul(&ring, 0, 10, &parsed_val, &end_offset) == C_SUCCESS);
|
||||
assert(parsed_val == 1234);
|
||||
assert(end_offset == 4); // Points exactly to the trailing space character offset
|
||||
|
||||
// Reset buffer tracking lines
|
||||
c_FastByteRingBuffer_Discard(&ring, 8);
|
||||
|
||||
// 2. Fragmented Wrap Hex Parsing
|
||||
// Pre-fill 5 elements to push cursor indices near wrapping layout boundaries
|
||||
uint8_t pre_fill[] = {0, 0, 0, 0, 0};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, pre_fill, 5);
|
||||
c_FastByteRingBuffer_Discard(&ring, 5); // Head pointer sits at physical index 5
|
||||
|
||||
// Write Hex parameter payload string ("0x2F") across memory boundaries
|
||||
uint8_t input_hex[] = {'0', 'x', '2', 'F'};
|
||||
c_FastByteRingBuffer_WriteBuffer(&ring, input_hex, 4);
|
||||
|
||||
assert(c_FastByteRingBuffer_Strtoul(&ring, 0, 16, &parsed_val, &end_offset) == C_SUCCESS);
|
||||
assert(parsed_val == 47); // 0x2F translates to decimal 47
|
||||
assert(end_offset == 4);
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&ring);
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
printf("==================================================\n");
|
||||
printf(" 開始執行 c_FastByteRingBuffer 最終最佳化版測試\n");
|
||||
printf(" Starting c_FastByteRingBuffer Unit Testing Suite\n");
|
||||
printf("==================================================\n\n");
|
||||
|
||||
c_FastByteRingBuffer_t q;
|
||||
// 故意傳入 6,測試是否會自動向上對齊到 8 (2的3次方)
|
||||
c_err_t err = c_FastByteRingBuffer_Init(&q, 6);
|
||||
assert(err == C_ERR_SUCCESS);
|
||||
|
||||
// 核心斷言:容量必須被修正為 8,遮罩必須為 7 (二進位 0111)
|
||||
assert(q.capacity == 8);
|
||||
assert(q.mask == 7);
|
||||
assert(c_FastByteRingBuffer_IsEmpty(&q) == C_TRUE);
|
||||
test_log("1. 2的冪次方自動容量對齊與遮罩初始化成功");
|
||||
|
||||
// ==========================================
|
||||
// 2. 測試單一 Byte 寫入與讀取
|
||||
// ==========================================
|
||||
err = c_FastByteRingBuffer_WriteByte(&q, 0x11); assert(err == C_ERR_SUCCESS);
|
||||
err = c_FastByteRingBuffer_WriteByte(&q, 0x22); assert(err == C_ERR_SUCCESS);
|
||||
assert(c_FastByteRingBuffer_GetSize(&q) == 2);
|
||||
|
||||
uint8_t out = 0;
|
||||
err = c_FastByteRingBuffer_ReadByte(&q, &out);
|
||||
assert(err == C_ERR_SUCCESS);
|
||||
assert(out == 0x11);
|
||||
assert(c_FastByteRingBuffer_GetSize(&q) == 1);
|
||||
test_log("2. 單一 Byte 讀寫與位元環形遞增驗證成功");
|
||||
|
||||
// 清空
|
||||
c_FastByteRingBuffer_ReadByte(&q, &out);
|
||||
assert(c_FastByteRingBuffer_IsEmpty(&q) == C_TRUE);
|
||||
|
||||
// ==========================================
|
||||
// 3. 測試大量區塊資料串流與滿載邊界
|
||||
// ==========================================
|
||||
uint8_t src_stream[10] = {0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA};
|
||||
|
||||
// 目前容量為 8。傳入長度 10 的陣列,預期只能成功寫入 8 個位元組
|
||||
c_size_t written = c_FastByteRingBuffer_WriteBuffer(&q, src_stream, 10);
|
||||
assert(written == 8);
|
||||
assert(c_FastByteRingBuffer_IsFull(&q) == C_TRUE);
|
||||
|
||||
// 溢位防呆檢查
|
||||
assert(c_FastByteRingBuffer_WriteByte(&q, 0xFF) == C_ERR_FULL);
|
||||
|
||||
// 大量讀出驗證
|
||||
uint8_t dest_stream[8] = {0};
|
||||
c_size_t read = c_FastByteRingBuffer_ReadBuffer(&q, dest_stream, 8);
|
||||
assert(read == 8);
|
||||
assert(dest_stream[0] == 0xA1);
|
||||
assert(dest_stream[7] == 0xA8);
|
||||
assert(c_FastByteRingBuffer_IsEmpty(&q) == C_TRUE);
|
||||
test_log("3. 區塊串流 API 與滿載位元遮罩邊界防呆成功");
|
||||
|
||||
c_FastByteRingBuffer_Destroy(&q);
|
||||
test_log("4. 資源安全銷毀成功");
|
||||
RUN_TEST_CASE(test_ring_buffer_behavior);
|
||||
RUN_TEST_CASE(test_overwrite_and_peek_mechanics);
|
||||
RUN_TEST_CASE(test_dma_and_discard_mechanics);
|
||||
RUN_TEST_CASE(test_ring_buffer_index_searching);
|
||||
RUN_TEST_CASE(test_reverse_search_and_token_stream);
|
||||
RUN_TEST_CASE(test_relative_random_access);
|
||||
RUN_TEST_CASE(test_conditional_is_validator);
|
||||
RUN_TEST_CASE(test_ring_buffer_memcmp);
|
||||
RUN_TEST_CASE(test_ring_buffer_strtoul);
|
||||
|
||||
printf("\n==================================================\n");
|
||||
printf(" 恭喜!快取位元最佳化版 RingBuffer 所有單元測試順利通過!\n");
|
||||
printf(" Success! Byte RingBuffer tests passed!\n");
|
||||
printf("==================================================\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user