# Key-Value Pool

## Purpose

<span style="white-space: pre-wrap;">The </span>`<span class="editor-theme-code">kv_pool</span>`<span style="white-space: pre-wrap;"> module implements a </span>**fixed-key key-value store backed by a custom memory pool**.

It is designed for systems where:

- keys are known as integer indices in a fixed range
- values are variable-sized blobs of bytes
- dynamic allocation from the general heap is undesirable or unavailable
- memory must come from a caller-provided region
- multiple threads may access the store concurrently

The module combines two things:

- <span style="white-space: pre-wrap;">a </span>**lookup table**<span style="white-space: pre-wrap;"> that maps keys to stored values</span>
- <span style="white-space: pre-wrap;">an internal </span>**heap allocator**<span style="white-space: pre-wrap;"> that manages the memory used by those values</span>

The result is a storage system where each key corresponds to one slot, and each valid slot points to a block allocated from the pool’s private heap.

<p class="callout info"><span style="white-space: pre-wrap;">This is not a dictionary in the desktop-software sense. Keys are not hashed, compared, or discovered dynamically. A key is just an </span>**index into a preallocated slot table**.</p>

## What problem this solves

This module exists to store variable-sized values in a memory-constrained system without relying on the standard heap.

It solves these problems:

- fixed set of logical keys, but variable-sized data per key
- need to provide memory externally
- need to safely read and update values across threads
- need to reclaim storage when a key is removed
- need predictable ownership of all stored data

<p class="callout info"><span style="white-space: pre-wrap;">Instead of calling </span>`<span class="editor-theme-code">malloc()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">free()</span>`<span style="white-space: pre-wrap;"> from the general runtime allocator, the module manages a private heap inside caller-provided memory.</span></p>

That gives the application explicit control over:

- where memory lives
- how large the pool is
- how many keys exist
- whether metadata and data live together or in different memory regions

## High-Level Design

The module consists of two main parts.

### Lookup table

<span style="white-space: pre-wrap;">Each key corresponds to one </span>`<span class="editor-theme-code">kv_slot</span>`.

A slot contains:

- a per-slot lock
- a pointer to the stored data block
- the size of that block
- a validity flag

If a slot is valid, the key currently has stored data.

If a slot is invalid, the key is empty.

### Internal heap

Actual data bytes are stored inside a custom heap managed by the module.

This heap:

- lives in caller-provided memory
- uses a free-list allocator
- allocates variable-sized blocks
- coalesces adjacent free blocks when freeing

This means the slot table stores metadata only. The actual value bytes are stored elsewhere in the pool heap.

## Memory model

The pool manages two logical memory regions:

- **lookup table memory**
- **data heap memory**

These can be provided in two ways:

### Contiguous mode

<span style="white-space: pre-wrap;">One big memory block is given to </span>`<span class="editor-theme-code">kv_pool_init()</span>`. The module splits it into:

1. lookup table
2. data heap

### Fragmented mode

<span style="white-space: pre-wrap;">Separate memory regions are given to </span>`<span class="editor-theme-code">kv_pool_init_fragmented()</span>`. This allows the lookup table and data heap to live in different memory banks.

That can be useful when, for example:

- metadata should live in fast SRAM
- bulk value storage should live in larger but slower RAM

## Public API overview

The public API consists of:

- data structures:
    - `<span class="editor-theme-code">kv_slot</span>`
    - `<span class="editor-theme-code">kv_header</span>`
    - `<span class="editor-theme-code">kv_pool</span>`
- macros:
    - `<span class="editor-theme-code">KV_ALIGNMENT</span>`
    - `<span class="editor-theme-code">ALIGN(x)</span>`
    - `<span class="editor-theme-code">MINIMUM_BLOCK_SIZE</span>`
    - `<span class="editor-theme-code">LOOKUP_TABLE_SIZE(x)</span>`
- initialization:
    - `<span class="editor-theme-code">kv_pool_init_fragmented()</span>`
    - `<span class="editor-theme-code">kv_pool_init()</span>`
