Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

31
src/libutil/build_id.c Normal file
View file

@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/build_id.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
bool build_id_contains_gnu_build_id(const ElfExternalNote *note) {
const uint32_t NT_GNU_BUILD_ID = 3;
const char *expected_name = "GNU";
const uint32_t expected_name_length = strlen(expected_name) + 1;
return note->type == NT_GNU_BUILD_ID &&
note->name_length == expected_name_length &&
note->data_length == BUILD_ID_EXPECTED_LEN &&
(strncmp((const char *) note->data, expected_name, expected_name_length) == 0);
}

View file

@ -0,0 +1,186 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/circular_buffer.h"
#include "util/assert.h"
#include "util/math.h"
#include <string.h>
static uint16_t get_write_length_available(CircularBuffer* buffer) {
return buffer->buffer_size - buffer->data_length;
}
static uint16_t get_write_index(CircularBuffer* buffer) {
return (buffer->read_index + buffer->data_length) % buffer->buffer_size;
}
void circular_buffer_init(CircularBuffer* buffer, uint8_t* storage, uint16_t storage_size) {
buffer->buffer = storage;
buffer->buffer_size = storage_size;
buffer->read_index = 0;
buffer->data_length = 0;
buffer->write_in_progress = false;
buffer->auto_reset = true;
}
void circular_buffer_init_ex(CircularBuffer* buffer, uint8_t* storage, uint16_t storage_size,
bool auto_reset) {
circular_buffer_init(buffer, storage, storage_size);
buffer->auto_reset = auto_reset;
}
bool circular_buffer_write(CircularBuffer* buffer, const void* data, uint16_t length) {
if (get_write_length_available(buffer) < length) {
return false;
}
// Now we know the message will fit, so no more checking against buffer_read_index required.
uint16_t write_index = get_write_index(buffer);
// Update the data_length member now beforce we muck with the length parameter
buffer->data_length += length;
const uint16_t remaining_length = buffer->buffer_size - write_index;
if (remaining_length < length) {
// Need to write the message in two chunks around the end of the buffer. Write the first chunk
memcpy(&buffer->buffer[write_index], data, remaining_length);
write_index = 0;
data = ((uint8_t*) data) + remaining_length;
length -= remaining_length;
}
// Write the last chunk
memcpy(&buffer->buffer[write_index], data, length);
return true;
}
uint16_t circular_buffer_write_prepare(CircularBuffer *buffer, uint8_t **data_out) {
if (!get_write_length_available(buffer) || buffer->write_in_progress) {
*data_out = NULL;
return 0;
}
buffer->write_in_progress = true;
const uint16_t write_index = get_write_index(buffer);
*data_out = buffer->buffer + write_index;
if (buffer->read_index > write_index) {
return buffer->read_index - write_index;
}
return buffer->buffer_size - write_index;
}
void circular_buffer_write_finish(CircularBuffer *buffer, uint16_t written_length) {
UTIL_ASSERT(buffer->data_length + written_length <= buffer->buffer_size);
buffer->data_length += written_length;
buffer->write_in_progress = false;
}
bool circular_buffer_read(const CircularBuffer* buffer, uint16_t length, const uint8_t** data_out, uint16_t* length_out) {
if (buffer->data_length < length) {
return false;
}
*data_out = &buffer->buffer[buffer->read_index];
const uint16_t remaining_length = buffer->buffer_size - buffer->read_index;
*length_out = MIN(remaining_length, length);
return true;
}
uint16_t circular_buffer_copy(const CircularBuffer* buffer,
void *data_out,
uint16_t length_to_copy) {
return circular_buffer_copy_offset(buffer, 0, data_out, length_to_copy);
}
uint16_t circular_buffer_copy_offset(const CircularBuffer* buffer, uint16_t start_offset,
uint8_t *data_out, uint16_t length_to_copy) {
if (buffer->data_length <= start_offset) {
return 0;
}
const uint16_t read_index = (buffer->read_index + start_offset) % buffer->buffer_size;
const uint16_t data_length = buffer->data_length - start_offset;
length_to_copy = MIN(length_to_copy, data_length);
// Number of total bytes after the read index to end of buffer:
const uint16_t total_length_to_end = buffer->buffer_size - read_index;
// Number of bytes of after the read index that need to be copied:
const uint16_t end_copy_length = MIN(total_length_to_end, length_to_copy);
memcpy(data_out, &buffer->buffer[read_index], end_copy_length);
// If length_to_copy > total_length_to_end, there is more data at the
// beginning of the circular buffer (wrapped around):
const uint16_t wrapped_length = length_to_copy - end_copy_length;
if (wrapped_length) {
memcpy(data_out + total_length_to_end, buffer->buffer, wrapped_length);
}
return length_to_copy;
}
bool circular_buffer_read_or_copy(const CircularBuffer* buffer, uint8_t **data_out, size_t length,
void *(*malloc_imp)(size_t), bool *caller_should_free) {
UTIL_ASSERT(buffer && malloc_imp && data_out && caller_should_free);
if (buffer->data_length < length) {
return false;
}
const uint16_t continguous_length = (buffer->buffer_size - buffer->read_index);
const bool should_malloc_and_copy = (length > continguous_length);
*caller_should_free = should_malloc_and_copy;
if (should_malloc_and_copy) {
*data_out = (uint8_t *) malloc_imp(length);
if (*data_out) {
circular_buffer_copy(buffer, *data_out, length);
} else {
*caller_should_free = false;
return false;
}
} else {
uint16_t length_out;
circular_buffer_read(buffer, length, (const uint8_t **)data_out, &length_out);
}
return true;
}
bool circular_buffer_consume(CircularBuffer* buffer, uint16_t length) {
if (buffer->data_length < length) {
return false;
}
buffer->read_index = (buffer->read_index + length) % buffer->buffer_size;
buffer->data_length -= length;
// Reset read_index if there's no more data, so any newly written data won't wrap
if (buffer->auto_reset && buffer->data_length == 0 && !buffer->write_in_progress) {
buffer->read_index = 0;
}
return true;
}
uint16_t circular_buffer_get_write_space_remaining(const CircularBuffer* buffer) {
return buffer->buffer_size - buffer->data_length;
}
uint16_t circular_buffer_get_read_space_remaining(const CircularBuffer* buffer) {
return buffer->data_length;
}

View file

@ -0,0 +1,93 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/circular_cache.h"
#include "util/assert.h"
#include "util/math.h"
#include <string.h>
void circular_cache_init(CircularCache* c, uint8_t* buffer, size_t item_size,
int total_items, Comparator compare_cb) {
UTIL_ASSERT(c);
UTIL_ASSERT(buffer);
UTIL_ASSERT(item_size);
UTIL_ASSERT(compare_cb);
*c = (CircularCache) {
.cache = buffer,
.next_erased_item_idx = 0,
.item_size = item_size,
.total_items = total_items,
.compare_cb = compare_cb
};
}
void circular_cache_set_item_destructor(CircularCache *c, CircularCacheItemDestructor destructor) {
c->item_destructor = destructor;
}
static uint8_t* prv_get_item_at_index(CircularCache* c, int index) {
return c->cache + index * c->item_size;
}
bool circular_cache_contains(CircularCache* c, void* theirs) {
return (circular_cache_get(c, theirs) != NULL);
}
void *circular_cache_get(CircularCache* c, void* theirs) {
for (int i = 0; i < c->total_items; ++i) {
uint8_t* ours = prv_get_item_at_index(c, i);
if (c->compare_cb(ours, theirs) == 0) {
return ours;
}
}
return NULL;
}
void circular_cache_push(CircularCache* c, void* new_item) {
uint8_t* old_item = prv_get_item_at_index(c, c->next_erased_item_idx);
if (c->item_destructor) {
c->item_destructor(old_item);
}
memcpy(old_item, new_item, c->item_size);
++c->next_erased_item_idx;
UTIL_ASSERT(c->next_erased_item_idx <= c->total_items);
if (c->next_erased_item_idx == c->total_items) {
c->next_erased_item_idx = 0;
}
}
void circular_cache_fill(CircularCache *c, uint8_t *item) {
// If you need item_destructor and fill, add an index pointing to the oldest item for destruction
UTIL_ASSERT(c->item_destructor == NULL);
for (int i = 0; i < c->total_items; ++i) {
memcpy(c->cache + i * c->item_size, item, c->item_size);
}
}
void circular_cache_flush(CircularCache *c) {
// This assumes that the user of this library can differentiate empty entries in the cache from
// valid ones.
for (int i = 0; i < c->total_items; i++) {
uint8_t* item = prv_get_item_at_index(c, i);
if (c->item_destructor) {
c->item_destructor(item);
}
}
c->next_erased_item_idx = 0;
}

51
src/libutil/crc32.c Normal file
View file

@ -0,0 +1,51 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/crc32.h"
#include <stdint.h>
#include <string.h>
// Nybble-wide table driven CRC-32 algorithm
//
// A compromise between speed and size, this algorithm uses a lookup table to
// calculate the CRC four bits at a time with a size cost of only 64 bytes. By
// contrast, a byte-wide algorithm requires a lookup table sixteen times larger!
//
// The lookup table is generated by the crc32_lut.py
static const uint32_t s_lookup_table[] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
};
uint32_t crc32(uint32_t crc, const void * restrict data, size_t length) {
if (data == 0) {
return 0;
}
const uint8_t * restrict bytes = data;
crc ^= 0xffffffff;
while (length--) {
crc = (crc >> 4) ^ s_lookup_table[(crc ^ *bytes) & 0xf];
crc = (crc >> 4) ^ s_lookup_table[(crc ^ (*bytes >> 4)) & 0xf];
bytes++;
}
crc ^= 0xffffffff;
return crc;
}

37
src/libutil/hash.c Normal file
View file

@ -0,0 +1,37 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/hash.h"
#include <stdint.h>
// Based on DJB2 Hash
uint32_t hash(const uint8_t *bytes, const uint32_t length) {
uint32_t hash = 5381;
if (length == 0) {
return hash;
}
uint8_t c;
const uint8_t *last_byte = bytes + length;
while (bytes != last_byte) {
c = *bytes;
hash = ((hash << 5) + hash) + c;
bytes++;
}
return hash;
}

634
src/libutil/heap.c Normal file
View file

