Ssf
Small System Framework - JSON parser/generator, Reed-Solomon, finite state machine framework, and other high quality interfaces for embedded systems.
Install / Use
/learn @supurloop/SsfREADME
ssf
Small System Framework
A hardened, easily portable, API consistent, C source code library of production ready functionality for small memory embedded systems.
Overview | Design Principles | Porting | Interfaces | Conclusion
<a id="overview"></a>
Overview
This code framework was specifically designed:
- To run on embedded systems that have constrained program and data memories.
- To minimize the potential for common coding errors and immediately detect and report runtime errors.
Microprocessors with >4KB RAM and >32KB program memory should easily be able to utilize this framework. Portions of the framework will work on even smaller microcontrollers.
<a id="designprinciples"></a>
Design Principles
No Dependencies
When you use the SSF, you just need the SSF and not a single other external dependency except for a few C standard library calls. No new definitions for basic data types that complicate your code base and cloud your mind; SSF uses <stdint.h> and <stdbool.h> for basic data types.
No Error Codes
Too often API error codes are ignored in part or in whole, or improperly handled due to overloaded encodings (ex. <0=error, 0=ignored, >0=length)
Either a SSF API call will always succeed, or it will return a boolean: true on success and false on failure. That's it. Any function outputs are handled via the parameter list. This makes application error handling simple to implement, and much less prone to errors.
Militant Buffer and C String Overrun Protection
All SSF interfaces require the total allocated size of buffers and C strings to be passed as arguments. The SSF API will never write beyond the end of a buffer or C string. All C strings returned by a successful SSF API call are guaranteed to be NULL terminated.
Use the Safe C string SSF API to replace crash inducing strlen(), strcat(), strcpy(), and strcmp(), and other "safe" C library calls that don't always ensure NULL termination like strncpy().
Design by Contract
To help ensure correctness the framework uses Design by Contract techniques to immediately catch common errors that tend to creep into systems and cause problems at the worst possible times.
Design by Contract uses assertions, conditional code that ensures that the state of the system at runtime is operating within the allowable tolerances. The assertion interface uses several macros that hint at the assertions purpose. SSF_REQUIRE() is used to check function input parameters. SSF_ENSURE() is used to check the return result of a function. SSF_ASSERT() is used to check any other system state. SSF_ERROR() always forces an assertion.
The framework does NOT use assertions when operating on inputs that likely come from external sources. For example, the SSF JSON parser will NOT assert if the input string is not valid JSON syntax, but it will assert if a NULL pointer is passed in as the pointer to the input.
The framework will also detect common errors like using interfaces before they have been properly initialized.
Hardened and Tested
For the main port testing platforms of Windows, Linux, and Mac OS, all relevant compiler warnings are turned on. SSF compiles cleanly on them all.
SSF is linted using Visual Studio's static analysis tool with no warnings.
SSF has been reviewed for buffer overflows and general correctness by Claude Code.
Every SSF interface has a suite of unit tests that MUST PASS completely on all of the main port testing platforms.
The author uses various modules of SSF in several different commercial products using a variety of microcontrollers and toolchains.
<a id="porting"></a>
Porting
This framework has been successfully ported and unit tested on the following platforms: Windows Visual Studio 32/64-bit, Linux 32/64-bit (GCC), MAC OS X, PIC32 (XC32), PIC24 (XC16), MSP430 (TI 15.12.3.LTS), and many others. You should only need to change ssfport.c and ssfport.h to implement a successful port.
The code will compile cleanly and not report any analyzer(Lint) warnings under Windows in Visual Studio using the ssf.sln solution file. The code will compile cleanly under Linux. ./build-linux.sh will create the ssf executable in the source directory. The code will compile cleanly under OS X. ./build-macos.sh will create the ssf executable in the source directory.
The Windows, Linux, and Mac OS builds are setup to output all compiler warnings.
For other platforms there are only a few essential tasks that must be completed:
- Copy the top level source files except main.c and some or all of the submodules into your project sources and add them to your build environment.
- In ssfport.h make sure SSF_TICKS_PER_SEC is set to the number of system ticks per second on your platform.
- Map SSFPortGetTick64() to an existing function that returns the number of system ticks since boot, or implement SSFPortGetTick64() in ssfport.c if a simple mapping is not possible.
- In ssfport.h make sure to follow the instructions for the byte order macros.
- Run the unit tests for the modules you intend to use.
Only a few modules in the framework use system ticks, so you can stub out SSFPortGetTick64() if it is not needed. If the framework runs in a multi-threaded environment and uses non-reentrant modules define SSF_CONFIG_ENABLE_THREAD_SUPPORT and implement the OS synchronization primitives.
SSFPortAssert()
For a production ready embedded system you should properly implement SSFPortAssert().
First, I recommend that it should somehow safely record the file and line number where the assertion occurred so that it can be reported on the next boot.
Second, the system should be automatically reset rather than sitting forever in a loop.
Third, the system should have a safe boot mode that kicks in if the system reboots quickly many times in a row.
RTOS Support
You may have heard the terms bare-metal or superloop to describe single thread of execution systems that lack an OS. This library is designed to primarily* run in a single thread of execution which avoids much of the overhead and pitfalls associated with RTOSes. (*See Byte FIFO Interface for an explanation of how to deal with interrupts.)
Very few small memory systems need to have a threaded/tasked/real-time capable OS. RTOSes introduce significant complexity, and in particular a dizzying amount of opportunity for introducing subtle race conditions (BUGS) that are easy to create and difficult to find and fix.
That said this framework can run perfectly fine with an RTOS, Windows, or Linux, so long as some precautions are taken.
Except for the finite state machine, RTC, and heap modules, all interfaces are reentrant. This means that calls into those interfaces from different execution contexts will not interfere with each other.
For example, you can have linked list object A in task 1 and linked list object B in task 2 and they will be managed independently and correctly, so long as they are only accessed from the context of their respective task.
To ensure safe operation of the non-renetrant modules in a multi-threaded system SSF_SM_CONFIG_ENABLE_THREAD_SUPPORT must be enabled and synchronization macros must be implemented.
Deinitialization
This library has added support to deinitialize interfaces that have initialization functions. Primarily deinitialization was added to allow the library to better integrate with unit test frameworks.
<a id="interfaces"></a>
Interfaces
Size estimates are for ARM Cortex-M (Thumb-2) compiled with maximum size optimization (-Os). Flash includes compiled code and any ROM lookup tables. Static RAM is memory permanently consumed by the module itself; it excludes user-allocated objects. Peak Stack is the maximum call-stack usage during any single API call, including all nested frames and local working buffers. Heap indicates whether the module calls
malloc/freeduring normal operation. Reentrant indicates whether the module can be safely called from independent execution contexts simultaneously — i.e., it holds no shared mutable state between calls.
Data Structures
Efficient data structure primitives for embedded systems, designed to avoid dynamic memory fragmentation and catch misuse at runtime.
| Module | Description | Flash | Static RAM | Peak Stack | Heap | Reentrant | |--------|-------------|-------|------------|------------|------|-----------| | Byte FIFO | Interrupt-safe byte FIFO with single-byte and multi-byte put/get | ~900 B | — | ~80 B | — | Yes | | Linked List | Doubly-linked list supporting FIFO and stack behaviors | ~800 B | — | ~64 B | — | Yes | | Memory Pool | Fixed-size block memory pool with no fragmentation | ~800 B | — | ~96 B | — | Yes | | Heap | Integrity-checked heap with double-free detection and mark-based ownership tracking | ~3.5 KB | — | ~96 B | — | No¹⁹ |
Codecs
Encoding and decoding interfaces for common data formats, all with strict buffer size enforcement and null-terminated output guarantees.
| Module | Description | Flash | Static RAM | Peak Stack | Heap | Reentrant | |--------|-------------|-------|------------|------------|------|-----------| | Base64 | Base64 encoder/decoder | ~700 B | — | ~64 B | — | Yes | | Hex ASCII | Binary-to-hex ASCII encoder/decoder | ~600 B | — | ~64 B | — | Yes | | JSON | JSON parser/generator with path-based field access and in-place update | ~7 KB | — | ~250 B⁹ | — | Yes | | TLV | Type-Length-Value encoder/decoder | ~1.2 KB | — | ~96 B | — | Yes | | INI | INI file parser/generator | ~3.5 KB | — | ~64 B | — | Yes | | UBJSON | Universal Binary JSON parser/generator | ~9 KB | —