- key operations:
    - `<span class="editor-theme-code">kv_pool_get()</span>`
    - `<span class="editor-theme-code">kv_pool_write()</span>`
    - `<span class="editor-theme-code">kv_pool_insert()</span>`
    - `<span class="editor-theme-code">kv_pool_remove()</span>`
    - `<span class="editor-theme-code">kv_pool_is_index_valid()</span>`
- allocator functions:
    - `<span class="editor-theme-code">kv_pool_allocate()</span>`
    - `<span class="editor-theme-code">kv_pool_free()</span>`

The last two are currently exposed in the header, although they behave more like internal allocator primitives than ordinary user-facing API.

## Data structures

### `<span class="editor-theme-code">kv_slot</span>`

```c
typedef struct {
  atomic_flag slot_lock;
  void *data_ptr;
  size_t data_size;
  bool is_valid;
} kv_slot;
```

#### Purpose

Represents the metadata for one key.

##### Fields

`<span class="editor-theme-code">slot_lock</span>`

A spinlock protecting this slot’s metadata and associated data access.

Used to synchronize operations on a single key.

`<span class="editor-theme-code">data_ptr</span>`

Pointer to the allocated data block in the internal heap.

The caller must not free or reallocate this pointer directly.

`<span class="editor-theme-code">data_size</span>`

Size in bytes of the stored value.

`<span class="editor-theme-code">is_valid</span>`

Whether this slot currently contains valid data.

If false, the key is considered empty.

#### Important note

<p class="callout info">The key itself is not stored in the slot. The key is simply the slot’s index in the lookup table.</p>

### `<span class="editor-theme-code">kv_header</span>`

```c
typedef struct kv_header {
  size_t size;
  union {
    struct kv_header *next_free;
    char data[1];
  } as;
} kv_header;
```

#### Purpose

Header for blocks in the internal heap.

#### Role

<span style="white-space: pre-wrap;">When a block is free, </span>`<span class="editor-theme-code">as.next_free</span>`<span style="white-space: pre-wrap;"> links it into the free list.</span>

<span style="white-space: pre-wrap;">When a block is allocated, </span>`<span class="editor-theme-code">as.data</span>`<span style="white-space: pre-wrap;"> is the start of the user-visible payload.</span>

#### <span style="white-space: pre-wrap;">Meaning of </span>`<span class="editor-theme-code">size</span>`

`<span class="editor-theme-code">size</span>`<span style="white-space: pre-wrap;"> is the total size of the block, including the header and payload region.</span>

This is important for:

- pointer arithmetic
- block splitting
- coalescing adjacent free blocks

---

### `<span class="editor-theme-code">kv_pool</span>`

```c
typedef struct {
  void *pool_start;
  size_t pool_size;

  atomic_flag heap_lock;
  void (*delay)(void);
  kv_header *free_list_head;

  size_t max_keys;
  kv_slot *lookup_table;
} kv_pool;
```

#### Purpose

Represents the entire key-value pool.

#### Fields

`<span class="editor-theme-code">pool_start</span>`

Start address of the data heap region.

`<span class="editor-theme-code">pool_size</span>`

Size of the data heap region in bytes.

`<span class="editor-theme-code">heap_lock</span>`

Spinlock protecting heap allocator operations.

`<span class="editor-theme-code">delay</span>`

Callback invoked while waiting for a lock.

This is used during busy-waiting to avoid a pure tight spin.

`<span class="editor-theme-code">free_list_head</span>`

Head of the free-list allocator.

`<span class="editor-theme-code">max_keys</span>`

Maximum number of keys supported by this pool.

Valid keys are:

```c
0 <= key < max_keys
```

`<span class="editor-theme-code">lookup_table</span>`

Pointer to the slot array.

## Alignment and size macros

### `<span class="editor-theme-code">KV_ALIGNMENT</span>`

```c
#define KV_ALIGNMENT 16
```

All allocations are aligned to this boundary.

This affects:

