Tiny no-heap C99 library for embedded firmware: pre-flight checks for embedded operations.
Embedded firmware often fails because operations are allowed to run even when the system is already under resource pressure.
loxbudget provides a deterministic pre-flight gate before risky work: MQTT publish, OTA update, flash write, log burst, debug dump, parser run, queue allocation, or any operation that must be degraded, delayed, rejected, or allowed under pressure.
- Deterministic
check/enter/leavedecisions for operation profiles under pressure. - No heap, no floats, no global mutable state (all state is user-owned storage).
- Optional audit ring buffer (
LOXBUDGET_ENABLE_AUDIT_TRAIL) to retrieve recent decisions. - Optional diagnostic strings (
LOXBUDGET_ENABLE_DIAGNOSTIC_STRINGS). - Optional rate windows + lifetime limits (
LOXBUDGET_ENABLE_RATE_WINDOWS). - Optional calibration (
LOXBUDGET_ENABLE_CALIBRATION). - API stability: the public API declared in
include/loxbudget.his intended to be stable starting withv1.0.0(semver).
- Prevent MQTT storms from exhausting queue slots.
- Block OTA when voltage or flash budget is unsafe.
- Degrade logging under memory pressure.
- Reject non-critical work during survival mode.
- Enforce flash-write lifetime budgets.
loxbudget is not a scheduler, allocator, watchdog, logger, profiler, or RTOS replacement. It is a deterministic admission-control layer for deciding whether an operation may run now.
Evaluators almost always ask "why not X?". This table is the short, practical answer.
| Alternative | Good for | Where it breaks down | Why loxbudget exists anyway |
|---|---|---|---|
| Token bucket / leaky bucket | Smooth-rate limiting a single flow (API calls, publishes) | Hard to model multiple resources (RAM/queue slots/flash budget), pressure modes, and enter/leave lifetimes; typically doesn't explain why a decision happened | loxbudget gates operations against multiple resource types + pressure state, with deterministic decisions and (optional) audit trail |
Ad-hoc if (...) return; |
Tiny one-off guardrails | Drifts into inconsistent rules, hidden coupling, and untestable edge cases; hard to enforce "no heap / no globals / bounded work" across a codebase | Centralizes policy in one library + test suite + CI gates (banned symbols, footprint budgets, sanitizers) |
| FreeRTOS resource handling (queues/semaphores/mutexes) | Concurrency control and backpressure within a subsystem | It answers “can I take this resource now?”, not “should I start this expensive operation under global pressure?”; no cross-cutting policy across subsystems | loxbudget is an admission controller you call before starting work, independent of the RTOS primitive you use underneath |
Current development line: Unreleased
Historical release: v1.0.1
Historical release notes:
v1.0.1is the current tagged release snapshot.v1.0.0-rc1is a historical release-candidate snapshot.v1.0.0is the historical first stable release snapshot.
The public API follows semver stability expectations starting with v1.0.0.
The project does not claim production readiness or safety certification in the README.
#include "loxbudget.h"
int main(void) {
static uint32_t storage[(LOXBUDGET_REQUIRED_SIZE(2, 2, 0) + 3u) / 4u];
loxbudget_t b;
loxbudget_op_profile_t p = loxbudget_op_profile_default(0);
loxbudget_decision_t d;
if (loxbudget_init_simple(&b, storage, sizeof(storage), 2, 2) != LOXBUDGET_OK) return 2;
(void)loxbudget_set_resource(&b, 0, 10, LOXBUDGET_RES_REUSABLE);
(void)loxbudget_register_op(&b, &p);
(void)loxbudget_op_set_need(&b, 0, 0, 5);
(void)loxbudget_check(&b, 0, &d);
return (d.action == LOXBUDGET_ALLOW_FULL) ? 0 : 1;
}This initializes a budget instance into caller-provided storage, declares one reusable resource, registers an operation profile, and asks the library for a deterministic decision before running the operation.
Use loxbudget_enter() and loxbudget_leave() when you need an atomic lease around the work itself.
- Main index:
docs/index.md - Getting started:
docs/getting_started.md - MISRA notes:
docs/misra.md - Handoff/release notes:
docs/handoff.md - Release notes:
releases/v1.0.1.md,releases/v1.0.0.md,releases/v1.0.0-rc1.md - Roadmap:
ROADMAP.md
make test
cmake -S . -B build && cmake --build build
ctest --test-dir build --output-on-failureInstall locally:
cmake -S . -B build
cmake --build build
cmake --install build --prefix ./distConsume from another CMake project:
find_package(loxbudget CONFIG REQUIRED)
target_link_libraries(your_target PRIVATE loxbudget::loxbudget)| Area | Status |
|---|---|
| GCC host build | covered in CI |
| Clang host build | covered in CI |
| ARM Cortex-M0 cross-build | covered in CI |
| clang-format | enforced |
| clang-tidy | enforced |
| ASan/UBSan | covered in CI |
| fuzz smoke | covered in CI |
| single-header build | released artifact |
| CMake install/export | supported |
| footprint budget | checked |
Embedded claims like "deterministic" need numbers. The library keeps .bss = 0 (no global mutable state) by design; user storage lives in your storage[] buffer.
| Platform | Toolchain | .text |
.bss |
loxbudget_check() WCET |
Stack peak (check()) |
|---|---|---|---|---|---|
| Cortex-M0 | arm-none-eabi-gcc -Os |
4192 B (standard, causality off) | 0 B | TBD (needs on-target timer; M0 has no DWT->CYCCNT) |
TBD |
| Cortex-M4 | arm-none-eabi-gcc -Os |
TBD | 0 B | TBD | TBD |
Measured with arm-none-eabi-size on a relocatable linked object (arm-none-eabi-ld -r). See benchmarks/v1.1_footprint.md for the exact methodology and toolchain versions.
| Build | .text |
.data |
.bss |
Notes |
|---|---|---|---|---|
Standard profile (LOXBUDGET_ENABLE_CAUSALITY=0) |
4192 B | 0 B | 0 B | build/loxbudget_arm_standard.o |
Standard profile (LOXBUDGET_ENABLE_CAUSALITY=1) |
5340 B | 0 B | 0 B | +1148 B .text vs baseline |
Host microbenchmarks are not MCU cycle counts, but they are good for catching accidental complexity regressions. See benchmarks/v1.1_cycles.md.
| Scenario | Result |
|---|---|
loxbudget_check() baseline (no causality) |
260.7 ns/check |
loxbudget_check() with causality (2 edges) |
338.8 ns/check |
| Overhead | ~1.30× |
Target-cycle and stack-peak measurement:
- ESP32 (real hardware):
examples/esp32_arduino_loxbudget/prints min/max/avg latency in a tight-loop stress pass. - Cortex-M (real hardware): use a cycle counter (
DWT->CYCCNTon M3/M4/M7) or a hardware timer on M0/M0+. Seebenchmarks/cortexm_wcet_stack/.
include/public APIsrc/library implementationadapters/optional integrationstests/host and integration coverageexamples/minimal and scenario-oriented demostools/utility scripts for amalgamation, calibration and checks
See CONTRIBUTING.md and CODE_OF_CONDUCT.md.