add generic, efficient, log style ring buffer based linked list implementation
This commit is contained in:
parent
46710c32b8
commit
06b882adaf
|
@ -0,0 +1,202 @@
|
|||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "objectlog.h"
|
||||
|
||||
#define MAX_FRAGMENT_LEN 0x7f
|
||||
#define FRAGMENT_FINAL 0x80
|
||||
#define FRAGMENT_LEN(x) ((x) & MAX_FRAGMENT_LEN)
|
||||
|
||||
#define DIV_ROUND_UP(x, y) (((x) + ((y) - 1)) / (y))
|
||||
|
||||
static void write_fragment(objectlog_t *log, uint8_t hdr, const void *data, uint16_t len) {
|
||||
ringbuffer_write_one(&log->ring, hdr);
|
||||
ringbuffer_write(&log->ring, data, len);
|
||||
}
|
||||
|
||||
static uint16_t get_next_entry(objectlog_t *log, uint16_t offset) {
|
||||
uint8_t fragment_hdr;
|
||||
|
||||
log->ring.read_ptr = offset;
|
||||
do {
|
||||
uint16_t hdr_pos = log->ring.read_ptr;
|
||||
|
||||
fragment_hdr = ringbuffer_read_one(&log->ring);
|
||||
ringbuffer_advance_read(&log->ring, FRAGMENT_LEN(fragment_hdr));
|
||||
/*
|
||||
* There might be no terminating entry in the list. Detect
|
||||
* whether we have wrapped across @offset and terminate if
|
||||
* we did
|
||||
*/
|
||||
if ((hdr_pos < offset && log->ring.read_ptr >= offset) ||
|
||||
(hdr_pos > offset && log->ring.read_ptr >= offset &&
|
||||
log->ring.read_ptr < hdr_pos)) {
|
||||
return log->ptr_first;
|
||||
}
|
||||
} while (!(fragment_hdr & FRAGMENT_FINAL));
|
||||
|
||||
return log->ring.read_ptr;
|
||||
}
|
||||
|
||||
static uint16_t objectlog_space_between(objectlog_t *log, uint16_t first, uint16_t second) {
|
||||
if (second >= first) {
|
||||
return second - first;
|
||||
} else {
|
||||
return log->ring.size - first + second;
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t drop_first_entry(objectlog_t *log) {
|
||||
log->ptr_first = get_next_entry(log, log->ptr_first);
|
||||
log->num_entries--;
|
||||
return log->ptr_first;
|
||||
}
|
||||
|
||||
static uint16_t objectlog_free_space(objectlog_t *log, uint16_t from) {
|
||||
return objectlog_space_between(log, from, log->ptr_first);
|
||||
}
|
||||
|
||||
static void objectlog_add_fragment(objectlog_t *log, const void *data, uint16_t len, bool final) {
|
||||
uint8_t hdr = FRAGMENT_LEN(len);
|
||||
|
||||
if (final) {
|
||||
hdr |= FRAGMENT_FINAL;
|
||||
}
|
||||
write_fragment(log, hdr, data, len);
|
||||
}
|
||||
|
||||
void objectlog_init(objectlog_t *log, void *storage, uint16_t size) {
|
||||
ringbuffer_init(&log->ring, storage, size);
|
||||
log->ptr_first = 0;
|
||||
log->ptr_last = 0;
|
||||
log->num_entries = 0;
|
||||
}
|
||||
|
||||
void objectlog_write_object(objectlog_t *log, const void *data, uint16_t len) {
|
||||
const uint8_t *data8 = data;
|
||||
uint16_t num_fragments;
|
||||
uint16_t total_len;
|
||||
uint16_t new_last;
|
||||
uint16_t free_space;
|
||||
uint16_t log_end = get_next_entry(log, log->ptr_last);
|
||||
|
||||
/* Calculate number of fragments required to store data */
|
||||
num_fragments = DIV_ROUND_UP(len, MAX_FRAGMENT_LEN);
|
||||
total_len = len + num_fragments;
|
||||
/* Special case: If we wrap we need an extra header! */
|
||||
if (log_end + total_len > log->ring.size) {
|
||||
total_len++;
|
||||
}
|
||||
/* We can not store any messages exceeding size of this buffer */
|
||||
if (total_len > log->ring.size) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get number of bytes not in use at the moment */
|
||||
free_space = objectlog_free_space(log, log_end);
|
||||
/* Delete entries from start of list until object fits */
|
||||
while (free_space < total_len && log->ptr_first != log->ptr_last) {
|
||||
drop_first_entry(log);
|
||||
free_space = objectlog_free_space(log, log_end);
|
||||
}
|
||||
|
||||
/* Store start of object header */
|
||||
new_last = log->ring.write_ptr;
|
||||
/* Write object as @num_fragments fragments */
|
||||
while(len) {
|
||||
uint16_t fragment_len = len;
|
||||
/* Limit fragment size to value representable in 7 bits */
|
||||
if (fragment_len > MAX_FRAGMENT_LEN) {
|
||||
fragment_len = MAX_FRAGMENT_LEN;
|
||||
}
|
||||
/* Don't create fragments wrapping around the end of ring buffer */
|
||||
if (fragment_len + 1 > log->ring.size - log->ring.write_ptr) {
|
||||
fragment_len = log->ring.size - log->ring.write_ptr - 1;
|
||||
}
|
||||
objectlog_add_fragment(log, data8, fragment_len, fragment_len == len);
|
||||
len -= fragment_len;
|
||||
data8 += fragment_len;
|
||||
}
|
||||
log->ptr_last = new_last;
|
||||
log->num_entries++;
|
||||
}
|
||||
|
||||
void objectlog_write_string(objectlog_t *log, const char *str) {
|
||||
objectlog_write_object(log, str, strlen(str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain interator for string at index @string_idx
|
||||
* Negative indices count from last to first string.
|
||||
*
|
||||
* @returns: non-negative iterator value on success, -1 on failure
|
||||
*/
|
||||
objectlog_iterator_t objectlog_iterator(objectlog_t *log, int string_idx) {
|
||||
uint16_t string_ptr = log->ptr_first;
|
||||
|
||||
if (string_idx < 0) {
|
||||
string_idx = -string_idx;
|
||||
if (string_idx >= log->num_entries) {
|
||||
return -1;
|
||||
}
|
||||
string_idx = log->num_entries - string_idx;
|
||||
} else {
|
||||
if (string_idx >= log->num_entries) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
while (string_idx--) {
|
||||
string_ptr = get_next_entry(log, string_ptr);
|
||||
}
|
||||
|
||||
return string_ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current iteration fragment
|
||||
*
|
||||
* @returns: non-NULL pointer to data on success, NULL on failure
|
||||
*/
|
||||
const void *objectlog_get_fragment(objectlog_t *log, objectlog_iterator_t iterator, uint8_t *len) {
|
||||
if (iterator < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (iterator > log->ring.size) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*len = FRAGMENT_LEN(log->ring.buffer[iterator]);
|
||||
return &log->ring.buffer[iterator + 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance iterator to next fragment
|
||||
*
|
||||
* @returns: -2 if there are no more fragments or
|
||||
* non-negative iterator value of next fragment or
|
||||
* -1 on failure
|
||||
*/
|
||||
objectlog_iterator_t objectlog_next(objectlog_t *log, objectlog_iterator_t iterator) {
|
||||
uint16_t len;
|
||||
|
||||
if (iterator < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (iterator > log->ring.size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
len = FRAGMENT_LEN(log->ring.buffer[iterator]);
|
||||
if (log->ring.buffer[iterator] & FRAGMENT_FINAL) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
iterator++;
|
||||
iterator += len;
|
||||
iterator %= log->ring.size;
|
||||
return iterator;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ringbuffer.h"
|
||||
|
||||
typedef struct {
|
||||
ringbuffer_t ring;
|
||||
uint16_t ptr_first;
|
||||
uint16_t ptr_last;
|
||||
unsigned int num_entries;
|
||||
} objectlog_t;
|
||||
|
||||
typedef long objectlog_iterator_t;
|
||||
|
||||
void objectlog_init(objectlog_t *log, void *storage, uint16_t size);
|
||||
void objectlog_write_object(objectlog_t *log, const void *data, uint16_t len);
|
||||
void objectlog_write_string(objectlog_t *log, const char *str);
|
||||
objectlog_iterator_t objectlog_iterator(objectlog_t *log, int string_idx);
|
||||
const void *objectlog_get_fragment(objectlog_t *log, objectlog_iterator_t iterator, uint8_t *len);
|
||||
objectlog_iterator_t objectlog_next(objectlog_t *log, objectlog_iterator_t iterator);
|
Loading…
Reference in New Issue