- payload placement
- allocator block sizes
- pointer arithmetic

If this value changes, allocator behavior changes with it.

---

### `<span class="editor-theme-code">ALIGN(x)</span>`

```c
#define ALIGN(x) (((x) + (KV_ALIGNMENT - 1)) & ~(KV_ALIGNMENT - 1))
```

<span style="white-space: pre-wrap;">Rounds a size up to the next </span>`<span class="editor-theme-code">KV_ALIGNMENT</span>`<span style="white-space: pre-wrap;"> boundary.</span>

Used by the allocator.

---

### `<span class="editor-theme-code">MINIMUM_BLOCK_SIZE</span>`

```c
#define MINIMUM_BLOCK_SIZE sizeof(kv_header) + 1
```

Minimum block size allowed in the heap.

Used to decide whether a free block can be split.

The allocator will not create a leftover free fragment smaller than this.

---

### `<span class="editor-theme-code">LOOKUP_TABLE_SIZE(x)</span>`

```c
#define LOOKUP_TABLE_SIZE(x) (sizeof(kv_slot) * (x))
```

<span style="white-space: pre-wrap;">Returns the number of bytes required for a lookup table supporting </span>`<span class="editor-theme-code">x</span>`<span style="white-space: pre-wrap;"> keys.</span>

Used during initialization and memory size validation.

## Initialization APIs

### `<span class="editor-theme-code">kv_pool_init_fragmented()</span>`

```c
result_t kv_pool_init_fragmented(void *lookup_table,
                                 size_t lookup_table_size,
                                 size_t max_keys,
                                 void *pool_data,
                                 size_t pool_size,
                                 kv_pool *pool,
                                 void (*delay)(void));
```

#### Purpose

Initializes a pool from two separate memory regions:

- one for slot metadata
- one for heap storage

#### Parameters

`<span class="editor-theme-code">lookup_table</span>`

<span style="white-space: pre-wrap;">Memory for the </span>`<span class="editor-theme-code">kv_slot</span>`<span style="white-space: pre-wrap;"> array.</span>

`<span class="editor-theme-code">lookup_table_size</span>`

Size of the lookup table memory region in bytes.

`<span class="editor-theme-code">max_keys</span>`

Maximum number of keys.

`<span class="editor-theme-code">pool_data</span>`

Memory for the heap region.

`<span class="editor-theme-code">pool_size</span>`

Size of the heap region in bytes.

`<span class="editor-theme-code">pool</span>`

Output pool structure to initialize.

`<span class="editor-theme-code">delay</span>`

Function called while waiting for spinlocks.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if required pointers are null or </span>`<span class="editor-theme-code">max_keys == 0</span>`
- `<span class="editor-theme-code">RESULT_ERR_BUFFER_TOO_SMALL</span>`<span style="white-space: pre-wrap;"> if:</span>
    - heap is too small
    - lookup table region is too small

#### What it does

1. validates inputs
2. <span style="white-space: pre-wrap;">clears both memory regions with </span>`<span class="editor-theme-code">memset</span>`
3. sets up the lookup table
4. sets up the free-list heap as one large free block
5. clears locks
6. marks all slots invalid

#### Important lifetime rule

<p class="callout warning">The memory regions provided to this function must remain valid for the entire lifetime of the pool.</p>

The module does not copy them.

---

### `<span class="editor-theme-code">kv_pool_init()</span>`

```c
result_t kv_pool_init(void *data,
                      size_t data_size,
                      size_t max_keys,
                      kv_pool *pool,
                      void (*delay)(void));
```

#### Purpose

Initializes a pool from one contiguous memory block.

#### Parameters

`<span class="editor-theme-code">data</span>`

Start of the full memory region.

`<span class="editor-theme-code">data_size</span>`

Total size of the region.

`<span class="editor-theme-code">max_keys</span>`

Number of keys.

`<span class="editor-theme-code">pool</span>`

Output pool structure.

`<span class="editor-theme-code">delay</span>`