@ -0,0 +1,634 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/heap.h"
#include "util/assert.h"
#include "util/math.h"
#include "util/logging.h"
#include <inttypes.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
/* The following defines a type that is the size in bytes of the */
/* desired alignment of each datr fragment. */
typedef unsigned long Alignment_t;
/* The following defines the byte boundary size that has been */
/* specified size if the alignment data. */
#define ALIGNMENT_SIZE sizeof(Alignment_t)
/* The following structure is used to allign data fragments on a */
/* specified memory boundary. */
typedef union _tagAlignmentStruct_t
{
Alignment_t AlignmentValue;
unsigned char ByteValue;
} AlignmentStruct_t;
/* The following defines the size in bytes of a data fragment that is*/
/* considered a large value. Allocations that are equal to and */
/* larger than this value will be allocated from the end of the */
/* buffer. */
#define LARGE_SIZE (256/ALIGNMENT_SIZE)
//! The maximum size of the heap as a number of ALIGNMENT_SIZE
#define SEGMENT_SIZE_MAX (0x7FFF)
/* The following defines the minimum size (in alignment units) of a */
/* fragment that is considered useful. The value is used when trying*/
/* to determine if a fragment that is larger than the requested size */
/* can be broken into 2 framents leaving a fragment that is of the */
/* requested size and one that is at least as larger as the */
/* MINIMUM_MEMORY_SIZE. */
#define MINIMUM_MEMORY_SIZE 1
/* The following defines the structure that describes a memory */
/* fragment. */
typedef struct _tagHeapInfo_t
{
//! Size of the preceding segment, measured in units of ALIGNMENT_SIZE, including the size of this beginer
uint16_t PrevSize;
//! Whether or not this block is currently allocated (vs being free).
bool is_allocated:1;
//! Size of this segment, measured in units of ALIGNMENT_SIZE, including this size of this beginer
uint16_t Size:15;
#ifdef MALLOC_INSTRUMENTATION
uintptr_t pc; //<! The address that called malloc.
#endif
//! This is the actual buffer that's returned to the caller. We use this struct to make
//! sure the buffer we return is appropriately aligned.
AlignmentStruct_t Data;
} HeapInfo_t;
//! The size of a block in units of Alignment_t, including the beginer and including _x words of data.
#define HEAP_INFO_BLOCK_SIZE(_x) ((offsetof(HeapInfo_t, Data) / ALIGNMENT_SIZE) + (_x))
//! Convert a pointer to the Data member to a pointer to the HeapInfo_t beginer
#define HEAP_INFO_FOR_PTR(ptr) (HeapInfo_t *)(((Alignment_t *)ptr) - HEAP_INFO_BLOCK_SIZE(0))
_Static_assert((offsetof(HeapInfo_t, Data) % ALIGNMENT_SIZE) == 0, "Heap not properly aligned.");
//! Heap is assumed corrupt if expr does not evaluate true
#define HEAP_ASSERT_SANE(heap, expr, log_addr) \
if (!(expr)) { prv_handle_corruption(heap, log_addr); }
//! Lock the heap, using whatever behaviour the heap has configured using heap_set_lock_impl
static void heap_lock(Heap* heap) {
if (heap->lock_impl.lock_function) {
heap->lock_impl.lock_function(heap->lock_impl.lock_context);
}
}
//! Unlock the heap, using whatever behaviour the heap has configured using heap_set_lock_impl
static void heap_unlock(Heap* heap) {
if (heap->lock_impl.unlock_function) {
heap->lock_impl.unlock_function(heap->lock_impl.lock_context);
}
// Handle any heap corruption that may have been detected while the heap was locked.
if (heap->corrupt_block != NULL) {
heap->corruption_handler(heap->corrupt_block);
heap->corrupt_block = NULL;
}
}
static void prv_handle_corruption(Heap * const heap, void *ptr) {
if (heap->corruption_handler) {
heap->corrupt_block = ptr;
return;
}
UTIL_ASSERT(0); // Error: Heap corrupt around <ptr>
}
static HeapInfo_t *find_segment(Heap* const heap, unsigned long n_units);
static HeapInfo_t *allocate_block(Heap* const heap, unsigned long n_units, HeapInfo_t* heap_info_ptr);
//! Advance the block pointer to the next block.
static HeapInfo_t* get_next_block(Heap * const heap, HeapInfo_t* block) {
HEAP_ASSERT_SANE(heap, block->Size != 0, block);
return (HeapInfo_t *)(((Alignment_t *)block) + block->Size);
}
//! Move the block pointer back to the previous block.
static HeapInfo_t* get_previous_block(Heap * const heap, HeapInfo_t* block) {
HEAP_ASSERT_SANE(heap, block->PrevSize != 0, block);
return (HeapInfo_t *)(((Alignment_t *)block) - block->PrevSize);
}
static void prv_calc_totals(Heap* const heap, unsigned int *used, unsigned int *free, unsigned int *max_free) {
HeapInfo_t *heap_info_ptr;
uint16_t free_segments;
uint16_t alloc_segments;
/* Initialize the return values. */
*used = 0;
*free = 0;
*max_free = 0;
free_segments = 0;
alloc_segments = 0;
heap_info_ptr = heap->begin;
do {
/* Check to see if the current fragment is marked as free. */
if(heap_info_ptr->is_allocated) {
alloc_segments++;
*used += heap_info_ptr->Size * ALIGNMENT_SIZE;
} else {
free_segments++;
/* Accumulate the total number of free Bytes. */
*free += heap_info_ptr->Size * ALIGNMENT_SIZE;
/* Check to see if the current fragment is larger that any */
/* we have seen and update the Max Value if it is larger. */
if(heap_info_ptr->Size > *max_free) {
*max_free = heap_info_ptr->Size * ALIGNMENT_SIZE;
}
}
/* Adjust the pointer to the next entry. */
heap_info_ptr = get_next_block(heap, heap_info_ptr);
} while (heap_info_ptr < heap->end);
UTIL_ASSERT(heap_info_ptr == heap->end);
char format_str[80];
snprintf(format_str, sizeof(format_str), "alloc: %u (%u bytes), free: %u (%u bytes)",
alloc_segments, *used, free_segments, *free);
util_dbgserial_str(format_str);
}
void heap_calc_totals(Heap* const heap, unsigned int *used, unsigned int *free, unsigned int *max_free) {
UTIL_ASSERT(heap);
/* Verify that the parameters that were passed in appear valid. */
if((used) && (free) && (max_free) && (heap->begin)) {
heap_lock(heap);
prv_calc_totals(heap, used, free, max_free);
heap_unlock(heap);
}
}
void heap_init(Heap* const heap, void* start, void* end, bool fuzz_on_free) {
UTIL_ASSERT(start && end);
// Align the pointer by advancing it to the next boundary.
start = (void*)((((uintptr_t) start) + (sizeof(Alignment_t) - 1)) & ~(sizeof(Alignment_t) - 1));
end = (void*) (((uintptr_t) end) & ~(sizeof(Alignment_t) - 1));
// Calculate the size of the heap in alignment units.
uint32_t heap_size = ((uintptr_t)end - (uintptr_t)start) / ALIGNMENT_SIZE;
// If we have more space than we can use, just limit it to the usable space. This limit is caused
// by the width of .Size and .PrevSize in HeapInfo_t
heap_size = MIN(SEGMENT_SIZE_MAX, heap_size);
end = ((char*) start) + (heap_size * ALIGNMENT_SIZE);
memset(start, 0, heap_size * ALIGNMENT_SIZE);
*heap = (Heap) {
.begin = start,
.end = end,
.fuzz_on_free = fuzz_on_free
};
*(heap->begin) = (HeapInfo_t) {
.PrevSize = heap_size,
.is_allocated = false,
.Size = heap_size
};
}
void heap_set_lock_impl(Heap *heap, HeapLockImpl lock_impl) {
heap->lock_impl = lock_impl;
}
void heap_set_double_free_handler(Heap *heap, DoubleFreeHandler double_free_handler) {
heap->double_free_handler = double_free_handler;
}
void heap_set_corruption_handler(Heap *heap, CorruptionHandler corruption_handler) {
heap->corruption_handler = corruption_handler;
}
void *heap_malloc(Heap* const heap, unsigned long nbytes, uintptr_t client_pc) {
// Check to make sure the heap we have is initialized.
UTIL_ASSERT(heap->begin);
/* Convert the requested memory allocation in bytes to alignment */
/* size. */
unsigned long allocation_size = (nbytes + (ALIGNMENT_SIZE - 1)) / ALIGNMENT_SIZE;
/* Add the beginer size to the requested size. */
allocation_size += HEAP_INFO_BLOCK_SIZE(0);
/* Verify that the requested size is valid */
if ((allocation_size < HEAP_INFO_BLOCK_SIZE(1)) || (allocation_size >= SEGMENT_SIZE_MAX)) {
return NULL;
}
HeapInfo_t* allocated_block;
heap_lock(heap);
{
HeapInfo_t* free_block = find_segment(heap, allocation_size);
allocated_block = allocate_block(heap, allocation_size, free_block);
if (allocated_block != NULL) {
// We've allocated a new block, update our metrics
#ifdef MALLOC_INSTRUMENTATION
allocated_block->pc = client_pc;
#endif
heap->current_size += allocated_block->Size * ALIGNMENT_SIZE;
if (heap->current_size > heap->high_water_mark) {
heap->high_water_mark = heap->current_size;
}
}
}
heap_unlock(heap);
if (allocated_block) {
return &allocated_block->Data;
}
return NULL;
}
void heap_free(Heap* const heap, void *ptr, uintptr_t client_pc) {
UTIL_ASSERT(heap->begin);
if (!ptr) {
// free(0) is a no-op, just bail
return;
}
UTIL_ASSERT(heap_contains_address(heap, ptr)); // <ptr> not in range (heap->begin, heap->end)
heap_lock(heap);
{
/* Get a pointer to the Heap Info. */
HeapInfo_t *heap_info_ptr = HEAP_INFO_FOR_PTR(ptr);
/* Verify that this segment is allocated. */
if (!heap_info_ptr->is_allocated) {
// If not, try to the call the handler if present. If there's no handler, just explode.
// If there is a handler, let them know this happened and then just no-op and fast return.
if (heap->double_free_handler) {
heap_unlock(heap);
heap->double_free_handler(ptr);
return;
}
UTIL_ASSERT(0); // heap_free on invalid pointer <ptr>
}
/* Mask out the allocation bit of the segment to be freed. */
/* This will make calculations in this block easier. */
heap_info_ptr->is_allocated = false;
#ifndef RELEASE
if (heap->fuzz_on_free) {
memset(ptr, 0xBD,
(heap_info_ptr->Size - HEAP_INFO_BLOCK_SIZE(0)) * ALIGNMENT_SIZE);
}
#endif
// Update metrics
#ifdef MALLOC_INSTRUMENTATION
heap_info_ptr->pc = client_pc;
#endif
heap->current_size -= heap_info_ptr->Size * ALIGNMENT_SIZE;
/* If the segment to be freed is at the begin of the heap, */
/* then we do not have to merge or update any sizes of the */
/* previous segment. This will also handle the case where */
/* the entire heap has been allocated to one segment. */
if(heap_info_ptr != heap->begin) {
/* Calculate the pointer to the previous segment. */
HeapInfo_t *previous_block = get_previous_block(heap, heap_info_ptr);
HEAP_ASSERT_SANE(heap, previous_block->Size == heap_info_ptr->PrevSize, heap_info_ptr);
/* Check to see if the previous segment can be combined. */
if(!previous_block->is_allocated) {
/* Add the segment to be freed to the new beginer. */
previous_block->Size += heap_info_ptr->Size;
/* Set the pointer to the beginning of the previous */
/* free segment. */
heap_info_ptr = previous_block;
}
}
/* Calculate the pointer to the next segment. */
HeapInfo_t *next_block = get_next_block(heap, heap_info_ptr);
/* If we are pointing at the end of the heap, then use the */
/* begin as the next segment. */
if(next_block == heap->end) {
/* We can't combine the begin with the end, so just */
/* update the PrevSize field. */
heap->begin->PrevSize = heap_info_ptr->Size;
}
else {
HEAP_ASSERT_SANE(heap, next_block->PrevSize == (HEAP_INFO_FOR_PTR(ptr))->Size, next_block);
/* We are not pointing to the end of the heap, so if the */
/* next segment is allocated then update the PrevSize */
/* field. */
if (next_block->is_allocated) {
next_block->PrevSize = heap_info_ptr->Size;
} else {
/* The next segment is free, so merge it with the */
/* current segment. */
heap_info_ptr->Size += next_block->Size;
/* Since we merged the next segment, we have to update*/
/* the next next segment's PrevSize field. */
HeapInfo_t *next_next_block = get_next_block(heap, heap_info_ptr);
/* If we are pointing at the end of the heap, then use*/
/* the begin as the next next segment. */
if(next_next_block == heap->end) {
heap->begin->PrevSize = heap_info_ptr->Size;
}
else {
next_next_block->PrevSize = heap_info_ptr->Size;
}
}
}
}
heap_unlock(heap);
}
bool heap_is_allocated(Heap* const heap, void* ptr) {
bool rc = false;
if (!heap_contains_address(heap, ptr)) {
return rc;
}
HeapInfo_t *heap_info_ptr = heap->begin;
// Iterate through the heap to see if this pointer is still allocated.
heap_lock(heap);
while (heap_info_ptr < heap->end) {
if (heap_info_ptr == HEAP_INFO_FOR_PTR(ptr)) {
rc = heap_info_ptr->is_allocated;
break;
}
heap_info_ptr = get_next_block(heap, heap_info_ptr);
}
heap_unlock(heap);
return rc;
}
bool heap_contains_address(Heap* const heap, void* ptr) {
return (ptr >= (void*) heap->begin && ptr < (void*) heap->end);
}
size_t heap_size(const Heap *heap) {
return ((char*) heap->end) - ((char*) heap->begin);
}
static void prv_sanity_check_block(Heap * const heap, HeapInfo_t *block) {
HeapInfo_t* prev_block = get_previous_block(heap, block);
HEAP_ASSERT_SANE(heap,
prev_block <= heap->begin || prev_block->Size == block->PrevSize, block);
HeapInfo_t* next_block = get_next_block(heap, block);
HEAP_ASSERT_SANE(heap, next_block >= heap->end || next_block->PrevSize == block->Size, block);
}
//! Finds a segment where data of the size n_units will fit.
//! @param n_units number of ALIGNMENT_SIZE units this segment requires.
static HeapInfo_t *find_segment(Heap* const heap, unsigned long n_units) {
HeapInfo_t *heap_info_ptr = NULL;
/* If we are allocating a large segment, then start at the */
/* end of the heap. Otherwise, start at the beginning of */
/* the heap. If there is only one segment in the heap, then*/
/* heap->begin will be used either way. */
if (n_units >= LARGE_SIZE) {
heap_info_ptr = (HeapInfo_t *)(((Alignment_t *)heap->end) - heap->begin->PrevSize);
} else {
heap_info_ptr = heap->begin;
}
/* Loop until we have walked the entire list. */
while (((n_units < LARGE_SIZE) || (heap_info_ptr > heap->begin)) && (heap_info_ptr < heap->end)) {
prv_sanity_check_block(heap, heap_info_ptr);
/* Check to see if the current entry is free and is large*/
/* enough to hold the data being requested. */
if (heap_info_ptr->is_allocated || (heap_info_ptr->Size < n_units)) {
/* If the requested size is larger than the limit then*/
/* search backwards for an available buffer, else go */
/* forward. This will hopefully help to reduce */
/* fragmentataion problems. */
if (n_units >= LARGE_SIZE) {
heap_info_ptr = get_previous_block(heap, heap_info_ptr);
} else {
heap_info_ptr = get_next_block(heap, heap_info_ptr);
}
} else {
break;
}
}
// make sure the space we found is within the bounds of the heap
UTIL_ASSERT((heap_info_ptr >= heap->begin) &&
(heap_info_ptr <= heap->end));
return heap_info_ptr;
}
//! Split a block into two smaller blocks, returning a pointer to the new second block.
//! The first block will be available at the same location as before, but with a smaller size.
//! Assumes the block is big enough to be split and is unallocated.
//! @param first_part_size the size of the new block, in ALIGNMENT_SIZE units, including the beginer
static HeapInfo_t* split_block(Heap *heap, HeapInfo_t* block, size_t first_part_size) {
HeapInfo_t* second_block = (HeapInfo_t*) (((Alignment_t*) block) + first_part_size);
second_block->PrevSize = first_part_size;
second_block->is_allocated = false;
second_block->Size = block->Size - first_part_size;
block->Size = first_part_size;
/* Calculate the pointer to the next segment. */
HeapInfo_t* next_next_block = get_next_block(heap, second_block);
/* Check for a wrap condition and update the next */
/* segment's PrevSize field. */
if (next_next_block == heap->end) {
heap->begin->PrevSize = second_block->Size;
} else {
next_next_block->PrevSize = second_block->Size;
}
return second_block;
}
//! Allocated the block in the given HeapInfo_t segment.
//! @param n_units number of ALIGNMENT_SIZE units this segment requires (including space for the beginer).
//! @param heap_info_ptr the segment where the block should be allocated.
static HeapInfo_t *allocate_block(Heap* const heap, unsigned long n_units, HeapInfo_t* heap_info_ptr) {
// Make sure we can use all or part of this block for this allocation.
if (heap_info_ptr == heap->end || heap_info_ptr->is_allocated || heap_info_ptr->Size < n_units) {
return NULL;
}
/* Check to see if we need to split this into two */
/* entries. */
/* * NOTE * If there is not enough room to make another */
/* entry then we will not adjust the size of */
/* this entry to match the amount requested. */
if (heap_info_ptr->Size < (n_units + HEAP_INFO_BLOCK_SIZE(MINIMUM_MEMORY_SIZE))) {
// Nope! Just use the whole block.
heap_info_ptr->is_allocated = true;
return heap_info_ptr;
}
/* If this is a large segment allocation, then split */
/* the segment so that the free segment is at the */
/* beginning. */
if (n_units >= LARGE_SIZE) {
HeapInfo_t *second_block = split_block(heap, heap_info_ptr, heap_info_ptr->Size - n_units);
second_block->is_allocated = true;
return second_block;
}
split_block(heap, heap_info_ptr, n_units);
heap_info_ptr->is_allocated = true;
return heap_info_ptr;
}
// A very naive realloc implementation
void* heap_realloc(Heap* const heap, void *ptr, unsigned long nbytes, uintptr_t client_pc) {
#if !defined(MALLOC_INSTRUMENTATION)
client_pc = 0;
#endif
// Get a pointer to the Heap Info.
void *new_ptr = heap_malloc(heap, nbytes, client_pc);
if (new_ptr && ptr) {
// Copy over old data.
const HeapInfo_t *heap_info_ptr = HEAP_INFO_FOR_PTR(ptr);
const uint16_t original_size = heap_info_ptr->Size * ALIGNMENT_SIZE;
memcpy(new_ptr, ptr, MIN(nbytes, original_size));
heap_free(heap, ptr, client_pc);
}
return new_ptr;
}
void* heap_zalloc(Heap* const heap, size_t size, uintptr_t client_pc) {
void *ptr = heap_malloc(heap, size, client_pc);
if (ptr) {
memset(ptr, 0, size);
}
return ptr;
}
void* heap_calloc(Heap* const heap, size_t count, size_t size, uintptr_t client_pc) {
return heap_zalloc(heap, count * size, client_pc);
}
uint32_t heap_get_minimum_headroom(Heap *heap) {
return (heap_size(heap) - heap->high_water_mark);
}
// Serial Commands
///////////////////////////////////////////////////////////
#ifdef MALLOC_INSTRUMENTATION
void heap_dump_malloc_instrumentation_to_dbgserial(Heap *heap) {
char buffer[80];
unsigned long num_free_blocks = 0;
unsigned long num_free_bytes = 0;
unsigned long num_alloc_blocks = 0;
unsigned long num_alloc_bytes = 0;
unsigned long largest_free = 0;
// The output in this function is parsed by tools/parse_dump_malloc.py, so don't change it
// without updating that file as well.
HeapInfo_t* heap_iter = heap->begin;
heap_lock(heap);
void* pc;
char* type;
while (heap_iter < heap->end) {
unsigned long block_size = (long)(heap_iter->Size) * ALIGNMENT_SIZE;
if (heap_iter->is_allocated) {
pc = (void *)heap_iter->pc;
type = "";
num_alloc_blocks++;
num_alloc_bytes += block_size;
} else {
pc = NULL;
type = "FREE";
num_free_blocks++;
num_free_bytes += block_size;
largest_free = MAX(largest_free, block_size);
}
snprintf(buffer, sizeof(buffer), "PC:0x%08lX Addr:0x%08lX Bytes:%-8lu %s",
(long)pc, (long)&heap_iter->Data, block_size, type);
util_dbgserial_str(buffer);
heap_iter = get_next_block(heap, heap_iter);
}
snprintf(buffer, sizeof(buffer), "Heap start %p", heap->begin);
util_dbgserial_str(buffer);
snprintf(buffer, sizeof(buffer), "Heap end %p", heap->end);
util_dbgserial_str(buffer);
snprintf(buffer, sizeof(buffer), "Heap total size %zu", heap_size(heap));
util_dbgserial_str(buffer);
snprintf(buffer, sizeof(buffer), "Heap allocated %u", heap->current_size);
util_dbgserial_str(buffer);
snprintf(buffer, sizeof(buffer), "Heap high water mark %u", heap->high_water_mark);
util_dbgserial_str(buffer);
snprintf(buffer, sizeof(buffer), "Heap free blocks: %lu bytes, %lu blocks", num_free_bytes,
num_free_blocks);
util_dbgserial_str(buffer);
snprintf(buffer, sizeof(buffer), "Heap alloc blocks: %lu bytes, %lu blocks", num_alloc_bytes,
num_alloc_blocks);
util_dbgserial_str(buffer);
snprintf(buffer, sizeof(buffer), "Heap largest free block: %lu", largest_free);
util_dbgserial_str(buffer);
heap_unlock(heap);
}
#endif

