Chapter 4 — OpenLCB Buffer System
The buffer system is the memory backbone of the library. It provides three cooperating modules: a buffer store (pool allocator), a buffer FIFO (ordered message queue), and a buffer list (random-access assembly area). All memory is statically allocated at compile time.
4.1 The message_buffer_t Master Structure
A single message_buffer_t instance holds all message structures and payload pools:
typedef struct {
openlcb_msg_array_t messages; // Array of openlcb_msg_t[LEN_MESSAGE_BUFFER]
openlcb_basic_data_buffer_t basic; // Pool of BASIC payloads (16 bytes each)
openlcb_datagram_data_buffer_t datagram; // Pool of DATAGRAM payloads (72 bytes each)
openlcb_snip_data_buffer_t snip; // Pool of SNIP payloads (256 bytes each)
openlcb_stream_data_buffer_t stream; // Pool of STREAM payloads (512 bytes each)
} message_buffer_t;
The total number of message structures equals the sum of all four pool depths:
#define LEN_MESSAGE_BUFFER (USER_DEFINED_BASIC_BUFFER_DEPTH \
+ USER_DEFINED_DATAGRAM_BUFFER_DEPTH \
+ USER_DEFINED_SNIP_BUFFER_DEPTH \
+ USER_DEFINED_STREAM_BUFFER_DEPTH)
4.2 Four Segregated Payload Pools
Each message structure is permanently paired with one payload buffer from its pool. During initialization, each openlcb_msg_t.payload pointer is wired to its corresponding payload slot. This pairing never changes.
| Pool | Payload Size | Depth Macro | Typical Depth | Use Cases |
|---|---|---|---|---|
| BASIC | 16 bytes | USER_DEFINED_BASIC_BUFFER_DEPTH | 32 | Events, Verify Node ID, Protocol Support, OIR |
| DATAGRAM | 72 bytes | USER_DEFINED_DATAGRAM_BUFFER_DEPTH | 4 | Config memory read/write, datagram protocol |
| SNIP | 256 bytes | USER_DEFINED_SNIP_BUFFER_DEPTH | 4 | SNIP replies, events with payload |
| STREAM | 512 bytes | USER_DEFINED_STREAM_BUFFER_DEPTH | 1 | Stream data transfers |
[LEN_MESSAGE_BUFFER]"] B["BASIC pool
16 bytes x N"] D["DATAGRAM pool
72 bytes x N"] S["SNIP pool
256 bytes x N"] ST["STREAM pool
512 bytes x N"] end MSG -->|"payload ptr"| B MSG -->|"payload ptr"| D MSG -->|"payload ptr"| S MSG -->|"payload ptr"| ST style Store fill:#e3f2fd,stroke:#1565c0 style B fill:#c8e6c9,stroke:#2e7d32 style D fill:#fff9c4,stroke:#f57f17 style S fill:#ffe0b2,stroke:#e65100 style ST fill:#e1bee7,stroke:#7b1fa2
4.3 Buffer Store API
4.3.1 Initialization
void OpenLcbBufferStore_initialize(void);
Clears all message structures, wires payload pointers, and resets counters. Must be called once at startup before any other buffer operation.
4.3.2 Allocation and Deallocation
openlcb_msg_t *OpenLcbBufferStore_allocate_buffer(payload_type_enum payload_type);
void OpenLcbBufferStore_free_buffer(openlcb_msg_t *msg);
void OpenLcbBufferStore_inc_reference_count(openlcb_msg_t *msg);
allocate_buffer(): Scans the specified pool for the first non-allocated slot, marks it allocated withreference_count = 1, clears the payload, and returns the pointer. ReturnsNULLif the pool is exhausted.free_buffer(): Decrements the reference count. When it reaches zero, clears the message structure and marks it as not allocated. PassingNULLis safe.inc_reference_count(): Increments the reference count, allowing the same buffer to be held by multiple queues simultaneously (e.g., sibling dispatch sends a loopback copy while the original is still in the TX pipeline).
4.3.3 Reference Counting Lifecycle
ref_count = 1 Allocated --> Shared : inc_reference_count()
ref_count++ Shared --> Shared : inc_reference_count()
ref_count++ Shared --> Allocated : free_buffer()
ref_count-- (still > 0) Allocated --> Free : free_buffer()
ref_count = 0 Shared --> Free : free_buffer()
ref_count = 0
Every code path that receives a buffer pointer must eventually call free_buffer() exactly once. Forgetting to free causes a pool leak; double-freeing corrupts the pool. When using inc_reference_count(), each holder must independently call free_buffer().
4.3.4 Peak Allocation Counters
The buffer store tracks both current and peak allocation counts for each pool type:
uint16_t OpenLcbBufferStore_basic_messages_allocated(void);
uint16_t OpenLcbBufferStore_basic_messages_max_allocated(void);
uint16_t OpenLcbBufferStore_datagram_messages_allocated(void);
uint16_t OpenLcbBufferStore_datagram_messages_max_allocated(void);
uint16_t OpenLcbBufferStore_snip_messages_allocated(void);
uint16_t OpenLcbBufferStore_snip_messages_max_allocated(void);
uint16_t OpenLcbBufferStore_stream_messages_allocated(void);
uint16_t OpenLcbBufferStore_stream_messages_max_allocated(void);
void OpenLcbBufferStore_clear_max_allocated(void);
Use the *_max_allocated() functions during development to determine if your pool depths are adequate. If the peak matches the pool depth, the pool was at capacity and messages may have been dropped.
4.4 Buffer FIFO
The FIFO is a circular buffer of openlcb_msg_t pointers. It serves as the primary message queue between the transport layer (producer) and the main state machine (consumer).
4.4.1 Circular Buffer Design
Uses one extra slot so that head == tail always means empty (no separate count or full flag needed).
4.4.2 API
void OpenLcbBufferFifo_initialize(void);
openlcb_msg_t *OpenLcbBufferFifo_push(openlcb_msg_t *new_msg);
openlcb_msg_t *OpenLcbBufferFifo_pop(void);
bool OpenLcbBufferFifo_is_empty(void);
uint16_t OpenLcbBufferFifo_get_allocated_count(void);
void OpenLcbBufferFifo_check_and_invalidate_messages_by_source_alias(uint16_t alias);
openlcb_msg_t *OpenLcbBufferFifo_push_to_head(openlcb_msg_t *new_msg);
push(): Adds a message to the tail (back) of the FIFO. Returns the message pointer on success,NULLif full.pop(): Removes and returns the oldest message from the head (front). ReturnsNULLif empty.push_to_head(): Inserts at the head so the message becomes the next one popped. Used by sibling dispatch to ensure loopback copies are processed immediately.check_and_invalidate_messages_by_source_alias(): Walks the FIFO and setsstate.invalidon any message whosesource_aliasmatches the given alias. Used when an AMR (Alias Map Reset) frame arrives -- stale messages from the departed node must not be processed.
4.5 Buffer List
The buffer list is a fixed-size array of openlcb_msg_t pointers supporting random access and attribute-based search. It is used primarily for multi-frame message assembly where incoming CAN frames must be matched to an in-progress message by source alias, dest alias, and MTI.
4.5.1 API
void OpenLcbBufferList_initialize(void);
openlcb_msg_t *OpenLcbBufferList_add(openlcb_msg_t *new_msg);
openlcb_msg_t *OpenLcbBufferList_find(uint16_t source_alias, uint16_t dest_alias, uint16_t mti);
openlcb_msg_t *OpenLcbBufferList_release(openlcb_msg_t *msg);
openlcb_msg_t *OpenLcbBufferList_index_of(uint16_t index);
bool OpenLcbBufferList_is_empty(void);
void OpenLcbBufferList_check_timeouts(uint8_t current_tick);
add(): Inserts into the first available (NULL) slot.find(): Searches for a message matching source_alias, dest_alias, and MTI. Used by the CAN RX handler to find the in-progress buffer for continuation frames.release(): Removes a message from the list (sets slot to NULL) without freeing it. The caller retains ownership.check_timeouts(): Scans for messages withstate.inprocessset whose assembly has stalled (elapsed ticks exceed threshold). Frees stale messages to prevent pool leaks. Caller must hold the shared resource lock.
4.6 Pool Exhaustion and Tuning
When a pool is exhausted, allocate_buffer() returns NULL. The consequences depend on context:
| Context | Symptom | Recovery |
|---|---|---|
| CAN RX (incoming message) | Frame is dropped silently. Sender may retry (datagrams) or the message is lost (events). | Increase the relevant pool depth. |
| Protocol handler (outgoing reply) | Reply cannot be constructed. Addressed messages may trigger an OIR from the remote node after timeout. | Increase the relevant pool depth. |
| Sibling dispatch (loopback copy) | Loopback copy for sibling nodes is dropped. The sibling never sees the message. | Increase BASIC pool depth. |
Run your application under expected peak load and monitor the *_max_allocated() counters. If any peak value equals the pool depth, increase that depth. A good rule of thumb is to add 25-50% headroom above the observed peak.