Lock wait callback.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_BUFFER_TOO_SMALL</span>`<span style="white-space: pre-wrap;"> if total memory is insufficient</span>
- <span style="white-space: pre-wrap;">any error returned by </span>`<span class="editor-theme-code">kv_pool_init_fragmented()</span>`

#### What it does

It computes:

- <span style="white-space: pre-wrap;">lookup table size = </span>`<span class="editor-theme-code">LOOKUP_TABLE_SIZE(max_keys)</span>`
- <span style="white-space: pre-wrap;">heap start = </span>`<span class="editor-theme-code">data + LOOKUP_TABLE_SIZE(max_keys)</span>`
- heap size = remaining bytes

<span style="white-space: pre-wrap;">Then it delegates to </span>`<span class="editor-theme-code">kv_pool_init_fragmented()</span>`.

#### When to use it

Use this function when you want simple setup from one static buffer.

<span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">kv_pool_init_fragmented()</span>`<span style="white-space: pre-wrap;"> when memory placement matters.</span>

## Key operations

### `<span class="editor-theme-code">kv_pool_get()</span>`

```c
result_t kv_pool_get(kv_pool *pool, int key, void *buffer, size_t *buffer_size);
```

#### Purpose

Copies the value for a key into a caller-provided buffer.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to read.

`<span class="editor-theme-code">buffer</span>`

Destination buffer for copied data.

`<span class="editor-theme-code">buffer_size</span>`

Input/output parameter.

- <span style="white-space: pre-wrap;">on input: capacity of </span>`<span class="editor-theme-code">buffer</span>`
- on output:
    - actual copied size on success
    - required size if buffer is too small

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if </span>`<span class="editor-theme-code">pool == NULL</span>`<span style="white-space: pre-wrap;"> or </span>`<span class="editor-theme-code">buffer_size == NULL</span>`
- `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`<span style="white-space: pre-wrap;"> if key is out of range or not valid</span>
- `<span class="editor-theme-code">RESULT_ERR_BUFFER_TOO_SMALL</span>`<span style="white-space: pre-wrap;"> if destination buffer is too small</span>

#### Behavior

The function:

1. validates inputs
2. checks key bounds
3. locks the slot
4. verifies the slot is valid
5. checks whether the provided buffer is large enough
6. <span style="white-space: pre-wrap;">copies the stored data with </span>`<span class="editor-theme-code">memcpy</span>`
7. unlocks the slot

#### Important note

<span style="white-space: pre-wrap;">The function does </span>**not**<span style="white-space: pre-wrap;"> require </span>`<span class="editor-theme-code">buffer</span>`<span style="white-space: pre-wrap;"> to be non-null when the buffer is too small path is taken first, but in practice a null </span>`<span class="editor-theme-code">buffer</span>`<span style="white-space: pre-wrap;"> with a large enough </span>`<span class="editor-theme-code">buffer_size</span>`<span style="white-space: pre-wrap;"> would lead to invalid </span>`<span class="editor-theme-code">memcpy</span>`. So callers should always provide a valid buffer unless they intentionally use this as a size query pattern and know what they are doing.

<p class="callout warning"><span style="white-space: pre-wrap;">The implementation does not explicitly validate </span>`<span class="editor-theme-code">buffer != NULL</span>`. Why? No clue.</p>

### `<span class="editor-theme-code">kv_pool_write()</span>`

```c
result_t kv_pool_write(kv_pool *pool, int key, void *buffer, size_t buffer_size);
```

#### Purpose

Overwrites the existing value for a valid key without reallocating memory.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to overwrite.

`<span class="editor-theme-code">buffer</span>`

Source data to copy from.

`<span class="editor-theme-code">buffer_size</span>`

Size of new data.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if </span>`<span class="editor-theme-code">pool == NULL</span>`<span style="white-space: pre-wrap;">, </span>`<span class="editor-theme-code">buffer == NULL</span>`, or size mismatches existing allocation
- `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`<span style="white-space: pre-wrap;"> if key is invalid or not currently in use</span>