90
src/libutil/hexdump.c Normal file
View file

@ -0,0 +1,90 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/hexdump.h"
#include <stdio.h>
#include <string.h>
// offset + gap + 8 hex bytes + gap + 8 hex bytes + gap + 8 ascii bytes
// + mini gap + 8 ascii bytes + null:
#define LINE_BUFFER_LENGTH (4 + 2 + (3 * 8) + 2 + (3 * 8) + 2 + 8 + 1 + 8 + 1)
void hexdump(const char *src_filename, int src_line_number, int level,
const uint8_t *data, size_t length, HexdumpLineCallback write_line_cb) {
char line_buffer[LINE_BUFFER_LENGTH];
unsigned int offset = 0;
while (offset < length) {
unsigned int buffer_offset = 0;
// Print data line offset
snprintf(line_buffer, LINE_BUFFER_LENGTH, "%04x ", offset);
buffer_offset += 6;
// Print the hex bytes
const unsigned int num_line_bytes = ((length - offset) > 16) ? 16 : (length - offset);
for (unsigned int i = 0; i < num_line_bytes; ++i) {
if (i == 8) {
line_buffer[buffer_offset++] = ' ';
}
snprintf(line_buffer + buffer_offset, LINE_BUFFER_LENGTH - buffer_offset, "%02x ",
data[offset + i]);
buffer_offset += 3;
}
// Calculate and apply padding between the hex dump and the ascii dump.
unsigned int required_padding = 2;
if (num_line_bytes < 16) {
// If we're printing a partial line, pad out the rest so the
// ascii lines up.
required_padding += (16 - num_line_bytes) * 3;
if (num_line_bytes <= 8)
{
// Account for the gap between the 8 byte hex blocks.
required_padding += 1;
}
}
memset(line_buffer + buffer_offset, ' ', required_padding);
buffer_offset += required_padding;
// Print the ASCII bytes
for (unsigned int i = 0; i < num_line_bytes; ++i) {
if (i == 8)
{
line_buffer[buffer_offset++] = ' ';
}
char c = data[offset + i];
if (c < 32 || c > 126 || c == '`') // ` is used for log hash string delimiting
{
c = '.';
}
line_buffer[buffer_offset++] = c;
}
// No need to pad after a partial ascii line, since we don't line up
// anything after it.
// Null terminate and print.
line_buffer[buffer_offset] = 0;
write_line_cb(level, src_filename, src_line_number, line_buffer);
offset += 16;
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/attributes.h"
#include "util/likely.h"
NORETURN util_assertion_failed(const char *filename, int line);
#define UTIL_ASSERT(expr) \
do { \
if (UNLIKELY(!(expr))) { \
util_assertion_failed(__FILE_NAME__, __LINE__); \
} \
} while (0)

View file

@ -0,0 +1,91 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#if defined(__clang__)
#define GCC_ONLY(x)
#else
#define GCC_ONLY(x) x
#endif
// Function attributes
#define FORMAT_FUNC(TYPE, STR_IDX, FIRST) __attribute__((__format__(TYPE, STR_IDX, FIRST)))
#define FORMAT_PRINTF(STR_IDX, FIRST) FORMAT_FUNC(__printf__, STR_IDX, FIRST)
#define ALWAYS_INLINE __attribute__((__always_inline__)) inline
#define DEPRECATED __attribute__((deprecated))
#define NOINLINE __attribute__((__noinline__))
#define NORETURN __attribute__((__noreturn__)) void
#define NAKED_FUNC __attribute__((__naked__))
#define OPTIMIZE_FUNC(LVL) GCC_ONLY(__attribute__((__optimize__(LVL))))
#define CONST_FUNC __attribute__((__const__))
#define PURE_FUNC __attribute__((__pure__))
// Variable attributes
#define ATTR_CLEANUP(FUNC) __attribute__((__cleanup__(FUNC)))
// Structure attributes
#define PACKED __attribute__((__packed__))
// General attributes
#define USED __attribute__((__used__))
#define UNUSED __attribute__((__unused__))
#define WEAK __attribute__((__weak__))
#define ALIAS(sym) __attribute__((__weak__, __alias__(sym)))
#define EXTERNALLY_VISIBLE GCC_ONLY(__attribute__((__externally_visible__)))
#define ALIGN(bytes) __attribute__((__aligned__(bytes)))
// Unit tests break if variables go in custom sections
#if !UNITTEST
# define SECTION(SEC) __attribute__((__section__(SEC)))
#else
# define SECTION(SEC)
#endif
// Only present on STM32F7
#define DTCM_BSS SECTION(".dtcm_bss")
// DMA_BSS: Section attribute for DMA buffers
#if MICRO_FAMILY_STM32F7
# define DMA_BSS DTCM_BSS
// There is an erratum present in STM32F7xx which causes DMA reads from DTCM
// (but not writes to DTCM) to be corrupted if the MCU enters sleep mode during
// the transfer. Source DMA buffers must be placed in SRAM on these platforms.
// The DMA driver enforces this. Also, alignment to the start of a cache line
// seems to be required, though it's unclear why.
# define DMA_READ_BSS ALIGN(32)
#else
# define DMA_BSS
# define DMA_READ_BSS
#endif
// Use this macro to allow overriding of private functions in order to test them within unit tests.
#if !UNITTEST
# define T_STATIC static
#else
# define T_STATIC WEAK
#endif
// Use this macro to allow overriding of non-static (i.e. global) functions in order to test them
// within unit tests. For lack of a better name, we have named this a MOCKABLE (i.e. can be
// mocked or overridden in unit tests but not in normal firmware)
#if !UNITTEST
# define MOCKABLE
#else
# define MOCKABLE WEAK
#endif

View file

@ -0,0 +1,41 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "util/attributes.h"
//! The linker inserts the build id as an "elf external note" structure:
typedef struct PACKED {
uint32_t name_length;
uint32_t data_length;
uint32_t type; // NT_GNU_BUILD_ID = 3
uint8_t data[]; // concatenated name ('GNU') + data (build id)
} ElfExternalNote;
// the build id is a unique identification for the built files. The default algo uses SHA1
// to produce a 160 bit (20 byte sequence)
#define BUILD_ID_EXPECTED_LEN (20)
#define BUILD_ID_NAME_EXPECTED_LEN (4)
#define BUILD_ID_TOTAL_EXPECTED_LEN (sizeof(ElfExternalNote) + \
BUILD_ID_NAME_EXPECTED_LEN + BUILD_ID_EXPECTED_LEN)
bool build_id_contains_gnu_build_id(const ElfExternalNote *note);

View file

@ -0,0 +1,124 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//! The circular buffer is a contiguous buffer of a fixed length where data is written and consumed
//! in a circular fashion. When the write ends up writing past the end of the buffer, it wraps
//! around to use the first part of the buffer again, assuming that someone else has consumed some
//! data to free it up.
typedef struct CircularBuffer {
uint8_t* buffer;
bool write_in_progress;
bool auto_reset;
uint16_t buffer_size;
uint16_t read_index; //! What offset in buffer that we should read from next.
uint16_t data_length; //! How many bytes after read_index contain valid data.
} CircularBuffer;
void circular_buffer_init(CircularBuffer* buffer, uint8_t* storage, uint16_t storage_size);
//! Extended _init() -- provides access to auto_reset flag.
//! If auto_reset is true (default), on _consume(), read/write pointers will be reset in an attempt
//! to reduce buffer wraps. If false, the buffer will always wrap, leaving the previous data
//! in the buffer. This is handy for post mortem evaluation of debug logs, etc.
void circular_buffer_init_ex(CircularBuffer* buffer, uint8_t* storage, uint16_t storage_size,
bool auto_reset);
//! Copies data from a given buffer and length into the circular buffer.
//! @return false if there's insufficient space.
bool circular_buffer_write(CircularBuffer* buffer, const void* data, uint16_t length);
//! Gets a pointer into the circular buffer where the caller itself can write data to.
//! @note After the client is done writing data, it _must_ call circular_buffer_write_finish()
//! so that the CircularBuffer can update the length of the data it contains and update its internal
//! bookkeeping
//! @param[out] data_out Pointer to storage for the pointer that is set the the start of the
//! writable area when the function returns, or NULL if there is no space available.
//! @return The maximum number of bytes that can be written starting at the returned the pointer.
//! Zero is returned when there is no space available.
uint16_t circular_buffer_write_prepare(CircularBuffer *buffer, uint8_t **data_out);
//! To be used after circular_buffer_write_prepare(), to make the CircularBuffer update the length
//! of the data it contains.
//! @param written_length The length that has just been writted at the pointer provided by
//! circular_buffer_write_prepare().
void circular_buffer_write_finish(CircularBuffer *buffer, uint16_t written_length);
//! Read a contiguous chunk of memory from the circular buffer. The data remains on the buffer until
//! circular_buffer_consume is called.
//!
//! If the circular buffer wraps in the middle of the requested data, this function call will return true but will
//! provide fewer bytes that requested. When this happens, the length_out parameter will be set to a value smaller
//! than length. A second read call can be made with the remaining smaller length to retreive the rest.
//!
//! The reason this read doesn't consume is to avoid having to copy out the data. The data_out pointer should be
//! stable until you explicitely ask for it to be consumed with circular_buffer_consume.
//!
//! @param buffer The buffer to read from
//! @param length How many bytes to read
//! @param[out] data_out The bytes that were read.
//! @param[out] length_out How many bytes were read.
//! @return false if there's less than length bytes in the buffer.
bool circular_buffer_read(const CircularBuffer* buffer, uint16_t length, const uint8_t** data_out, uint16_t* length_out);
//! Same as circular_buffer_copy_offset(), but with a starting offset of zero.
uint16_t circular_buffer_copy(const CircularBuffer* buffer, void *data_out, uint16_t length);
//! Copy a number of bytes from the circular buffer into another (contiguous)
//! buffer. The function takes care of any wrapping of the circular buffer.
//! The data remains on the circular buffer until circular_buffer_consume is
//! called.
//! @param buffer The circular buffer to copy from.
//! @param start_offset Number of bytes of the source content to skip.
//! @param[out] data_out The buffer to copy the data to.
//! @param length The number of bytes to copy.
//! @return The number of bytes copied.
uint16_t circular_buffer_copy_offset(const CircularBuffer* buffer, uint16_t start_offset,
uint8_t *data_out, uint16_t length);
//! Gets a pointer to a continuous byte array of the requested length from the circular buffer.
//! In case the requested length wraps around the edges of the circular buffer, a heap-allocated
//! copy is made.
//! @param buffer The buffer to read or copy from.
//! @param[out] data_ptr_out After returning, it will point to the byte array with the data. NOTE:
//! This can be NULL in the case the malloc_imp wasn't able to allocate the buffer.
//! @param[in] length The length of the data to read. If this is longer than what
//! circular_buffer_get_read_space_remaining() returns, the function will return false.
//! @param[in] malloc_imp The malloc() implementation to allocate a new buffer, in case if the
//! requested length is non-contiguously stored.
//! @param[out] caller_should_free Will be true after returning if the caller must call
//! free(*data_ptr_out) at some point, because the data had been copied into a temporary
//! heap-allocated buffer.
//! @return false if there's less than length bytes in the buffer or if the copy failed because
//! there was not enough memory.
bool circular_buffer_read_or_copy(const CircularBuffer* buffer, uint8_t **data_out, size_t length,
void *(*malloc_imp)(size_t), bool *caller_should_free);
//! Removes length bytes of the oldest data from the buffer.
bool circular_buffer_consume(CircularBuffer* buffer, uint16_t length);
//! @return The number of bytes we can write before circular_buffer_write will return false.
uint16_t circular_buffer_get_write_space_remaining(const CircularBuffer* buffer);
//! @return The number of bytes we can read before circular_buffer_read will return false.
uint16_t circular_buffer_get_read_space_remaining(const CircularBuffer* buffer);

View file

@ -0,0 +1,64 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/order.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//! @note Needs to handle NULL items gracefully
typedef void (*CircularCacheItemDestructor)(void *item);
//! Array-backed circular cache
typedef struct {
uint8_t* cache; //<! Pointer to the array
size_t item_size; //<! Size of the array element in bytes
int next_erased_item_idx; //<! Next array element to be deleted
int total_items;
Comparator compare_cb;
CircularCacheItemDestructor item_destructor;
} CircularCache;
void circular_cache_init(CircularCache* c, uint8_t* buffer, size_t item_size,
int total_items, Comparator compare_cb);
//! Add a destructor to be called when an item is evicted from the circular cache.
//! @note the destructor needs to handle NULL items gracefully
void circular_cache_set_item_destructor(CircularCache *c, CircularCacheItemDestructor destructor);
//! @return True if the cache contains the data
//! @note Item must be of size item_size
bool circular_cache_contains(CircularCache* c, void* item);
//! @return Pointer to buffer of entry in cache that contains the data
//! @note Item must be of size item_size
void *circular_cache_get(CircularCache* c, void* theirs);
//! Push data of size item_size into the circular cache
//! Overwrites the item at next_erased_item_idx
void circular_cache_push(CircularCache* c, void* item);
//! Fills a circular cache with the representation an item, useful for non-zero clearing a cache
//! @note this will assert if an item destructor is set
void circular_cache_fill(CircularCache *c, uint8_t *item);
//! Flushes the buffer, calling destructors for each item in the cache. The calling module must
//! be able to differentiate between a valid and invalid entry in the cache (e.g. the cache is not
//! yet filled, so it has entries with zeroed out data).
void circular_cache_flush(CircularCache *c);

View file

@ -0,0 +1,67 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! \file
//! Calculate the CRC-32 checksum of data.
//!
//! The checksum is the standard CRC-32 used by zlib, PNG and others.
//! The model parameters for the algorithm, as described in A Painless Guide to
//! CRC Error Detection Algorithms (http://www.zlib.net/crc_v3.txt), are:
//! Name: "CRC-32"
//! Width: 32
//! Poly: 04C11DB7
//! Init: FFFFFFFF
//! RefIn: True
//! RefOut: True
//! XorOut: FFFFFFFF
//! Check: CBF43926
#include <stdint.h>
#include <string.h>
//! Update a running CRC-32 checksum with the bytes of data and return the
//! updated CRC-32. If data is NULL, the function returns the required initial
//! value for the CRC.
//!
//! This function is drop-in compatible with zlib's crc32 function.
//!
//! \par Usage
//! \code
//! uint32_t crc = crc32(0, NULL, 0);
//! while (read_buffer(data, length)) {
//! crc = crc32(crc, data, length);
//! }
//! \endcode
uint32_t crc32(uint32_t crc, const void * restrict data, size_t length);
//! The initial CRC register value for a standard CRC-32 checksum.
//!
//! It is the same value as is returned by the `crc32` function when data is
//! NULL.
//!
//! \code
//! assert(CRC32_INIT == crc32(0, NULL, 0));
//! \endcode
#define CRC32_INIT (0)
//! The residue constant of the CRC-32 algorithm.
//!
//! If the CRC-32 value of a message is appended (little-endian) onto the
//! end of the message, the CRC-32 of the concatenated message and CRC will be
//! equal to CRC32_RESIDUE if the message has not been corrupted in transit.
#define CRC32_RESIDUE (0x2144DF1C)

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
uint32_t hash(const uint8_t *bytes, const uint32_t length);

View file

@ -0,0 +1,139 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
struct _tagHeapInfo_t;
typedef struct _tagHeapInfo_t HeapInfo_t;
typedef void (*LockFunction)(void*);
typedef void (*UnlockFunction)(void*);
typedef struct HeapLockImpl {
LockFunction lock_function;
UnlockFunction unlock_function;
void* lock_context;
} HeapLockImpl;
typedef void (*DoubleFreeHandler)(void*);
typedef void (*CorruptionHandler)(void*);
typedef struct Heap {
// These HeapInfo_t structure pointers are initialized to the start and the end of the heap area.
// The begin will point to the first block that's in the heap area, where the end is actually a
// pointer to the first block *after* the heap area. This means the last block in the heap isn't
// actually at end, but at end - begin->PrevSize.
HeapInfo_t *begin;
HeapInfo_t *end;
//! Number of allocated bytes, including beginers
unsigned int current_size;
//! Peak number of allocated bytes, including beginers
unsigned int high_water_mark;
HeapLockImpl lock_impl;
DoubleFreeHandler double_free_handler;
bool fuzz_on_free;
void *corrupt_block;
CorruptionHandler corruption_handler;
} Heap;
//! Initialize the heap inside the specified boundaries, zero-ing out the free
//! list data structure.
//! @note Assumes 0 is not a valid address for allocation.
//! @param start The start of the heap will be the first int-aligned address >= start
//! @param end The end of the heap will be the last sizeof(HeaderBlock) aligned
//! address that is < end
//! @param fuzz_on_free if true, memsets memory contents to junk values upon free in order to
//! catch bad accesses more quickly
void heap_init(Heap* const heap, void* start, void* end, bool fuzz_on_free);
//! Configure this heap for thread safety using the given locking implementation
void heap_set_lock_impl(Heap *heap, HeapLockImpl lock_impl);
//! Configure the heap with a pointer that gets called when a double free is detected.
//! If this isn't configured on a heap, the default behaviour is to trigger a PBL_CROAK.
void heap_set_double_free_handler(Heap *heap, DoubleFreeHandler double_free_handler);
//! Configure the heap with a pointer that gets called when (and if) corruption is detected.
//! If this isn't configured on a heap, the default behaviour is to trigger a PBL_CROAK.
void heap_set_corruption_handler(Heap *heap, CorruptionHandler corruption_handler);
//! Allocate a fragment of memory on the given heap. Tries to avoid
//! fragmentation by obtaining memory requests larger than LARGE_SIZE from the
//! endo of the buffer, while small fragments are taken from the start of the
//! buffer.
//! @note heap_init() must be called prior to using heap_malloc().
//! @param nbytes Number of bytes to be allocated. Must be > 0.
//! @param client_pc The PC register of the client who caused this malloc. Only used when
//! MALLOC_INSTRUMENTATION is defined.
//! @return A pointer to the start of the allocated memory
void* heap_malloc(Heap* const heap, unsigned long nbytes, uintptr_t client_pc);
//! Return memory to free list. Where possible, make contiguous blocks of free
//! memory. The function tries to verify that the structure is a valid fragment
//! structure before the memory is freed. When a fragment is freed, adjacent
//! free fragments may be combined.
//! @note heap_init() must be called prior to using heap_free().
//! otherwise, the free list will be NULL.)
//! @note Assumes that 0 is not a valid address for allocation.
void heap_free(Heap* const heap, void* ptr, uintptr_t client_pc);
//! Allocate a new block of the given size, and copy over the data at ptr into
//! the new block. If the new size is smaller than the old size, will only copy
//! over as much data as possible. Frees ptr.
//! @param ptr Points to the memory region to re-allocate.
//! @param nbytes The total number of bytes to allocate.
//! @param client_pc The PC register of the client who caused this malloc. Only used when
//! MALLOC_INSTRUMENTATION is defined.
void* heap_realloc(Heap* const heap, void *ptr, unsigned long nbytes, uintptr_t client_pc);
//! Allocate a buffer to hold anything. The initial contents of the buffer
//! are zero'd.
void* heap_zalloc(Heap* const heap, size_t size, uintptr_t client_pc);
//! Allocate a buffer to hold an array of count elements, each of size size (in bytes)
//! and initializes all bits to zero.
void* heap_calloc(Heap* const heap, size_t count, size_t size, uintptr_t client_pc);
//! @return True if ptr is on the given heap, false otherwise.
bool heap_contains_address(Heap* const heap, void* ptr);
//! @return True if ptr is allocated on the given heap, false otherwise.
bool heap_is_allocated(Heap* const heap, void* ptr);
//! @return The size of the heap in bytes
size_t heap_size(const Heap *heap);
//! @return the fewest amount of bytes that the given heap had free
uint32_t heap_get_minimum_headroom(Heap *heap);
//! Used for debugging.
//! Calculates and outputs the current memory usage on the given heap.
//! @param used Output, will contain the number of bytes currently
//! allocated and in use.
//! @param free Output, will contain the number of unallocated bytes.
//! @param max_free Output, will contain size of the largest unallocated
//! fragment.
void heap_calc_totals(Heap* const heap, unsigned int *used, unsigned int *free, unsigned int *max_free);
void heap_dump_malloc_instrumentation_to_dbgserial(Heap *heap);

View file

@ -0,0 +1,29 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef void (*HexdumpLineCallback)(int level, const char *src_filename, int src_line_number,
const char *line_buffer);
//! Hexdumps data in xxd-style formatting, by repeatedly calling write_line_cb for each line.
//! @note The line_buffer that is passed does not end with any newline characters.
void hexdump(const char *src_filename, int src_line_number, int level,
const uint8_t *data, size_t length, HexdumpLineCallback write_line_cb);

View file

@ -0,0 +1,44 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//! Simple utility for enforcing consistent use of the iterator pattern
//! and facilitate unit testing.
#pragma once
#include <stdbool.h>
typedef void* IteratorState;
typedef bool (*IteratorCallback)(IteratorState state);
typedef struct {
IteratorCallback next;
IteratorCallback prev;
IteratorState state;
} Iterator;
#define ITERATOR_EMPTY ((Iterator){ 0, 0, 0 })
void iter_init(Iterator* iter, IteratorCallback next, IteratorCallback prev, IteratorState state);
//! @return true if successfully moved to next node
bool iter_next(Iterator* iter);
//! @return true if successfully moved to previous node
bool iter_prev(Iterator* iter);
IteratorState iter_get_state(Iterator* iter);

View file

@ -0,0 +1,46 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/order.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef uint32_t KeyedCircularCacheKey;
//! Array-backed circular cache, optimized for data cache efficiency
typedef struct {
KeyedCircularCacheKey *cache_keys;
uint8_t *cache_data; //<! Pointer to the array
size_t item_size; //<! Size of the array element in bytes
size_t next_item_to_erase_idx; //<! Next array element to be deleted
size_t total_items;
} KeyedCircularCache;
void keyed_circular_cache_init(KeyedCircularCache *c, KeyedCircularCacheKey *key_buffer,
void *data_buffer, size_t item_size, size_t total_items);
//! @return Pointer to buffer of entry in cache that contains the data
//! @note Item must be of size item_size
void *keyed_circular_cache_get(KeyedCircularCache *c, KeyedCircularCacheKey key);
//! Push data of size item_size into the circular cache
//! Overwrites the item at next_item_to_erase_idx
void keyed_circular_cache_push(KeyedCircularCache *c, KeyedCircularCacheKey key,
const void *item);

View file

@ -0,0 +1,20 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#define LIKELY(x) __builtin_expect((x),1)
#define UNLIKELY(x) __builtin_expect((x),0)

View file

@ -0,0 +1,156 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "order.h"
typedef struct ListNode {
struct ListNode* next;
struct ListNode* prev;
} ListNode;
typedef bool (*ListFilterCallback)(ListNode *found_node, void *data);
//! - If a callback returns true, the iteration continues
//! - If a callback returns false, the ieration stops.
typedef bool (*ListForEachCallback)(ListNode *node, void *context);
#define LIST_NODE_NULL { .next = NULL, .prev = NULL }
//! Initializes the node.
void list_init(ListNode *head);
//! Inserts new_node after node in the list.
//! Always returns one of the two nodes that is closest to, or is the tail of the list.
ListNode* list_insert_after(ListNode *node, ListNode *new_node);
//! Inserts new_node before node in the list.
//! Always returns one of the two nodes that is closest to, or is the head of the list.
//! Warning: Returns new_node, rather than the new head of the list
//! as you might expect.
ListNode* list_insert_before(ListNode *node, ListNode *new_node);
//! Removes the head of the list and returns the new head.
ListNode* list_pop_head(ListNode *node);
//! Removes the tail of the list and returns the new tail.
ListNode* list_pop_tail(ListNode *node);
//! Removed the node from the list.
//! @param node the ListNode to remove.
//! @param[in,out] *head will be updated if the removed node happens to be the head
//! @param[in,out] *tail will be updated if the removed node happens to be the tail.
//! @note head and tail parameters are optional. Pass in NULL if not used.
void list_remove(ListNode *node, ListNode **head, ListNode **tail);
//! Appends new_node to the tail of the list that node is part of.
//! @param node Any node in the list, can be NULL (will result in a list containing only new_node)
//! Always returns the tail of the list.
ListNode* list_append(ListNode *node, ListNode *new_node);
//! Appends new_node to the head of the list that node is part of.
//! @param node Any node in the list, can be NULL (will result in a list containing only new_node)
//! Always returns the head of the list.
ListNode* list_prepend(ListNode *node, ListNode *new_node);
//! Gets the next node
ListNode* list_get_next(ListNode *node);
//! Gets the previous node
ListNode* list_get_prev(ListNode *node);
//! Gets the last node in the list
ListNode* list_get_tail(ListNode *node);
//! Gets the first node in the list
ListNode* list_get_head(ListNode *node);
//! @return true if the passed in node is the head of a list.
bool list_is_head(const ListNode *node);
//! @return true if the passed in node is the tail of a list.
bool list_is_tail(const ListNode *node);
//! Counts the number of nodes from node to the tail of the list, including said node
uint32_t list_count_to_tail_from(ListNode *node);
//! Counts the number of nodes from node to the head of the list, including said node
uint32_t list_count_to_head_from(ListNode *node);
//! Counts the number of nodes from head to tail
uint32_t list_count(ListNode *node);
//! Gets the node at <index> away, where positive index is towards the tail
ListNode* list_get_at(ListNode *node, int32_t index);
//! Adds a node to a list ordered by given comparator.
//! @param[in] head The head of the list that we want to add to.
//! @param[in] new_node The node being added.
//! @param[in] comparator The comparison function to use
//! @param[in] ascending True to maintain the list ordered ascending from head to tail.
//! @returns The (new) head of the list.
//! @note This function will not sort existing nodes in the list.
ListNode* list_sorted_add(ListNode *head, ListNode *new_node, Comparator comparator, bool ascending);
//! @param[in] head The head of the list to search.
//! @param[in] node The node to search for.
//! @returns True if the list contains node
bool list_contains(const ListNode *head, const ListNode *node);
//! Gets the first node that conforms to the given filter callback
//! @param node The list node from which to depart the search
//! @param filter_callback A function returning true in case the node that is passed in matches the
//! filter criteria, and false if it doesn't and should be skipped.
//! @param found_node The node to be evaluated by the filter callback
//! @param data Optional callback data
ListNode* list_find(ListNode *node, ListFilterCallback filter_callback, void *data);
//! Gets the next node that conforms to the given filter callback
//! @param node The list node from which to depart the search
//! @param filter_callback A function returning true in case the node that is passed in matches the
//! filter criteria, and false if it doesn't and should be skipped.
//! @param found_node The node to be evaluated by the filter callback
//! @param wrap_around True if the search should continue from the head if the tail has been reached
//! @param data Optional callback data
ListNode* list_find_next(ListNode *node, ListFilterCallback filter_callback, bool wrap_around, void *data);
//! Gets the previous node that conforms to the given filter callback
//! @param node The list node from which to depart the search
//! @param filter_callback A function returning true in case the node that is passed in matches the
//! filter criteria, and false if it doesn't and should be skipped.
//! @param found_node The node to be evaluated by the filter callback
//! @param wrap_around True if the search should continue from the tail if the head has been reached
//! @param data Optional callback data
ListNode* list_find_prev(ListNode *node, ListFilterCallback filter_callback, bool wrap_around, void *data);
//! Concatenate two lists.
//! @param list_a list onto which to concatenate list_b
//! @param list_b list to concatenate onto list_a
//! @return head of the new list
ListNode* list_concatenate(ListNode *list_a, ListNode *list_b);
//! Iterates over each node and passes it into callback given
//! @param[in] head The head of the list that we want to iterate over.
//! @param[in] each_cb The callback function to pass each node into
//! @param[in] data Optional callback data
void list_foreach(ListNode *head, ListForEachCallback each_cb, void *context);
//! Dump a list to PBL_LOG
void list_debug_dump(ListNode *head);

View file

@ -0,0 +1,26 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
void util_log(const char *filename, int line, const char *string);
#define UTIL_LOG(string) \
do { \
util_log(__FILE_NAME__, __LINE__, string); \
} while (0)
void util_dbgserial_str(const char *string);

View file

@ -0,0 +1,101 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#define ABS(a) (((a) > 0) ? (a) : -1 * (a))
#define CLIP(n, min, max) ((n)<(min)?(min):((n)>(max)?(max):(n)))
#define ROUND(num, denom) (((num) + ((denom) / 2))/(denom))
#define WITHIN(n, min, max) ((n) >= (min) && (n) <= (max))
#define RANGE_WITHIN(n_min, n_max, min, max) ((n_min) >= (min) && (n_max) <= (max))
// Divide num by denom, rounding up (ceil(0.5) is 1.0, and ceil(-0.5) is 0.0)
// ex. 3, 4 (ie. 3/4) : returns 1
// ex. -3, 4 : returns 0
#define DIVIDE_CEIL(num, denom) (((num) + ((denom) - 1)) / (denom))
// Round value up to the next increment of modulus
// ex. val = 152 mod = 32 : returns 160
// val = -32 mod = 90 : returns -90
#define ROUND_TO_MOD_CEIL(val, mod) \
((val >= 0) ? \
((((val) + ABS(ABS(mod) - 1)) / ABS(mod)) * ABS(mod)) : \
-((((-val) + ABS(ABS(mod) - 1)) / ABS(mod)) * ABS(mod)))
int32_t sign_extend(uint32_t a, int bits);
//! Calculates the distance (end - start), taking a roll-over into account as good as it can get.
int32_t serial_distance32(uint32_t start, uint32_t end);
//! Calculates the distance (end - start), taking a roll-over into account as good as it can get.
//! @param bits the number of bits that are valid in start and end.
int32_t serial_distance(uint32_t start, uint32_t end, int bits);
/*
* find the log base two of a number rounded up
*/
int ceil_log_two(uint32_t n);
//! newton's method for floor(sqrt(x)) -> should always converge
int32_t integer_sqrt(int64_t x);
/*
* The -Wtype-limits flag generated an error with the previous IS_SIGNED maco.
* If an unsigned number was passed in the macro would check if the unsigned number was less than 0.
*/
//! Determine whether a variable is signed or not.
//! @param var The variable to evaluate.
//! @return true if the variable is signed.
#define IS_SIGNED(var) (__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned char), false, \
__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned short), false, \
__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned int), false, \
__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned long), false, \
__builtin_choose_expr( \
__builtin_types_compatible_p(__typeof__(var), unsigned long long), false, true))))) \
)
// http://stackoverflow.com/questions/14997165/fastest-way-to-get-a-positive-modulo-in-c-c
static inline int positive_modulo(int i, int n) {
return (i % n + n) % n;
}
// https://stackoverflow.com/questions/1878907/the-smallest-difference-between-2-angles
static inline int distance_to_mod_boundary(int32_t i, uint16_t n) {
const int mod = positive_modulo(i, n);
const int half = n / 2;
return ABS((mod + half) % n - half);
}
/**
* Compute the next backoff interval using a bounded binary expoential backoff formula.
*
* @param[in,out] attempt The number of retries performed so far. This count will be incremented by the function.
* @param[in] initial_value The inital backoff interval. Subsequent backoff attempts will be this number multiplied by a power of 2.
* @param[in] max_value The maximum backoff interval that returned by the function.
* @return The next backoff interval.
*/
uint32_t next_exponential_backoff(uint32_t *attempt, uint32_t initial_value, uint32_t max_value);
//! Find the greatest common divisor of two numbers.
uint32_t gcd(uint32_t a, uint32_t b);