#### Behavior

The function:

1. validates arguments
2. checks that the key refers to a valid existing slot
3. locks the slot
4. <span style="white-space: pre-wrap;">checks that </span>`<span class="editor-theme-code">buffer_size</span>`<span style="white-space: pre-wrap;"> exactly matches the stored size</span>
5. copies new bytes over existing allocation
6. unlocks the slot

#### Important constraint

<p class="callout info"><span style="white-space: pre-wrap;">This function does </span>**not**<span style="white-space: pre-wrap;"> resize.</span></p>

The size must exactly match the existing allocation.

If the caller wants to store a different-sized value, they must:

- remove the key
- insert again with the new size

or implement a resize API in the future.

### `<span class="editor-theme-code">kv_pool_insert()</span>`

```c
result_t kv_pool_insert(kv_pool *pool, int key, void *data, size_t data_size);
```

#### Purpose

Allocates heap space and stores new data for a key.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to populate.

`<span class="editor-theme-code">data</span>`

Source bytes to copy into pool storage.

`<span class="editor-theme-code">data_size</span>`

Size of the data to store.

#### Returns

Actual implementation returns:

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if:</span>
    - `<span class="editor-theme-code">pool == NULL</span>`
    - `<span class="editor-theme-code">data == NULL</span>`
    - key is out of range
- `<span class="editor-theme-code">RESULT_ERR_NO_MEM</span>`<span style="white-space: pre-wrap;"> if allocation fails</span>

#### Inserting on an already allocated slot

<p class="callout info"><span style="white-space: pre-wrap;">If </span>`<span class="editor-theme-code">kv_pool_insert()</span>`<span style="white-space: pre-wrap;"> is called on a slot that is already valid, the old allocation is orphaned and leaked from the pool heap.</span></p>

So the safe usage rule today is:

- <span style="white-space: pre-wrap;">only call </span>`<span class="editor-theme-code">kv_pool_insert()</span>`<span style="white-space: pre-wrap;"> on an empty key</span>
- remove existing keys first before reinserting

The implementation does not enforce that, but callers must.

#### Internal behavior

The function:

1. validates inputs
2. locks the slot
3. marks the slot invalid and clears metadata
4. allocates a heap block
5. copies data into the new block
6. marks the slot valid
7. unlocks and returns

---

### `<span class="editor-theme-code">kv_pool_remove()</span>`

```c
result_t kv_pool_remove(kv_pool *pool, int key);
```

#### Purpose

Removes a key and frees its allocated data block.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to remove.

#### Returns

Actual implementation returns:

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if </span>`<span class="editor-theme-code">pool == NULL</span>`
- `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`<span style="white-space: pre-wrap;"> if key is out of bounds or not valid</span>

#### Behavior

The function:

1. validates the pool pointer
2. <span style="white-space: pre-wrap;">verifies the key is valid with </span>`<span class="editor-theme-code">kv_pool_is_index_valid()</span>`
3. <span style="white-space: pre-wrap;">calls </span>`<span class="editor-theme-code">kv_pool_free()</span>`<span style="white-space: pre-wrap;"> on the stored pointer</span>

### `<span class="editor-theme-code">kv_pool_is_index_valid()</span>`

```c
result_t kv_pool_is_index_valid(kv_pool *pool, int key);
```

#### Purpose

Checks whether a key currently contains valid data.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">key</span>`

Key index to check.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> if key is in range and valid</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if pool is null or key is out of range</span>
- `<span class="editor-theme-code">RESULT_ERR_NOT_FOUND</span>`<span style="white-space: pre-wrap;"> if slot is currently invalid</span>

#### Behavior

The function:

1. validates arguments
2. locks the slot
3. <span style="white-space: pre-wrap;">checks </span>`<span class="editor-theme-code">is_valid</span>`
4. unlocks and returns a snapshot result

#### Important concurrency note

<p class="callout warning">This is only a snapshot.</p>