View file

@ -0,0 +1,188 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <inttypes.h>
#include <stdbool.h>
////////////////////////////////////////////////////////////////
/// Fixed_S16_3 = 1 bit sign, 12 bits integer, 3 bits fraction
////////////////////////////////////////////////////////////////
// Note the fraction is unsigned and represents a positive addition
// to the integer. So for example:
// The value -1.125 will be stored as (-2 + 7*0.125) ==> integer = -2, fraction = 7
// The value 1.125 will be stored as (1 + 1*0.125) ==> integer = 1, fraction = 1
// This representation allows for direct addition/multiplication between numbers to happen
// without any complicated logic.
// The same representation for negative numbers applies for all fixed point representations
// in this file (i.e. fraction component is a positive addition to the integer).
typedef union __attribute__ ((__packed__)) Fixed_S16_3 {
int16_t raw_value;
struct {
uint16_t fraction:3;
int16_t integer:13;
};
} Fixed_S16_3;
#define Fixed_S16_3(raw) ((Fixed_S16_3){ .raw_value = (raw) })
#define FIXED_S16_3_PRECISION 3
#define FIXED_S16_3_FACTOR (1 << FIXED_S16_3_PRECISION)
#define FIXED_S16_3_ZERO ((Fixed_S16_3){ .integer = 0, .fraction = 0 })
#define FIXED_S16_3_ONE ((Fixed_S16_3){ .integer = 1, .fraction = 0 })
#define FIXED_S16_3_HALF ((Fixed_S16_3){ .raw_value = FIXED_S16_3_ONE.raw_value / 2 })
static __inline__ Fixed_S16_3 Fixed_S16_3_mul(Fixed_S16_3 a, Fixed_S16_3 b) {
return Fixed_S16_3(((int32_t) a.raw_value * b.raw_value) >> FIXED_S16_3_PRECISION);
}
static __inline__ Fixed_S16_3 Fixed_S16_3_add(Fixed_S16_3 a, Fixed_S16_3 b) {
return Fixed_S16_3(a.raw_value + b.raw_value);
}
static __inline__ Fixed_S16_3 Fixed_S16_3_sub(Fixed_S16_3 a, Fixed_S16_3 b) {
return Fixed_S16_3(a.raw_value - b.raw_value);
}
static __inline__ Fixed_S16_3 Fixed_S16_3_add3(Fixed_S16_3 a, Fixed_S16_3 b, Fixed_S16_3 c) {
return Fixed_S16_3(a.raw_value + b.raw_value + c.raw_value);
}
static __inline__ bool Fixed_S16_3_equal(Fixed_S16_3 a, Fixed_S16_3 b) {
return (a.raw_value == b.raw_value);
}
static __inline__ int16_t Fixed_S16_3_rounded_int(Fixed_S16_3 a) {
const int16_t delta = a.raw_value >= 0 ? FIXED_S16_3_HALF.raw_value : -FIXED_S16_3_HALF.raw_value;
return (a.raw_value + delta) / FIXED_S16_3_FACTOR;
}
////////////////////////////////////////////////////////////////
/// Fixed_S32_16 = 1 bit sign, 15 bits integer, 16 bits fraction
////////////////////////////////////////////////////////////////
typedef union __attribute__ ((__packed__)) Fixed_S32_16 {
int32_t raw_value;
struct {
uint16_t fraction:16;
int16_t integer:16;
};
} Fixed_S32_16;
//! Work-around for function pointer return type Fixed_S32_16 to avoid
//! tripping the pre-processor to use the equally named Fixed_S32_16 define
typedef Fixed_S32_16 Fixed_S32_16Return;
#define Fixed_S32_16(raw) ((Fixed_S32_16){ .raw_value = (raw) })
#define FIXED_S32_16_PRECISION 16
#define FIXED_S32_16_ONE ((Fixed_S32_16){ .integer = 1, .fraction = 0 })
#define FIXED_S32_16_ZERO ((Fixed_S32_16){ .integer = 0, .fraction = 0 })
static __inline__ Fixed_S32_16 Fixed_S32_16_mul(Fixed_S32_16 a, Fixed_S32_16 b) {
Fixed_S32_16 x;
x.raw_value = (int32_t)((((int64_t) a.raw_value * (int64_t) b.raw_value)) >>
FIXED_S32_16_PRECISION);
return x;
}
static __inline__ Fixed_S32_16 Fixed_S32_16_add(Fixed_S32_16 a, Fixed_S32_16 b) {
return Fixed_S32_16(a.raw_value + b.raw_value);
}
static __inline__ Fixed_S32_16 Fixed_S32_16_add3(Fixed_S32_16 a, Fixed_S32_16 b, Fixed_S32_16 c) {
return Fixed_S32_16(a.raw_value + b.raw_value + c.raw_value);
}
static __inline__ Fixed_S32_16 Fixed_S32_16_sub(Fixed_S32_16 a, Fixed_S32_16 b) {
return Fixed_S32_16(a.raw_value - b.raw_value);
}
////////////////////////////////////////////////////////////////
/// Fixed_S64_32 = 1 bit sign, 31 bits integer, 32 bits fraction
////////////////////////////////////////////////////////////////
typedef union __attribute__ ((__packed__)) Fixed_S64_32 {
int64_t raw_value;
struct {
uint32_t fraction:32;
int32_t integer:32;
};
} Fixed_S64_32;
#define FIXED_S64_32_PRECISION 32
#define FIXED_S64_32_ONE ((Fixed_S64_32){ .integer = 1, .fraction = 0 })
#define FIXED_S64_32_ZERO ((Fixed_S64_32){ .integer = 0, .fraction = 0 })
#define FIXED_S64_32_FROM_RAW(raw) ((Fixed_S64_32){ .raw_value = (raw) })
#define FIXED_S64_32_FROM_INT(x) ((Fixed_S64_32){ .integer = x, .fraction = 0 })
#define FIXED_S64_32_TO_INT(x) (x.integer)
static __inline__ Fixed_S64_32 Fixed_S64_32_mul(Fixed_S64_32 a, Fixed_S64_32 b) {
Fixed_S64_32 result;
result.raw_value = (((uint64_t)(a.integer * b.integer)) << 32)
+ ((((uint64_t)a.fraction) * ((uint64_t)b.fraction)) >> 32)
+ ((a.integer) * ((uint64_t)b.fraction))
+ (((uint64_t)a.fraction) * (b.integer));
return result;
}
static __inline__ Fixed_S64_32 Fixed_S64_32_add(Fixed_S64_32 a, Fixed_S64_32 b) {
return FIXED_S64_32_FROM_RAW(a.raw_value + b.raw_value);
}
static __inline__ Fixed_S64_32 Fixed_S64_32_add3(Fixed_S64_32 a, Fixed_S64_32 b, Fixed_S64_32 c) {
return FIXED_S64_32_FROM_RAW(a.raw_value + b.raw_value + c.raw_value);
}
static __inline__ Fixed_S64_32 Fixed_S64_32_sub(Fixed_S64_32 a, Fixed_S64_32 b) {
return FIXED_S64_32_FROM_RAW(a.raw_value - b.raw_value);
}
////////////////////////////////////////////////////////////////
/// Mixed operations
////////////////////////////////////////////////////////////////
// This function muliples a Fixed_S16_3 and Fixed_S32_16 and returns result in Fixed_S16_3 format
static __inline__ Fixed_S16_3 Fixed_S16_3_S32_16_mul(Fixed_S16_3 a, Fixed_S32_16 b) {
return Fixed_S16_3( a.raw_value * b.raw_value >> FIXED_S32_16_PRECISION );
}
////////////////////////////////////////////////////////////////
/// High level math functions and filters
////////////////////////////////////////////////////////////////
//! Run x through a linear recursive filter. See https://en.wikipedia.org/wiki/Digital_biquad_filter
//! for example of a 2nd order recursive filter. This function implements a generic Nth order one.
//! @param[in] x the next input value, x[n]
//! @param[in] num_input_coefficients the number of taps on the input side
//! @param[in] num_output_coefficients the number of taps on the output side
//! @param[in] cb pointer to array of input side coefficients. Must be an array of size
//! num_input_coefficients
//! @param[in] ca pointer to array of output side coefficients. Must be an array of size
//! num_output_coefficients
//! @param[in|out] state_x pointer to array to hold the history of x. Must be an array
//! of size num_input_coefficients
//! @param[in|out] state_y pointer to array to hold the history of y. Must be an array
//! of size num_output_coefficients
//! @return the filtered output value, y[n]
Fixed_S64_32 math_fixed_recursive_filter(Fixed_S64_32 x,
int num_input_coefficients, int num_output_coefficients,
const Fixed_S64_32 *cb, const Fixed_S64_32 *ca,
Fixed_S64_32 *state_x, Fixed_S64_32 *state_y);

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <inttypes.h>
//! A Comparator returns the Order in which (a, b) occurs
//! @return negative int for a descending value (a > b), positive for an ascending value (b > a), 0 for equal
typedef int (*Comparator)(void *a, void *b);
int uint32_comparator(void *a, void *b);

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <inttypes.h>
extern uint32_t rand32(void);