<p class="callout warning">A slot that is valid at the time of the check may become invalid immediately afterward.</p>

So callers must not do:

1. `<span class="editor-theme-code">kv_pool_is_index_valid()</span>`
2. assume later access is now guaranteed safe forever

They must still handle failure from the actual operation.

## Allocator APIs

These are declared in the header and can be called externally, but they behave like internal heap primitives.

Using them directly requires understanding the allocator and slot ownership rules.

### `<span class="editor-theme-code">kv_pool_allocate()</span>`

```c
result_t kv_pool_allocate(kv_pool *pool, size_t size, void **out_ptr);
```

#### Purpose

Allocates a block from the pool heap.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">size</span>`

Payload size requested.

`<span class="editor-theme-code">out_ptr</span>`

Output pointer for the allocated payload address.

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> for null pointers or zero size</span>
- `<span class="editor-theme-code">RESULT_ERR_NO_MEM</span>`<span style="white-space: pre-wrap;"> if no free block can satisfy the request</span>

#### Allocation strategy

<span style="white-space: pre-wrap;">The allocator uses </span>**first fit**<span style="white-space: pre-wrap;"> on the free list.</span>

It scans from the head until it finds the first block large enough.

#### Block sizing

The allocator computes:

- header offset to payload
- total required size including header
- alignment rounding
- minimum block size enforcement

#### Splitting

If the selected block is larger than needed and the remainder is big enough, it splits the block and leaves the remainder on the free list.

Otherwise it consumes the whole block.

### `<span class="editor-theme-code">kv_pool_free()</span>`

```c
result_t kv_pool_free(kv_pool *pool, void *ptr);
```

#### Purpose

Returns a previously allocated block to the pool heap.

#### Parameters

`<span class="editor-theme-code">pool</span>`

Initialized pool.

`<span class="editor-theme-code">ptr</span>`

<span style="white-space: pre-wrap;">Pointer previously returned by </span>`<span class="editor-theme-code">kv_pool_allocate()</span>`<span style="white-space: pre-wrap;"> or stored in a slot.</span>

#### Returns

- `<span class="editor-theme-code">RESULT_OK</span>`<span style="white-space: pre-wrap;"> on success</span>
- `<span class="editor-theme-code">RESULT_ERR_INVALID_ARG</span>`<span style="white-space: pre-wrap;"> if arguments are null</span>

#### Behavior

The function:

1. derives the block header from the payload pointer
2. inserts the block back into the sorted free list
3. coalesces with adjacent free neighbors when possible
4. <span style="white-space: pre-wrap;">scans the slot table for a matching </span>`<span class="editor-theme-code">data_ptr</span>`
5. if found, clears that slot’s metadata

#### Important consequence

`<span class="editor-theme-code">kv_pool_free()</span>`<span style="white-space: pre-wrap;"> does not merely free heap memory. It also tries to invalidate any slot pointing at that memory.</span>

That means allocator state and key-value metadata are coupled.

### Safe usage implication

<span style="white-space: pre-wrap;">External callers should not casually use </span>`<span class="editor-theme-code">kv_pool_allocate()</span>`<span style="white-space: pre-wrap;"> and </span>`<span class="editor-theme-code">kv_pool_free()</span>`<span style="white-space: pre-wrap;"> unless they understand this coupling.</span>

If you free a pointer that a slot still references, the function will clear that slot.

<p class="callout info">If you allocate memory manually and never attach it to a slot, the allocator still works, but you are now using the pool partly as a raw allocator and partly as a key-value store, which increases maintenance complexity.</p>

## <span style="white-space: pre-wrap;">Role of </span>`<span class="editor-theme-code">delay()</span>`

<p class="callout info">The caller supplies the delay function during pool initialization.</p>

This allows board or environment-specific waiting behavior, such as:

- yielding
- sleeping
- short pause loop
- RTOS task delay
- platform-specific backoff

The pool does not define how delay behaves. That is the caller’s responsibility.

## Example usage

### Contiguous initialization