View file

@ -0,0 +1,31 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! Calculate the length of an array, based on the size of the element type.
//! @param array The array to be evaluated.
//! @return The length of the array.
#define ARRAY_LENGTH(array) (sizeof((array))/sizeof((array)[0]))
//! Calculate the length of a literal array based on the size of the given type
//! This is usable in contexts that require compile time constants
//! @param type Type of the elements
//! @param array Literal definition of the array
//! @return Length of the array in bytes
#define STATIC_ARRAY_LENGTH(type, array) (sizeof((type[]) array) / sizeof(type))
#define MEMBER_SIZE(type, member) sizeof(((type *)0)->member)

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
//! Standard sort comparator function
typedef int (*SortComparator)(const void *, const void *);
//! Bubble sorts an array
//! @param[in] array The array that should be sorted
//! @param[in] num_elem Number of elements in the array
//! @param[in] elem_size Size of each element in the array
//! @param[in] comp SortComparator comparator function
void sort_bubble(void *array, size_t num_elem, size_t elem_size, SortComparator comp);

View file

@ -0,0 +1,72 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
// For some reason the string.h that newlib generates doesn't have this definition,
// but if you just extern this the libc still has the right symbol. Weirdddd.
size_t strnlen(const char *, size_t);
const char *string_strip_leading_whitespace(const char * string);
void string_strip_trailing_whitespace(const char *string, char *string_out);
#define IS_EMPTY_STRING(s) (s[0] == '\0')
// Stolen from http://stackoverflow.com/a/8488201
#define GET_FILE_NAME(file) (strrchr(file, '/') ? (strrchr(file, '/') + 1) : (file))
//! Converts an unsigned integer value to a null-terminated hex-value string and stores the result
//! in buffer.
void itoa(uint32_t num, char *buffer, int buffer_length);
//! Converts a signed integer value to a null-terminated string and stores the result in buffer.
//! NOTE: Buffer must be long enough to fit a string 12 bytes long.
void itoa_int(int n, char *str, int base);
//! Reverses a string in place
void string_reverse(char *str);
uintptr_t str_to_address(const char *address_str);
const char *bool_to_str(bool b);
//! @param hex 12-digit hex string representing a BT address
//! @param addr Points to a SS1 BD_ADDR_t as defined in BTBTypes.h
//! @return True on success
bool convert_bt_addr_hex_str_to_bd_addr(const char *hex_str, uint8_t *bd_addr, const unsigned int bd_addr_size);
//! Concatenates a simple string and a number.
//! NOTE: Buffer must be long enough to fit the largest number value (12 bytes) and the string, plus
//! A null-terminated character
void concat_str_int(const char *str, uint32_t num, char *buf, uint8_t buf_len);
//! Convert an ASCII string to uppercase
void toupper_str(char *str);
//! Converts a byte stream to a hex string, i.e ({0xaa, 0xbb, 0xcc} -> "aabbcc")
void byte_stream_to_hex_string(char *out_buf, size_t out_buf_len,
const uint8_t *byte_stream, size_t byte_stream_len, bool stream_backward);
//! Appends the src string to dst, taking the overall size of the dst buffer into account
//! @param dst Destination string to append to
//! @param src String to append
//! @param dst_space Total size of dst buffer
void safe_strcat(char* dst, const char* src, int dst_space);

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
//! Returns `struct_ptr->field_name` if `struct_ptr` isn't NULL, otherwise returns default_value
#define NULL_SAFE_FIELD_ACCESS(struct_ptr, field_name, default_value) \
((struct_ptr) ? ((struct_ptr)->field_name) : (default_value))

View file

@ -0,0 +1,73 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <stdint.h>
//! @file trig.h
//!
//! @addtogroup Foundation
//! @{
//! @addtogroup Math
//! @{
//! The largest value that can result from a call to \ref sin_lookup or \ref cos_lookup.
//! For a code example, see the detailed description at the top of this chapter: \ref Math
#define TRIG_MAX_RATIO 0xffff
//! Angle value that corresponds to 360 degrees or 2 PI radians
//! @see \ref sin_lookup
//! @see \ref cos_lookup
#define TRIG_MAX_ANGLE 0x10000
//! Angle value that corresponds to 180 degrees or PI radians
//! @see \ref sin_lookup
//! @see \ref cos_lookup
#define TRIG_PI 0x8000
#define TRIG_FP 16
//! Converts from a fixed point value representation to the equivalent value in degrees
//! @see DEG_TO_TRIGANGLE
//! @see TRIG_MAX_ANGLE
#define TRIGANGLE_TO_DEG(trig_angle) (((trig_angle) * 360) / TRIG_MAX_ANGLE)
//! Converts from an angle in degrees to the equivalent fixed point value representation
//! @see TRIGANGLE_TO_DEG
//! @see TRIG_MAX_ANGLE
#define DEG_TO_TRIGANGLE(angle) (((angle) * TRIG_MAX_ANGLE) / 360)
//! Look-up the sine of the given angle from a pre-computed table.
//! @param angle The angle for which to compute the cosine.
//! The angle value is scaled linearly, such that a value of 0x10000 corresponds to 360 degrees or 2 PI radians.
int32_t sin_lookup(int32_t angle);
//! Look-up the cosine of the given angle from a pre-computed table.
//! This is equivalent to calling `sin_lookup(angle + TRIG_MAX_ANGLE / 4)`.
//! @param angle The angle for which to compute the cosine.
//! The angle value is scaled linearly, such that a value of 0x10000 corresponds to 360 degrees or 2 PI radians.
int32_t cos_lookup(int32_t angle);
//! Look-up the arctangent of a given x, y pair
//! The angle value is scaled linearly, such that a value of 0x10000 corresponds to 360 degrees or 2 PI radians.
int32_t atan2_lookup(int16_t y, int16_t x);
//! @internal
//! Normalize an angle to the range [0, TRIG_MAX_ANGLE]
uint32_t normalize_angle(int32_t angle);
//! @} // group Math
//! @} // group Foundation

View file

@ -0,0 +1,91 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "util/list.h"
#include <stdint.h>
#include <stdbool.h>
#define UUID_SIZE 16
typedef struct __attribute__((__packed__)) {
uint8_t byte0;
uint8_t byte1;
uint8_t byte2;
uint8_t byte3;
uint8_t byte4;
uint8_t byte5;
uint8_t byte6;
uint8_t byte7;
uint8_t byte8;
uint8_t byte9;
uint8_t byte10;
uint8_t byte11;
uint8_t byte12;
uint8_t byte13;
uint8_t byte14;
uint8_t byte15;
} Uuid;
#define UUID_SYSTEM {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define UUID_INVALID_INIT {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
#define UUID_INVALID ((const Uuid) UUID_INVALID_INIT)
//! Make a Uuid object from sixteen bytes.
//! @return A Uuid structure representing the bytes p0 to p15.
#define UuidMake(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15) ((Uuid) {p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15})
//! Creates a Uuid from an array of bytes with 16 bytes in Big Endian order.
//! @return The created Uuid
#define UuidMakeFromBEBytes(b) ((Uuid) { b[0], b[1], b[2], b[3], \
b[4], b[5], b[6], b[7], \
b[8], b[9], b[10], b[11], \
b[12], b[13], b[14], b[15] })
//! Creates a Uuid from an array of bytes with 16 bytes in Little Endian order.
//! @return The created Uuid
#define UuidMakeFromLEBytes(b) ((Uuid) { b[15], b[14], b[13], b[12], \
b[11], b[10], b[9], b[8], \
b[7], b[6], b[5], b[4], \
b[3], b[2], b[1], b[0] })
//! Compares two UUIDs.
//! @return True if the two UUIDs are equal, false if they are not.
bool uuid_equal(const Uuid *uu1, const Uuid *uu2);
//! Generate a v4 compliant UUID
void uuid_generate(Uuid *uuid_out);
//! Check if a UUID is the system UUID
//! @return True if UUID is UUID_SYSTEM, false otherwise
bool uuid_is_system(const Uuid *uuid);
//! Tests whether a UUID is equal to UUID_INVALID
//! @param uuid The UUID to test
//! @return True uuid is equal to UUID_INVALID, false if not
bool uuid_is_invalid(const Uuid *uuid);
//! The minimum required length of a string used to hold a uuid (including null).
#define UUID_STRING_BUFFER_LENGTH (32 + 4 + 2 + 1) // numbers + dashes + brackets + trailing null
//! Writes UUID in a string form into buffer that looks like the following...
//! {12345678-1234-5678-1234-567812345678} or {NULL UUID} if NULL was passed.
//! @param uuid The Uuid to write into the buffer as human-readable string
//! @param buffer Memory to write the string to. Must be at least \ref UUID_STRING_BUFFER_LENGTH bytes long.
void uuid_to_string(const Uuid *uuid, char *buffer);

40
src/libutil/iterator.c Normal file
View file

@ -0,0 +1,40 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/iterator.h"
#include "util/assert.h"
#include <stdbool.h>
void iter_init(Iterator* iter, IteratorCallback next, IteratorCallback prev, IteratorState state) {
*iter = (Iterator) {
.next = next,
.prev = prev,
.state = state
};
}
bool iter_next(Iterator* iter) {
UTIL_ASSERT(iter->next);
return iter->next(iter->state);
}
bool iter_prev(Iterator* iter) {
UTIL_ASSERT(iter->prev);
return iter->prev(iter->state);
}

View file

@ -0,0 +1,69 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/keyed_circular_cache.h"
#include "util/assert.h"
#include "util/math.h"
#include <string.h>
void keyed_circular_cache_init(KeyedCircularCache *c, KeyedCircularCacheKey *key_buffer,
void *data_buffer, size_t item_size, size_t total_items) {
UTIL_ASSERT(c);
UTIL_ASSERT(key_buffer);
UTIL_ASSERT(data_buffer);
UTIL_ASSERT(item_size);
*c = (KeyedCircularCache) {
.cache_keys = key_buffer,
.cache_data = (uint8_t *)data_buffer,
.item_size = item_size,
.total_items = total_items,
};
}
static uint8_t *prv_get_item_at_index(KeyedCircularCache *c, int index) {
return c->cache_data + (index * c->item_size);
}
void *keyed_circular_cache_get(KeyedCircularCache *c, KeyedCircularCacheKey key) {
// Optimize for accessing most recently pushed elements.
int idx = c->next_item_to_erase_idx;
for (unsigned int i = 0; i < c->total_items; i++) {
idx--;
if (idx < 0) {
idx += c->total_items;
}
if (c->cache_keys[idx] == key) {
return prv_get_item_at_index(c, idx);
}
}
return NULL;
}
void keyed_circular_cache_push(KeyedCircularCache *c, KeyedCircularCacheKey key,
const void *new_item) {
uint8_t *old_item = prv_get_item_at_index(c, c->next_item_to_erase_idx);
memcpy(old_item, new_item, c->item_size);
c->cache_keys[c->next_item_to_erase_idx] = key;
c->next_item_to_erase_idx++;
UTIL_ASSERT(c->next_item_to_erase_idx <= c->total_items);
if (c->next_item_to_erase_idx == c->total_items) {
c->next_item_to_erase_idx = 0;
}
}

356
src/libutil/list.c Normal file
View file