```c
static uint8_t kv_memory[2048];
static kv_pool pool;

static void pool_delay(void) {
  /* platform-specific wait/yield */
}

result_t app_kv_init(void) {
  return kv_pool_init(kv_memory, sizeof(kv_memory), 16, &pool, pool_delay);
}
```

This creates a pool with:

- 16 keys
- <span style="white-space: pre-wrap;">lookup table at the start of </span>`<span class="editor-theme-code">kv_memory</span>`
- heap in the remaining bytes

## Insert value

```c
uint32_t value = 1234;
result_t res = kv_pool_insert(&pool, 3, &value, sizeof(value));
```

<span style="white-space: pre-wrap;">This stores 4 bytes at key </span>`<span class="editor-theme-code">3</span>`.

---

## Read value

```c
uint32_t value = 0;
size_t size = sizeof(value);

result_t res = kv_pool_get(&pool, 3, &value, &size);
```

On success:

- `<span class="editor-theme-code">res == RESULT_OK</span>`
- `<span class="editor-theme-code">value</span>`<span style="white-space: pre-wrap;"> contains the stored bytes</span>
- `<span class="editor-theme-code">size == sizeof(uint32_t)</span>`

### Handle too-small buffer

```c
uint8_t small_buf[4];
size_t size = sizeof(small_buf);

result_t res = kv_pool_get(&pool, key, small_buf, &size);
if (res == RESULT_ERR_BUFFER_TOO_SMALL) {
  /* size now contains required size */
}
```

This is the intended size negotiation pattern.

### Overwrite same-sized value

```c
uint32_t new_value = 5678;
result_t res = kv_pool_write(&pool, 3, &new_value, sizeof(new_value));
```

<span style="white-space: pre-wrap;">This succeeds only if key </span>`<span class="editor-theme-code">3</span>`<span style="white-space: pre-wrap;"> already exists and has exactly 4 bytes allocated.</span>

### Remove key

```c
result_t res = kv_pool_remove(&pool, 3);
```

This frees the associated heap block and invalidates the slot.

## Backend and platform independence

<p class="callout info">The API is largely independent of where memory comes from and how waiting is implemented.</p>

The caller provides:

- raw memory regions
- a delay function
- <span style="white-space: pre-wrap;">the </span>`<span class="editor-theme-code">kv_pool</span>`<span style="white-space: pre-wrap;"> object itself</span>

That means different boards or environments can use the same API with different backing strategies, for example:

- contiguous static RAM region on one board
- split metadata/data regions across different RAM banks on another
- RTOS yield in the delay callback on one target
- busy-wait pause or test hook in host-side simulation

<p class="callout info"><span style="white-space: pre-wrap;">The implementation is therefore </span>**memory-placement agnostic**<span style="white-space: pre-wrap;"> and </span>**wait-strategy agnostic**, even though the current source file provides one specific allocator and lock implementation.</p>

This is useful for portability, provided each target respects the same concurrency and memory lifetime contract.

## Recommended usage rules for current code

Given the implementation as it exists today, these rules are the safest:

1. Initialize once before concurrent use.
2. Provide memory that remains valid for the full pool lifetime.
3. <span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">insert()</span>`<span style="white-space: pre-wrap;"> only on empty keys.</span>
4. <span style="white-space: pre-wrap;">Use </span>`<span class="editor-theme-code">write()</span>`<span style="white-space: pre-wrap;"> only when new data size exactly matches old size.</span>
5. <span style="white-space: pre-wrap;">Do not rely on </span>`<span class="editor-theme-code">is_index_valid()</span>`<span style="white-space: pre-wrap;"> as a guarantee for later access.</span>
6. Treat direct allocator calls as advanced/internal use.
7. Do not assume allocator concurrency is fully correct (there is definitely a bug or two in there).
8. <span style="white-space: pre-wrap;">Always pass a valid destination buffer to </span>`<span class="editor-theme-code">get()</span>`<span style="white-space: pre-wrap;"> when copying data.</span>