@ -0,0 +1,356 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/list.h"
#include "util/assert.h"
#include "util/logging.h"
#include <stddef.h>
#include <stdio.h>
void list_init(ListNode* node) {
node->next = NULL;
node->prev = NULL;
}
ListNode* list_insert_after(ListNode* node, ListNode* new_node) {
if (node == NULL) {
return new_node;
}
new_node->next = node->next;
new_node->prev = node;
if (node->next) {
node->next->prev = new_node;
}
node->next = new_node;
return new_node;
}
ListNode* list_insert_before(ListNode* node, ListNode* new_node) {
if (node == NULL) {
return new_node;
}
new_node->next = node;
new_node->prev = node->prev;
if (node->prev) {
node->prev->next = new_node;
}
node->prev = new_node;
return new_node;
}
ListNode* list_pop_head(ListNode *node) {
if (node == NULL) {
return NULL;
}
ListNode *head = list_get_head(node);
ListNode *new_head = head->next;
list_remove(head, NULL, NULL);
return new_head;
}
ListNode* list_pop_tail(ListNode *node) {
if (node == NULL) {
return NULL;
}
ListNode* tail = list_get_tail(node);
ListNode* new_tail = tail->prev;
list_remove(tail, NULL, NULL);
return new_tail;
}
void list_remove(ListNode* node, ListNode **head, ListNode **tail) {
if (node == NULL) {
return;
}
if (head && *head == node) {
*head = node->next;
}
if (tail && *tail == node) {
*tail = node->prev;
}
if (node->next) {
node->next->prev = node->prev;
}
if (node->prev) {
node->prev->next = node->next;
}
node->prev = NULL;
node->next = NULL;
}
ListNode* list_append(ListNode* node, ListNode* new_node) {
return list_insert_after(list_get_tail(node), new_node);
}
ListNode* list_prepend(ListNode* node, ListNode* new_node) {
return list_insert_before(list_get_head(node), new_node);
}
ListNode* list_get_next(ListNode* node) {
if (node == NULL) {
return NULL;
}
return node->next;
}
ListNode* list_get_prev(ListNode* node) {
if (node == NULL) {
return NULL;
}
return node->prev;
}
ListNode* list_get_tail(ListNode* node) {
if (node == NULL) {
return NULL;
}
while (node->next != NULL) {
node = node->next;
}
return node;
}
ListNode* list_get_head(ListNode* node) {
if (node == NULL) {
return NULL;
}
while (node->prev != NULL) {
node = node->prev;
}
return node;
}
bool list_is_head(const ListNode *node) {
if (!node) {
return false;
}
return !node->prev;
}
bool list_is_tail(const ListNode *node) {
if (!node) {
return false;
}
return !node->next;
}
uint32_t list_count_to_tail_from(ListNode* node) {
if (node == NULL) {
return 0;
}
uint32_t count = 1;
while ((node = node->next) != NULL) {
++count;
}
return count;
}
uint32_t list_count_to_head_from(ListNode *node) {
if (node == NULL) {
return 0;
}
uint32_t count = 1;
while ((node = node->prev) != NULL) {
++count;
}
return count;
}
uint32_t list_count(ListNode* node) {
return list_count_to_tail_from(list_get_head(node));
}
ListNode* list_get_at(ListNode *node, int32_t index) {
while (node != NULL && index != 0) {
if (index > 0) {
node = node->next;
index--;
} else {
node = node->prev;
index++;
}
}
return node;
}
ListNode* list_sorted_add(ListNode *node, ListNode *new_node, Comparator comparator, bool ascending) {
if (node == NULL) {
return new_node;
}
if (new_node == NULL) {
return node;
}
ListNode * const head = node;
for(;;) {
int order = comparator(node, new_node);
if (!ascending) {
order = -order;
}
if (order < 0) {
list_insert_before(node, new_node);
if (node == head) {
return new_node;
} else {
return head;
}
}
ListNode *next = node->next;
if (next == NULL) {
list_insert_after(node, new_node);
return head;
}
node = next;
}
}
bool list_contains(const ListNode *node, const ListNode *node_to_search) {
if (node == NULL || node_to_search == NULL) {
return false;
}
while (node) {
if (node == node_to_search) {
return true;
}
node = node->next;
}
return false;
}
ListNode* list_find(ListNode *node, ListFilterCallback filter_callback, void *data) {
if (node == NULL) {
return NULL;
}
ListNode *cursor = node;
do {
if (filter_callback(cursor, data)) {
return cursor;
}
} while ((cursor = cursor->next));
return NULL;
}
ListNode* list_find_next(ListNode *node, ListFilterCallback filter_callback, bool wrap_around, void *data) {
if (node == NULL) {
return NULL;
}
ListNode *cursor = node;
while ((cursor = cursor->next)) {
if (filter_callback(cursor, data)) {
return cursor;
}
}
if (wrap_around == false) {
return NULL;
}
cursor = list_get_head(node);
while (cursor) {
if (filter_callback(cursor, data)) {
return cursor;
}
// We're back at where we started and even <node> itself doesn't match the filter
if (cursor == node) {
return NULL;
}
cursor = cursor->next;
}
UTIL_ASSERT(0);
return NULL;
}
ListNode* list_find_prev(ListNode *node, ListFilterCallback filter_callback, bool wrap_around, void *data) {
if (node == NULL) {
return NULL;
}
ListNode *cursor = node;
while ((cursor = cursor->prev)) {
if (filter_callback(cursor, data)) {
return cursor;
}
}
if (wrap_around == false) {
return NULL;
}
cursor = list_get_tail(node);
while (cursor) {
if (filter_callback(cursor, data)) {
return cursor;
}
// We're back at where we started and even <node> itself doesn't match the filter
if (cursor == node) {
return NULL;
}
cursor = cursor->prev;
}
UTIL_ASSERT(0);
return NULL;
}
ListNode *list_concatenate(ListNode *restrict list_a, ListNode *restrict list_b) {
ListNode *head_a = list_get_head(list_a);
if (list_b == NULL) {
return head_a;
}
ListNode *head_b = list_get_head(list_b);
if (list_a == NULL) {
return head_b;
}
if (head_a == head_b) {
// list b is already in list a!
return head_a;
}
ListNode *tail_a = list_get_tail(list_a);
head_b->prev = tail_a;
tail_a->next = head_b;
return head_a;
}
void list_foreach(ListNode *head, ListForEachCallback each_cb, void *context) {
if (!each_cb) {
return;
}
ListNode *iter = head;
while (iter) {
// Save off a pointer so the client to this function can destroy the node (useful for deinits)
ListNode *next = iter->next;
if (!each_cb(iter, context)) {
return;
}
iter = next;
}
}
void list_debug_dump(ListNode *head) {
ListNode *iter = head;
char buffer[30];
while (iter) {
snprintf(buffer, sizeof(buffer), "node %p (%p, %p)", iter, iter->prev, iter->next);
UTIL_LOG(buffer);
iter = iter->next;
}
}

95
src/libutil/math.c Normal file
View file

@ -0,0 +1,95 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/math.h"
#include <stdint.h>
#include <stdbool.h>
int32_t sign_extend(uint32_t a, int bits) {
if (bits == 32) {
return a;
}
// http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend
int const m = 1U << (bits - 1); // mask can be pre-computed if b is fixed
a = a & ((1U << bits) - 1); // (Skip this if bits in x above position b are already zero.)
return (a ^ m) - m;
}
int32_t serial_distance32(uint32_t a, uint32_t b) {
return serial_distance(a, b, 32);
}
int32_t serial_distance(uint32_t a, uint32_t b, int bits) {
// See https://en.wikipedia.org/wiki/Serial_Number_Arithmetic
const int64_t a_minus_b = a - b;
const int64_t b_minus_a = b - a;
const bool a_is_earlier_than_b = (a < b && b_minus_a < (1 << (bits - 1))) || (a > b && a_minus_b > (1 << (bits - 1)));
return sign_extend(a_is_earlier_than_b ? -a_minus_b : b_minus_a, bits);
}
int ceil_log_two(uint32_t n) {
// clz stands for Count Leading Zeroes. We use it to find the MSB
int msb = 31 - __builtin_clz(n);
// popcount counts the number of set bits in a word (1's)
bool power_of_two = __builtin_popcount(n) == 1;
// if not exact power of two, use the next power of two
// we want to err on the side of caution and want to
// always round up
return ((power_of_two) ? msb : (msb + 1));
}
//! newton's method for floor(sqrt(x)) -> should always converge
int32_t integer_sqrt(int64_t x) {
if (x < 0) {
return 0;
}
int64_t last_res = 0x3fff;
uint16_t iterations = 0;
while ((last_res > 0) && (iterations < 15)) {
last_res = ((x / last_res) + last_res)/2;
iterations++;
}
return (last_res);
}
uint32_t next_exponential_backoff(uint32_t *attempt, uint32_t initial_value, uint32_t max_value) {
if (*attempt > 31) {
return max_value;
}
uint32_t backoff_multiplier = 0x1 << (*attempt)++;
uint32_t next_value = initial_value * backoff_multiplier;
return MIN(next_value, max_value);
}
uint32_t gcd(uint32_t a, uint32_t b) {
// Infinite loops are bad
if (a == 0 || b == 0) {
return 0;
}
// Euclidean algorithm, yay!
while (a != b) {
if (a > b) {
a -= b;
} else {
b -= a;
}
}
return a;
}

50
src/libutil/math_fixed.c Normal file
View file

@ -0,0 +1,50 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/assert.h"
#include "util/math_fixed.h"
Fixed_S64_32 math_fixed_recursive_filter(Fixed_S64_32 x,
int num_input_coefficients, int num_output_coefficients,
const Fixed_S64_32 *cb, const Fixed_S64_32 *ca,
Fixed_S64_32 *state_x, Fixed_S64_32 *state_y) {
UTIL_ASSERT(num_input_coefficients >= 1);
// shift the input over by one
for (int k = num_input_coefficients - 1; k > 0; k--) {
state_x[k] = state_x[k - 1];
}
state_x[0] = x;
// Factor in the x * b elements
Fixed_S64_32 ytmp = Fixed_S64_32_mul(cb[0], state_x[0]);
for (int i = 1; i < num_input_coefficients; i++) {
ytmp = Fixed_S64_32_add(ytmp, Fixed_S64_32_mul(cb[i], state_x[i]));
}
// Factor in the y * a coeficients
for (int i = 0; i < num_output_coefficients; i++) {
ytmp = Fixed_S64_32_sub(ytmp, Fixed_S64_32_mul(ca[i], state_y[i]));
}
// shift the y output elements
for (int k = num_output_coefficients - 1; k > 0; k--) {
state_y[k] = state_y[k-1];
}
state_y[0] = ytmp;
return ytmp;
}

30
src/libutil/order.c Normal file
View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/order.h"
int uint32_comparator(void *a, void *b) {
uint32_t A = *(uint32_t *)a;
uint32_t B = *(uint32_t *)b;
if (B > A) {
return 1;
} else if (B < A) {
return -1;
} else {
return 0;
}
}

49
src/libutil/platform.c Normal file
View file

@ -0,0 +1,49 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/assert.h"
#include "util/logging.h"
#include "util/rand32.h"
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
// Below are some default implementations for system-specific functions required by libutil.
// These functions assume a working C standard library is linked into the program.
// For programs where this isn't the case (e.g. the Pebble FW),
// alternate implementations need to be provided.
// The functions are defined as WEAK so they may be easily overridden.
// If you are getting link errors due to printf not being defined, you probably
// need to provide your own implementation of the functions below.
WEAK void util_log(const char *filename, int line, const char *string) {
printf("%s:%d %s\n", filename, line, string);
}
WEAK void util_dbgserial_str(const char *string) {
printf("%s\n", string);
}
WEAK NORETURN util_assertion_failed(const char *filename, int line) {
util_log(filename, line, "*** UTIL ASSERT FAILED");
exit(EXIT_FAILURE);
}
WEAK uint32_t rand32(void) {
return ((uint32_t)rand() << 1) + (uint32_t)rand;
}

44
src/libutil/sort.c Normal file
View file

@ -0,0 +1,44 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <util/sort.h>
#include <stdint.h>
#include <stddef.h>
static void prv_swap(void *a, void *b, size_t elem_size) {
uint8_t *a_ptr = (uint8_t *)a;
uint8_t *b_ptr = (uint8_t *)b;
for (size_t i = 0; i < elem_size; i++) {
uint8_t tmp = *a_ptr;
*a_ptr++ = *b_ptr;
*b_ptr++ = tmp;
}
}
void sort_bubble(void *array, size_t num_elem, size_t elem_size, SortComparator comp) {
// as the number of sessions is expected to be small (<=16), and we don't seem to have a generic
// sort implementation, we do a simple bubble sort here
for (uint32_t i = 0; i + 1 < num_elem; i++) {
for (uint32_t j = i + 1; j < num_elem; j++) {
uint8_t *val1 = ((uint8_t *)array) + (i * elem_size);
uint8_t *val2 = ((uint8_t *)array) + (j * elem_size);
if (comp(val1, val2) > 0) {
prv_swap(val1, val2, elem_size);
}
}
}
}

188
src/libutil/string.c Normal file
View file

@ -0,0 +1,188 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/string.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
const char *string_strip_leading_whitespace(const char *string) {
const char *result_string = string;
while (*result_string != '\0') {
if (*result_string != ' ' &&
*result_string != '\n') {
break;
}
result_string++;
}
return result_string;
}
void string_strip_trailing_whitespace(const char *string, char *string_out) {
int string_len = strlen(string);
bool trim = true;
for (int i = string_len; i >= 0; i--) {
if (trim &&
(string[i] == ' ' || string[i] == '\n' || string[i] == '\0')) {
string_out[i] = '\0';
} else {
trim = false;
string_out[i] = string[i];
}
}
}
const char *bool_to_str(bool b) {
if (b) {
return "yes";
} else {
return "no";
}
}
void itoa(uint32_t num, char *buffer, int buffer_length) {
if (buffer_length < 11) {
return;
}
*buffer++ = '0';
*buffer++ = 'x';
for (int i = 7; i >= 0; --i) {
uint32_t digit = (num & (0xf << (i * 4))) >> (i * 4);
char c;
if (digit < 0xa) {
c = '0' + digit;
} else if (digit < 0x10) {
c = 'a' + (digit - 0xa);
} else {
c = ' ';
}
*buffer++ = c;
}
*buffer = '\0';
}
void string_reverse(char *str) {
uint8_t i = 0;
int8_t j = strlen(str) - 1;
for (i = 0; i < j; i++, j--) {
char c = str[i];
str[i] = str[j];
str[j] = c;
}
}
/* itoa: convert n to characters in s */
void itoa_int(int n, char *str, int base) {
bool neg;
if ((neg = (n < 0))) { /* record sign */
n = -n; /* make n positive */
}
int i = 0;
do { /* generate digits in reverse order */
str[i++] = (n % base) + '0'; /* get next digit */
} while ((n /= base) > 0); /* delete it */
if (neg) {
str[i++] = '-'; /* append sign */
}
str[i] = '\0';
string_reverse(str);
}
static int8_t ascii_hex_to_int(const uint8_t c) {
if (isdigit(c)) return c - '0';
if (isupper(c)) return (c - 'A') + 10;
if (islower(c)) return (c - 'a') + 10;
return -1;
}
static uint8_t ascii_hex_to_uint(const uint8_t msb, const uint8_t lsb) {
return 16 * ascii_hex_to_int(msb) + ascii_hex_to_int(lsb);
}
uintptr_t str_to_address(const char *address_str) {
char *endptr;
uintptr_t address = strtoul(address_str, &endptr, 0);
if (*endptr != '\0') { // A non-address character encountered
return -1;
}
return address;
}
bool convert_bt_addr_hex_str_to_bd_addr(const char *hex_str, uint8_t *bd_addr, const unsigned int bd_addr_size) {
const int len = strlen(hex_str);
if (len != 12) {
return false;
}
uint8_t* src = (uint8_t*) hex_str;
uint8_t* dest = bd_addr + bd_addr_size - 1;
for (unsigned int i = 0; i < bd_addr_size; ++i, src += 2, --dest) {
*dest = ascii_hex_to_uint(src[0], src[1]);
}
return true;
}
void concat_str_int(const char *str, uint32_t num, char *buf, uint8_t buf_len) {
uint8_t str_len = strlen(str);
strncpy(buf, str, str_len);
itoa_int(num, buf + str_len, 10);
}
void toupper_str(char *str) {
int len = strlen(str);
for (int i = 0; i < len; i++) {
str[i] = toupper((unsigned char)str[i]);
}
}
void byte_stream_to_hex_string(char *out_buf, size_t out_buf_len,
const uint8_t *byte_stream, size_t byte_stream_len, bool print_backward) {
size_t bytes_left = byte_stream_len;
if (print_backward) {
byte_stream += (byte_stream_len - 1); // addr of the last element
}
while (out_buf_len >= 3 /* 2 hex digits, plus '\0' */
&& bytes_left > 0) {
snprintf(out_buf, out_buf_len, "%02x", *byte_stream);
out_buf += 2;
out_buf_len -= 2;
byte_stream += (print_backward) ? -1 : 1;
bytes_left -= 1;
}
}
// -------------------------------------------------------------------------------
void safe_strcat(char* dst, const char* src, int dst_space) {
int remaining = dst_space - strlen(dst);
if (dst_space > 0) {
strncat(dst, src, remaining);
}
dst[dst_space-1] = 0;
}

623
src/libutil/trig.c Normal file
View file

@ -0,0 +1,623 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/trig.h"
#include "util/math.h"
// lookup points on the first quarter sin wave
// generated by tintin/tools/trigTable.py
static const uint16_t SIN_LOOKUP[] = {
0,
401,
801,
1202,
1602,
2002,
2403,
2803,
3203,
3603,
4003,
4403,
4802,
5202,
5601,
6000,
6399,
6797,
7195,
7593,
7991,
8389,
8786,
9182,
9579,
9975,
10371,
10766,
11161,
11555,
11949,
12343,
12736,
13129,
13521,
13913,
14304,
14694,
15085,
15474,
15863,
16251,
16639,
17026,
17413,
17798,
18184,
18568,
18952,
19335,
19717,
20099,
20480,
20860,
21239,
21618,
21996,
22372,
22749,
23124,
23498,
23872,
24244,
24616,
24987,
25356,
25725,
26093,
26460,
26826,
27191,
27555,
27918,
28280,
28641,
29000,
29359,
29717,
30073,
30428,
30783,
31136,
31487,
31838,
32188,
32536,
32883,
33229,
33574,
33917,
34259,
34600,
34939,
35278,
35614,
35950,
36284,
36617,
36949,
37279,
37608,
37935,
38261,
38585,
38908,
39230,
39550,
39869,
40186,
40502,
40816,
41128,
41439,
41749,
42057,
42363,
42668,
42971,
43273,
43573,
43871,
44168,
44463,
44757,
45048,
45338,
45627,
45913,
46198,
46482,
46763,
47043,
47321,
47597,
47872,
48144,
48415,
48684,
48951,
49217,
49480,
49742,
50002,
50260,
50516,
50770,
51023,
51273,
51521,
51768,
52013,
52255,
52496,
52735,
52972,
53207,
53440,
53670,
53899,
54126,
54351,
54574,
54794,
55013,
55230,
55444,
55657,
55867,
56076,
56282,
56486,
56688,
56888,
57086,
57281,
57475,
57666,
57856,
58043,
58228,
58410,
58591,
58769,
58945,
59119,
59291,
59461,
59628,
59793,
59956,
60116,
60275,
60431,
60585,
60736,
60886,
61033,
61177,
61320,
61460,
61598,
61734,
61867,
61998,
62126,
62253,
62377,
62498,
62618,
62735,
62850,
62962,
63072,
63179,
63285,
63388,
63488,
63586,
63682,
63775,
63866,
63955,
64041,
64125,
64206,
64286,
64362,
64436,
64508,
64578,
64645,
64709,
64771,
64831,
64889,
64943,
64996,
65046,
65094,
65139,
65182,
65222,
65260,
65295,
65328,
65359,
65387,
65413,
65436,
65457,
65475,
65491,
65504,
65515,
65524,
65530,
65534,
65535
};
// lookup points on the first quarter atan wave
// generated by tintin/tools/trigTable.py
static const uint16_t ATAN_LOOKUP[] = {
0,
328,
648,
976,
1296,
1624,
1944,
2272,
2600,
2920,
3248,
3568,
3896,
4216,
4544,
4864,
5192,
5512,
5832,
6160,
6480,
6800,
7128,
7448,
7768,
8088,
8416,
8736,
9056,
9376,
9696,
10016,
10336,
10656,
10976,
11296,
11616,
11928,
12248,
12568,
12880,
13200,
13520,
13832,
14152,
14464,
14776,
15096,
15408,
15720,
16032,
16344,
16656,
16968,
17280,
17592,
17904,
18216,
18520,
18832,
19136,
19448,
19752,
20056,
20368,
20672,
20976,
21280,
21584,
21888,
22192,
22488,
22792,
23096,
23392,
23696,
23992,
24288,
24584,
24888,
25184,
25480,
25768,
26064,
26360,
26656,
26944,
27240,
27528,
27816,
28104,
28400,
28688,
28976,
29256,
29544,
29832,
30112,
30400,
30680,
30960,
31248,
31528,
31808,
32088,
32368,
32640,
32920,
33192,
33472,
33744,
34024,
34296,
34568,
34840,
35112,
35376,
35648,
35920,
36184,
36448,
36720,
36984,
37248,
37512,
37776,
38040,
38296,
38560,
38816,
39080,
39336,
39592,
39848,
40104,
40360,
40616,
40864,
41120,
41368,
41624,
41872,
42120,
42368,
42616,
42864,
43112,
43352,
43600,
43840,
44088,
44328,
44568,
44808,
45048,
45288,
45520,
45760,
45992,
46232,
46464,
46696,
46928,
47160,
47392,
47624,
47856,
48080,
48312,
48536,
48768,
48992,
49216,
49440,
49664,
49880,
50104,
50328,
50544,
50768,
50984,
51200,
51416,
51632,
51848,
52064,
52272,
52488,
52696,
52912,
53120,
53328,
53536,
53744,
53952,
54160,
54368,
54568,
54776,
54976,
55184,
55384,
55584,
55784,
55984,
56184,
56384,
56576,
56776,
56968,
57168,
57360,
57552,
57744,
57936,
58128,
58320,
58512,
58704,
58888,
59080,
59264,
59448,
59632,
59824,
60008,
60184,
60368,
60552,
60736,
60912,
61096,
61272,
61456,
61632,
61808,
61984,
62160,
62336,
62512,
62680,
62856,
63032,
63200,
63368,
63544,
63712,
63880,
64048,
64216,
64384,
64552,
64720,
64880,
65048,
65208,
65376,
65535,
};
int32_t sin_lookup(int32_t angle) {
int32_t mult = 1;
// modify the input angle and output multiplier for use in a first quadrent sine lookup
if (angle < 0) {
angle = -angle;
mult = -mult;
}
if (angle >= TRIG_MAX_ANGLE) {
angle %= TRIG_MAX_ANGLE;
}
if (angle >= TRIG_MAX_ANGLE / 2) {
mult = -mult;
angle -= TRIG_MAX_ANGLE / 2;
}
if (angle >= TRIG_MAX_ANGLE / 4 && angle <= TRIG_MAX_ANGLE / 2) {
angle = TRIG_MAX_ANGLE / 2 - angle;
}
// if I can interpolate linearly
int32_t lookup_angle = angle * 4 / 0xff;
if ((uint32_t)(lookup_angle + 1) < sizeof(SIN_LOOKUP) / sizeof(int32_t)) {
return mult * (SIN_LOOKUP[lookup_angle] + ((angle * 4) % 0xff) * (SIN_LOOKUP[lookup_angle + 1] - SIN_LOOKUP[lookup_angle]) / 0xff);
}
return mult * SIN_LOOKUP[lookup_angle];
}
int32_t cos_lookup(int32_t angle) {
return sin_lookup(angle + TRIG_MAX_ANGLE / 4);
}
#define ATAN_LUT_STRIDE 0xff
int32_t atan2_lookup(int16_t y, int16_t x) {
// inspired by http://www.coranac.com/documents/arctangent/
if (y == 0) {
return (x >= 0 ? 0 : TRIG_PI);
}
// moving x and y values into bigger containers to avoid overflows
int32_t x_wide = x;
int32_t y_wide = y;
int32_t angle = 0;
// Find octant
if (y_wide < 0) {
x_wide = -x_wide;
y_wide = -y_wide;
angle += 4;
}
if (x_wide <= 0) {
uint32_t t = x_wide;
x_wide = y_wide;
y_wide = -t;
angle += 2;
}
if (x_wide <= y_wide) {
uint32_t t = y_wide - x_wide;
x_wide = x_wide + y_wide;
y_wide = t;
angle += 1;
}
angle *= TRIG_PI / 4;
uint32_t ratio = (y_wide << TRIG_FP) / x_wide;
return (angle + ATAN_LOOKUP[ratio / ATAN_LUT_STRIDE] / 8);
}
uint32_t normalize_angle(int32_t angle) {
uint32_t normalized_angle = ABS(angle) % TRIG_MAX_ANGLE;
if (angle < 0) {
normalized_angle = TRIG_MAX_ANGLE - normalized_angle;
}
return normalized_angle;
}

77
src/libutil/uuid.c Normal file
View file

@ -0,0 +1,77 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "util/uuid.h"
#include "util/rand32.h"
#include <inttypes.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
static const Uuid system_uuid = UUID_SYSTEM;
static const Uuid invalid_uuid = UUID_INVALID_INIT;
void uuid_generate(Uuid *uuid_out) {
uint8_t uuid_bytes[UUID_SIZE];
uint32_t *uuid_words = (uint32_t*)uuid_bytes;
for (size_t i = 0; i < UUID_SIZE / sizeof(uint32_t); i++) {
uuid_words[i] = rand32();
}
// set the version bits
uuid_bytes[6] = (uuid_bytes[6] & ~0xF0) | 0x40;
// set the reserved bits
uuid_bytes[8] = (uuid_bytes[8] & ~0xC0) | 0x80;
// Use BE so that the bytes for version and reserved are correctly placed.
*uuid_out = UuidMakeFromBEBytes(uuid_bytes);
}
bool uuid_equal(const Uuid *uu1, const Uuid *uu2) {
if (uu1 == NULL || uu2 == NULL) {
return false;
}
return memcmp(uu1, uu2, sizeof(Uuid)) == 0;
}
bool uuid_is_system(const Uuid *uuid) {
return uuid_equal(uuid, &system_uuid);
}
bool uuid_is_invalid(const Uuid *uuid) {
return !uuid || uuid_equal(uuid, &invalid_uuid);
}
void uuid_to_string(const Uuid *uuid, char *buffer) {
if (!uuid) {
strcpy(buffer, "{NULL UUID}");
return;
}
// For reference...
// {12345678-1234-5678-1234-567812345678}
*buffer++ = '{';
for (uint32_t i = 0; i < sizeof(Uuid); i++) {
if ((i >= 4) && (i <= 10) && ((i % 2) == 0)) {
*buffer++ = '-';
}
buffer += snprintf(buffer, 3, "%02"PRIx8, ((uint8_t *)uuid)[i]);
}
*buffer++ = '}';
*buffer = '\0';
}

24
src/libutil/wscript Normal file
View file

@ -0,0 +1,24 @@
import waftools
def build(bld):
sources = bld.path.ant_glob('**/*.c')
def build_libutil(target, env):
# Build the libutil directory using firmware environment
bld.stlib(source=sources,
target=target,
includes=['.', 'includes'],
use='pblibc_includes',
env=env.derive())
bld(export_includes=['includes'], name='libutil_includes')
if (bld.variant not in ('test', 'test_rocky_emx')):
build_libutil('libutil-cm0', bld.all_envs['cortex-m0'])
build_libutil('libutil', bld.env)
build_libutil('libutil-32bit', bld.all_envs['32bit'])
# vim:filetype=python