# facil.io — complete documentation (all versions) # facil.io 0.8.x — complete documentation # facil.io 0.8.x Documentation The 0.8.x line re-organizes facil.io as the **fio-stl** library — a single-header-style C STL plus IO, HTTP, Pub/Sub and TLS 1.3 modules. Source code: [facil-io/cstl](https://github.com/facil-io/cstl). **[Download fio-stl.h](https://github.com/facil-io/cstl/releases/download/0.8.0/fio-stl.h)** — the entire library as a single header file (release 0.8.0, one click). > Stable release — the fio-stl library: single-header C core, IO reactor, HTTP, Pub/Sub and TLS 1.3. The documentation is organized into module guides below, plus a complete [API Reference](https://facil.io/0.8.x/api/) generated from the headers. Press / or ⌘K anywhere to search. ## Core Library Version macros, compiler shims, atomics, memory primitives, SIMD and math — the foundation everything builds on. - [facil.io STL — Core](https://facil.io/0.8.x/facil-io-stl-core/) - [Atomics and Locks](https://facil.io/0.8.x/atomics-and-locks/) - [Bit Operations](https://facil.io/0.8.x/bit-operations/) - [Compiler Attributes](https://facil.io/0.8.x/compiler-attributes/) - [Constant-Time Helpers](https://facil.io/0.8.x/constant-time-helpers/) - [Endian and Byte Ordering](https://facil.io/0.8.x/endian-and-byte-ordering/) - [Linked Lists](https://facil.io/0.8.x/linked-lists/) - [Logging and Assertions](https://facil.io/0.8.x/logging-and-assertions/) - [Core Math Helpers](https://facil.io/0.8.x/core-math-helpers/) - [Memory Alternatives and OS Patches](https://facil.io/0.8.x/memory-alternatives-and-os-patches/) - [Memory Primitives](https://facil.io/0.8.x/memory-primitives/) - [OS Detection](https://facil.io/0.8.x/os-detection/) - [Core Random PRNG](https://facil.io/0.8.x/core-random-prng/) - [Fake x86 SIMD](https://facil.io/0.8.x/fake-x86-simd/) - [Static Scratch Allocator](https://facil.io/0.8.x/static-scratch-allocator/) - [String and Buffer Information](https://facil.io/0.8.x/string-and-buffer-information/) - [UTF-8 Helpers](https://facil.io/0.8.x/utf-8-helpers/) - [Vector Math](https://facil.io/0.8.x/vector-math/) - [Version and Settings](https://facil.io/0.8.x/version-and-settings/) ## Utilities & Parsers Text conversion, hashing, glob, sorting, threads and the wire-format parsers (HTTP/1.x, JSON, RESP3, WebSocket…). - [String / Number Conversion](https://facil.io/0.8.x/string-number-conversion/) - [CRC32](https://facil.io/0.8.x/crc32/) - [Glob Matching](https://facil.io/0.8.x/glob-matching/) - [Index Mapped Array (iMap)](https://facil.io/0.8.x/index-mapped-array-imap/) - [Multi-Precision Math](https://facil.io/0.8.x/multi-precision-math/) - [Pseudo-Random and Hashing](https://facil.io/0.8.x/pseudo-random-and-hashing/) - [Signal Monitoring](https://facil.io/0.8.x/signal-monitoring/) - [Sorting](https://facil.io/0.8.x/sorting/) - [Portable Threads](https://facil.io/0.8.x/portable-threads/) - [URL / URI Parsing](https://facil.io/0.8.x/url-uri-parsing/) - [ML Entity Decoding](https://facil.io/0.8.x/ml-entity-decoding/) - [File Helpers](https://facil.io/0.8.x/file-helpers/) - [HTTP/1.x Parser](https://facil.io/0.8.x/http-1-x-parser/) - [JSON Parser](https://facil.io/0.8.x/json-parser/) - [MIME Multipart Parser](https://facil.io/0.8.x/mime-multipart-parser/) - [RESP3 Parser](https://facil.io/0.8.x/resp3-parser/) - [Basic Socket Helpers](https://facil.io/0.8.x/basic-socket-helpers/) - [State Callbacks](https://facil.io/0.8.x/state-callbacks/) - [Time Helpers](https://facil.io/0.8.x/time-helpers/) - [URL-Encoded Parser](https://facil.io/0.8.x/url-encoded-parser/) - [WebSocket Parser](https://facil.io/0.8.x/websocket-parser/) - [CLI Helpers](https://facil.io/0.8.x/cli-helpers/) - [Memory Allocator](https://facil.io/0.8.x/memory-allocator/) - [String Core](https://facil.io/0.8.x/string-core/) - [GFM Markdown Parser](https://facil.io/0.8.x/gfm-markdown-parser/) ## System, Streams & Templates Polling backends, task and timer queues, packet streams, Markdown and Mustache rendering. - [POSIX Portable Polling API](https://facil.io/0.8.x/posix-portable-polling-api/) - [POSIX Polling — epoll Backend](https://facil.io/0.8.x/posix-polling-epoll-backend/) - [POSIX Polling — kqueue Backend](https://facil.io/0.8.x/posix-polling-kqueue-backend/) - [POSIX Polling — poll Backend](https://facil.io/0.8.x/posix-polling-poll-backend/) - [Task and Timer Queues](https://facil.io/0.8.x/task-and-timer-queues/) - [Packet Data Stream](https://facil.io/0.8.x/packet-data-stream/) - [Markdown to HTML bstr Renderer](https://facil.io/0.8.x/markdown-to-html-bstr-renderer/) - [Mustache-ish Template Engine](https://facil.io/0.8.x/mustache-ish-template-engine/) ## Cryptography & TLS Hashes, AEAD, signatures, X.509, KDFs, password hashing, compression and a standalone TLS 1.3 stack. - [Crypto Core](https://facil.io/0.8.x/crypto-core/) - [BLAKE2](https://facil.io/0.8.x/blake2/) - [ChaCha20 & Poly1305](https://facil.io/0.8.x/chacha20-poly1305/) - [HKDF](https://facil.io/0.8.x/hkdf/) - [SHA-1](https://facil.io/0.8.x/sha-1/) - [SHA-2](https://facil.io/0.8.x/sha-2/) - [SHA-3](https://facil.io/0.8.x/sha-3/) - [AES-GCM](https://facil.io/0.8.x/aes-gcm/) - [Ed25519 & X25519](https://facil.io/0.8.x/ed25519-x25519/) - [P-256 (secp256r1)](https://facil.io/0.8.x/p-256-secp256r1/) - [P-384 (secp384r1)](https://facil.io/0.8.x/p-384-secp384r1/) - [ASN.1 DER](https://facil.io/0.8.x/asn-1-der/) - [RSA Signatures](https://facil.io/0.8.x/rsa-signatures/) - [ML-KEM-768](https://facil.io/0.8.x/ml-kem-768/) - [X.509 Certificates](https://facil.io/0.8.x/x-509-certificates/) - [PEM Parser](https://facil.io/0.8.x/pem-parser/) - [Argon2](https://facil.io/0.8.x/argon2/) - [Lyra2](https://facil.io/0.8.x/lyra2/) - [OTP (TOTP)](https://facil.io/0.8.x/otp-totp/) - [Secrets](https://facil.io/0.8.x/secrets/) - [Brotli](https://facil.io/0.8.x/brotli/) - [DEFLATE / Gzip](https://facil.io/0.8.x/deflate-gzip/) - [TLS 1.3 Standalone Library](https://facil.io/0.8.x/tls-1-3-standalone-library/) ## Dynamic Types Dynamic strings, arrays, hash maps, reference counting and the FIOBJ soft dynamic types. - [Dynamic Types — Module Group 200](https://facil.io/0.8.x/dynamic-types-module-group-200/) - [Dynamic Strings — Module 201](https://facil.io/0.8.x/dynamic-strings-module-201/) - [Dynamic Arrays — Module 202](https://facil.io/0.8.x/dynamic-arrays-module-202/) - [Hash Maps and Sets — Module 210](https://facil.io/0.8.x/hash-maps-and-sets-module-210/) - [Reference Counting Wrapper — Module 249](https://facil.io/0.8.x/reference-counting-wrapper-module-249/) - [FIOBJ — Soft Dynamic Types — Module 250](https://facil.io/0.8.x/fiobj-soft-dynamic-types-module-250/) ## IO, HTTP & Pub/Sub The IO reactor, TLS backends, Pub/Sub, Redis and the HTTP module that ties it all together. - [IO and HTTP — Group Overview (400-range)](https://facil.io/0.8.x/io-and-http-group-overview-400-range/) - [IO Reactor API (401 io api.h)](https://facil.io/0.8.x/io-reactor-api-401-io-api-h/) - [IPC — Inter-Process Communication (404 ipc.h)](https://facil.io/0.8.x/ipc-inter-process-communication-404-ipc-h/) - [OpenSSL TLS Backend](https://facil.io/0.8.x/openssl-tls-backend/) - [Native TLS 1.3 IO Backend](https://facil.io/0.8.x/native-tls-1-3-io-backend/) - [Pub/Sub — Publish/Subscribe Messaging (420 pubsub.h)](https://facil.io/0.8.x/pub-sub-publish-subscribe-messaging-420-pubsub-h/) - [Redis — Pub/Sub Engine and Command Client (422 redis.h)](https://facil.io/0.8.x/redis-pub-sub-engine-and-command-client-422-redis-h/) - [HTTP Module](https://facil.io/0.8.x/http-module/) ## API Reference Every macro, type and function, per header file: - [Core](https://facil.io/0.8.x/api/#grp-core) - [Utilities](https://facil.io/0.8.x/api/#grp-util) - [System](https://facil.io/0.8.x/api/#grp-system) - [Cryptography](https://facil.io/0.8.x/api/#grp-crypto) - [Dynamic Types](https://facil.io/0.8.x/api/#grp-types) - [IO & HTTP](https://facil.io/0.8.x/api/#grp-io) # fio-stl API Reference The complete macro, type and function reference, one page per header file. Counts are listed per header. ## Core (000–1) - [`000 copyright.h`](https://facil.io/0.8.x/api/000-copyright/) — 1 macro - [`000 core.h`](https://facil.io/0.8.x/api/000-core/) — 311 macros, 14 types, 1439 functions - [`001 header.h`](https://facil.io/0.8.x/api/001-header/) — 12 macros - [`001 logging.h`](https://facil.io/0.8.x/api/001-logging/) — 1 macro - [`001 memalt.h`](https://facil.io/0.8.x/api/001-memalt/) — 5 functions - [`001 patches.h`](https://facil.io/0.8.x/api/001-patches/) — 2 functions ## Utilities (002–12) - [`002 atol.h`](https://facil.io/0.8.x/api/002-atol/) — 4 macros, 1 type, 44 functions - [`002 crc32.h`](https://facil.io/0.8.x/api/002-crc32/) — 1 function - [`002 glob matching.h`](https://facil.io/0.8.x/api/002-glob-matching/) — 1 function - [`002 imap.h`](https://facil.io/0.8.x/api/002-imap/) — 7 macros - [`002 math.h`](https://facil.io/0.8.x/api/002-math/) — 6 functions - [`002 random.h`](https://facil.io/0.8.x/api/002-random/) — 1 macro, 14 functions - [`002 signals.h`](https://facil.io/0.8.x/api/002-signals/) — 3 macros, 1 type, 4 functions - [`002 sort.h`](https://facil.io/0.8.x/api/002-sort/) — 3 macros - [`002 threads.h`](https://facil.io/0.8.x/api/002-threads/) — 2 macros, 5 types, 26 functions - [`002 url.h`](https://facil.io/0.8.x/api/002-url/) — 1 macro, 3 types, 3 functions - [`003 entities.h`](https://facil.io/0.8.x/api/003-entities/) — 1 function - [`004 files.h`](https://facil.io/0.8.x/api/004-files/) — 4 macros, 3 types, 21 functions - [`004 http1 parser.h`](https://facil.io/0.8.x/api/004-http1-parser/) — 3 macros - [`004 json.h`](https://facil.io/0.8.x/api/004-json/) — 2 macros, 2 types, 1 function - [`004 multipart.h`](https://facil.io/0.8.x/api/004-multipart/) — 2 types, 1 function - [`004 resp3.h`](https://facil.io/0.8.x/api/004-resp3/) — 19 macros, 4 types, 1 function - [`004 sock.h`](https://facil.io/0.8.x/api/004-sock/) — 9 macros, 2 types, 24 functions - [`004 state callbacks.h`](https://facil.io/0.8.x/api/004-state-callbacks/) — 1 type, 3 functions - [`004 time.h`](https://facil.io/0.8.x/api/004-time/) — 17 functions - [`004 urlencoded.h`](https://facil.io/0.8.x/api/004-urlencoded/) — 2 types, 1 function - [`004 websocket parser.h`](https://facil.io/0.8.x/api/004-websocket-parser/) — 29 macros - [`005 cli.h`](https://facil.io/0.8.x/api/005-cli/) — 7 macros, 1 type, 15 functions - [`010 mem.h`](https://facil.io/0.8.x/api/010-mem/) — 27 macros, 23 functions - [`011 string core.h`](https://facil.io/0.8.x/api/011-string-core/) — 18 macros, 3 types, 82 functions - [`012 gfm.h`](https://facil.io/0.8.x/api/012-gfm/) — 10 macros ## System (102–104) - [`102 poll api.h`](https://facil.io/0.8.x/api/102-poll-api/) — 7 macros, 2 types, 7 functions - [`102 queue.h`](https://facil.io/0.8.x/api/102-queue/) — 3 macros, 4 types, 21 functions - [`102 stream.h`](https://facil.io/0.8.x/api/102-stream/) — 3 macros, 2 types, 11 functions - [`103 md2html.h`](https://facil.io/0.8.x/api/103-md2html/) — 2 macros - [`104 mustache.h`](https://facil.io/0.8.x/api/104-mustache/) — 5 macros, 3 types, 6 functions ## Cryptography (150–190) - [`150 crypto core.h`](https://facil.io/0.8.x/api/150-crypto-core/) — 2 types - [`152 blake2.h`](https://facil.io/0.8.x/api/152-blake2/) — 2 types, 12 functions - [`152 chacha20poly1305.h`](https://facil.io/0.8.x/api/152-chacha20poly1305/) — 7 functions - [`152 sha1.h`](https://facil.io/0.8.x/api/152-sha1/) — 1 type, 3 functions - [`152 sha2.h`](https://facil.io/0.8.x/api/152-sha2/) — 2 types, 12 functions - [`152 sha2z hkdf.h`](https://facil.io/0.8.x/api/152-sha2z-hkdf/) — 2 macros, 3 functions - [`152 sha3.h`](https://facil.io/0.8.x/api/152-sha3/) — 1 type, 16 functions - [`153 aes.h`](https://facil.io/0.8.x/api/153-aes/) — 4 functions - [`154 ed25519.h`](https://facil.io/0.8.x/api/154-ed25519/) — 2 macros, 11 functions - [`154 p256.h`](https://facil.io/0.8.x/api/154-p256/) — 5 functions - [`154 p384.h`](https://facil.io/0.8.x/api/154-p384/) — 2 functions - [`155 der.h`](https://facil.io/0.8.x/api/155-der/) — 4 types, 14 functions - [`155 rsa.h`](https://facil.io/0.8.x/api/155-rsa/) — 3 macros, 3 types, 3 functions - [`156 mlkem.h`](https://facil.io/0.8.x/api/156-mlkem/) — 9 macros, 8 functions - [`156 x509.h`](https://facil.io/0.8.x/api/156-x509/) — 34 macros, 10 types, 13 functions - [`157 pem.h`](https://facil.io/0.8.x/api/157-pem/) — 3 types, 5 functions - [`159 argon2.h`](https://facil.io/0.8.x/api/159-argon2/) — 2 types, 4 functions - [`159 lyra2.h`](https://facil.io/0.8.x/api/159-lyra2/) — 1 type, 4 functions - [`159 otp.h`](https://facil.io/0.8.x/api/159-otp/) — 1 type, 6 functions - [`159 secret.h`](https://facil.io/0.8.x/api/159-secret/) — 5 functions - [`162 brotli.h`](https://facil.io/0.8.x/api/162-brotli/) — 4 functions - [`162 deflate.h`](https://facil.io/0.8.x/api/162-deflate/) — 1 type, 12 functions - [`190 tls13.h`](https://facil.io/0.8.x/api/190-tls13/) — 16 macros, 16 types, 28 functions ## Dynamic Types (200–250) - [`201 string.h`](https://facil.io/0.8.x/api/201-string/) — 7 macros, 1 type, 39 functions - [`202 array.h`](https://facil.io/0.8.x/api/202-array/) — 9 macros, 2 types, 19 functions - [`210 map.h`](https://facil.io/0.8.x/api/210-map/) — 3 macros, 4 types, 23 functions - [`210 map2.h`](https://facil.io/0.8.x/api/210-map2/) — 3 macros - [`249 reference counter.h`](https://facil.io/0.8.x/api/249-reference-counter/) — 17 functions - [`250 fiobj.h`](https://facil.io/0.8.x/api/250-fiobj/) — 24 macros, 2 types, 40 functions ## IO & HTTP (400–439) - [`401 io api.h`](https://facil.io/0.8.x/api/401-io-api/) — 6 macros, 13 types, 85 functions - [`404 ipc.h`](https://facil.io/0.8.x/api/404-ipc/) — 13 macros, 4 types, 24 functions - [`405 tls13.h`](https://facil.io/0.8.x/api/405-tls13/) — 1 function - [`420 pubsub.h`](https://facil.io/0.8.x/api/420-pubsub/) — 2 macros, 4 types, 20 functions - [`422 redis.h`](https://facil.io/0.8.x/api/422-redis/) — 2 macros, 2 types, 6 functions - [`430 http api.h`](https://facil.io/0.8.x/api/430-http-api/) — 34 macros, 12 types, 107 functions # facil.io STL — Core ## The C Server Toolbox Library Welcome to the heart of the facil.io C STL. This is where the boring-but-essential stuff lives: version numbers, compiler incantations, OS patches, atomics, memory primitives, and the low-level helpers that everything else builds on. It is not glamorous, but without it the rest of the library would be a pile of undefined behavior and segfaults. The core group covers the first two slices of the library — `000 core.h` and `001 header.h` — plus their specialized sidekicks. Each file below is a focused guide to one corner of that foundation. ## Core documentation index - [001 version and settings](https://facil.io/0.8.x/version-and-settings/) — Version macros and the compile-time knobs that tune the library's behavior. - [001 compiler attributes](https://facil.io/0.8.x/compiler-attributes/) — Compiler detection, C/C++ attribute helpers, and pointer-math macros. - [001 os detection](https://facil.io/0.8.x/os-detection/) — POSIX/Windows detection, process helpers, and portable type patches. - [001 atomics and locks](https://facil.io/0.8.x/atomics-and-locks/) — Atomic operations, spinlocks, atomic bitmap access, and thread-yield hints. - [001 static alloc](https://facil.io/0.8.x/static-scratch-allocator/) — The round-robin scratch allocator for short-lived temporary buffers. - [001 logging](https://facil.io/0.8.x/logging-and-assertions/) — Log levels, assertion macros, and the leak counter. - [001 endian](https://facil.io/0.8.x/endian-and-byte-ordering/) — Byte-order detection, swaps, and conversions between host and network/little-endian layouts. - [001 memory primitives](https://facil.io/0.8.x/memory-primitives/) — Low-level copy/move/compare helpers and unaligned buffer accessors. - [001 memalt](https://facil.io/0.8.x/memory-alternatives-and-os-patches/) — Portable fallback memory routines and the environment helpers they lean on. - [001 vector math](https://facil.io/0.8.x/vector-math/) — Wide integer unions from 128 to 4096 bits and their lane-wise arithmetic. - [001 simd](https://facil.io/0.8.x/fake-x86-simd/) — Fake/portable x86 intrinsics and SIMD helpers built on the wide vector types. - [001 linked lists](https://facil.io/0.8.x/linked-lists/) — Pointer-based and indexed linked-list macros. - [001 constant time](https://facil.io/0.8.x/constant-time-helpers/) — Branchless selection, bitwise comparisons, and other security-sensitive primitives. - [001 bit operations](https://facil.io/0.8.x/bit-operations/) — Rotations, popcount, bitmap access, and byte-value bit tricks. - [001 math](https://facil.io/0.8.x/core-math-helpers/) — Hash primes, modular arithmetic, and multi-precision add/sub/mul helpers. - [001 random core](https://facil.io/0.8.x/core-random-prng/) — The deterministic 128-bit PRNG and its reseeding/cycle-counter hooks. - [001 string info](https://facil.io/0.8.x/string-and-buffer-information/) — Lightweight string and buffer descriptors plus equality helpers. - [001 utf8](https://facil.io/0.8.x/utf-8-helpers/) — Encoding, decoding, and validation helpers for UTF-8 text. Everything after the core builds on these pages. Pick a topic and dig in. # Atomics and Locks Concurrency primitives live in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). The lock selector that chooses between spinlocks and OS mutexes is in [`./001 header.h`](https://facil.io/0.8.x/api/001-header/). ## Atomic operations All atomic macros use sequentially consistent ordering. They work on a pointer to the object and return the **previous** value, unless the name ends in `_fetch`, in which case they return the **new** value. ### Load and exchange | Macro | Returns | Notes | |---|---|---| | `fio_atomic_load(dest, p_obj)` | assigns value to `dest` | Atomic read of `*p_obj`. | | `fio_atomic_exchange(p_obj, value)` | previous value | Atomically sets `*p_obj = value`. | | `fio_atomic_compare_exchange_p(p_obj, p_expected, p_desired)` | `1` on success, `0` on failure | System-specific compare-and-swap. `p_expected` may be overwritten with the current value. | ### Arithmetic and logic | Macro | Operation | Returns | |---|---|---| | `fio_atomic_add(p_obj, value)` | `*p_obj += value` | previous value | | `fio_atomic_sub(p_obj, value)` | `*p_obj -= value` | previous value | | `fio_atomic_and(p_obj, value)` | `*p_obj &= value` | previous value | | `fio_atomic_or(p_obj, value)` | `*p_obj \|= value` | previous value | | `fio_atomic_xor(p_obj, value)` | `*p_obj ^= value` | previous value | | `fio_atomic_add_fetch(p_obj, value)` | `*p_obj += value` | new value | | `fio_atomic_sub_fetch(p_obj, value)` | `*p_obj -= value` | new value | | `fio_atomic_and_fetch(p_obj, value)` | `*p_obj &= value` | new value | | `fio_atomic_or_fetch(p_obj, value)` | `*p_obj \|= value` | new value | | `fio_atomic_xor_fetch(p_obj, value)` | `*p_obj ^= value` | new value | The implementation prefers GCC/Clang `__atomic` builtins, falls back to legacy `__sync` builtins, then C11 `stdatomic.h`, then MSVC intrinsics. ## Spinlocks The default lock is an 8-bit spinlock with one main lock and seven sub-locks packed into the same byte. | Type / macro | Meaning | |---|---| | `fio_lock_i` | `volatile unsigned char` spinlock type. | | `FIO_LOCK_INIT` | Initializer (`0`). | | `FIO_LOCK_SUBLOCK(i)` | Bit mask for sub-lock `i` (`0` to `7`). Combine with `\|`. | ### Single lock API | Function | Behavior | |---|---| | `uint8_t fio_trylock(fio_lock_i *lock)` | Try to acquire the main lock. Returns `0` on success, `1` on failure. | | `void fio_lock(fio_lock_i *lock)` | Busy-wait until the main lock is acquired. Avoid long waits. | | `void fio_unlock(fio_lock_i *lock)` | Release the main lock. | | `uint8_t fio_is_locked(fio_lock_i *lock)` | Returns non-zero if the main lock is held. | ### Group lock API | Function | Behavior | |---|---| | `uint8_t fio_trylock_group(fio_lock_i *lock, uint8_t group)` | Try to acquire a group of sub-locks. Returns `0` on success, `1` on failure. | | `void fio_lock_group(fio_lock_i *lock, uint8_t group)` | Busy-wait until the whole group is acquired. | | `void fio_unlock_group(fio_lock_i *lock, uint8_t group)` | Release a group of sub-locks. | | `uint8_t fio_is_group_locked(fio_lock_i *lock, uint8_t group)` | Returns non-zero if any sub-lock in `group` is held. | | `uint8_t fio_trylock_full(fio_lock_i *lock)` | Try to lock all sub-locks at once. | | `void fio_lock_full(fio_lock_i *lock)` | Busy-wait until every sub-lock is acquired. | | `void fio_unlock_full(fio_lock_i *lock)` | Release every sub-lock. | Example: ```c fio_lock_i lock = FIO_LOCK_INIT; if (!fio_trylock_group(&lock, FIO_LOCK_SUBLOCK(1) | FIO_LOCK_SUBLOCK(2))) { /* critical section */ fio_unlock_group(&lock, FIO_LOCK_SUBLOCK(1) | FIO_LOCK_SUBLOCK(2)); } ``` ## Atomic bitmap access Operate on a single bit inside a byte array. All four helpers are atomic at the byte level. | Function | Behavior | |---|---| | `uint8_t fio_atomic_bit_get(void *map, size_t bit)` | Read bit `bit` from `map`. | | `void fio_atomic_bit_set(void *map, size_t bit)` | Set bit `bit` to `1`. | | `void fio_atomic_bit_unset(void *map, size_t bit)` | Set bit `bit` to `0`. | | `void fio_atomic_bit_flip(void *map, size_t bit)` | Toggle bit `bit`. | ## Lock selector [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) chooses between facil.io spinlocks and OS mutexes. The default is spinlocks unless `FIO_USE_THREAD_MUTEX` is set to `1`. | Macro | Purpose | |---|---| | `FIO_USE_THREAD_MUTEX_TMP` | Per-include override. Defaults to `FIO_USE_THREAD_MUTEX`. | | `FIO_THREADS` | Defined when OS mutexes are selected. | | `FIO___LOCK_NAME` | Human-readable name of the selected lock type. | | `FIO___LOCK_TYPE` | The selected lock type (`fio_thread_mutex_t` or `fio_lock_i`). | | `FIO___LOCK_INIT` | Initializer for the selected lock type. | | `FIO___LOCK_DESTROY(lock)` | Destroy a lock instance. | | `FIO___LOCK_LOCK(lock)` | Acquire the lock. | | `FIO___LOCK_TRYLOCK(lock)` | Try to acquire the lock. | | `FIO___LOCK_UNLOCK(lock)` | Release the lock. | These macros are used by modules that need thread safety but do not care which lock type is active. ## Thread scheduling hints Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) and used by the spinlock back-off loops. | Macro | Behavior | |---|---| | `FIO_THREAD_WAIT(nano_sec)` | Sleep the current thread for about `nano_sec` nanoseconds. POSIX uses `nanosleep`; Windows uses `Sleep` (rounded up to at least 1 ms). | | `FIO_THREAD_YIELD()` | Hint to the CPU that the thread is spinning. Emits `pause` on x86, `yield` on ARM, calls `YieldProcessor()` on MSVC, or falls back to `sched_yield()` on POSIX. | | `FIO_THREAD_RESCHEDULE()` | Short sleep via `FIO_THREAD_WAIT(4)`. Used by `fio_lock_group` to back off without fully suspending the thread. | # Bit Operations Bit-twiddling helpers defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). ## Bit rotation ### Generic rotation macros ```c #define FIO_LROT(i, bits) #define FIO_RROT(i, bits) ``` Rotate the value `i` left or right by `bits`. The rotation count is masked to the width of `i`, so these macros work for any integer type. ### Typed rotation helpers Each width has a left and right variant. When the compiler provides a rotate builtin it is used; otherwise a portable shift-or expression is emitted. All are marked `FIO_CONST`. Note: the `fio_rrot8` builtin guard has a typo (`__has_builtin(__builtin_rotatrightt8)`), so the 8-bit right rotation always falls back to the portable implementation. | Width | Left | Right | |---|---|---| | 8-bit | `fio_lrot8(i, bits)` | `fio_rrot8(i, bits)` | | 16-bit | `fio_lrot16(i, bits)` | `fio_rrot16(i, bits)` | | 32-bit | `fio_lrot32(i, bits)` | `fio_rrot32(i, bits)` | | 64-bit | `fio_lrot64(i, bits)` | `fio_rrot64(i, bits)` | | 128-bit | `fio_lrot128(i, bits)` | `fio_rrot128(i, bits)` | The 128-bit variants are only available when the compiler supports `__int128`/`__uint128_t`. ### Endian-aware forward rotation ```c fio_frot16(i, bits) fio_frot32(i, bits) fio_frot64(i, bits) ``` On little-endian systems `fio_frot*` maps to the right-rotation helpers; on big-endian systems it maps to the left-rotation helpers. These are macros, not functions. ## Combined rotation-XOR operations Common SHA-2/BLAKE2-style combinations kept as separate operations so the compiler can optimize them as a unit. ```c uint32_t fio_xor_rrot3_32(uint32_t x, uint8_t a, uint8_t b, uint8_t c); uint64_t fio_xor_rrot3_64(uint64_t x, uint8_t a, uint8_t b, uint8_t c); ``` Computes `ROTR(x,a) ^ ROTR(x,b) ^ ROTR(x,c)`. Used by SHA-256/SHA-512 Sigma functions. ```c uint32_t fio_xor_rrot2_shr_32(uint32_t x, uint8_t a, uint8_t b, uint8_t c); uint64_t fio_xor_rrot2_shr_64(uint64_t x, uint8_t a, uint8_t b, uint8_t c); ``` Computes `ROTR(x,a) ^ ROTR(x,b) ^ (x >> c)`. Used by SHA-256/SHA-512 lowercase sigma functions for message scheduling. ## XOR masking ```c void fio_xmask(void *buf, size_t len, uint64_t mask); void fio_xmask_cpy(char *restrict dest, const char *src, size_t len, uint64_t mask); ``` XOR every byte of a buffer with a repeating 64-bit `mask`. `fio_xmask` works in place; `fio_xmask_cpy` reads from `src` and writes to `dest`. If `dest == src`, `fio_xmask_cpy` behaves like `fio_xmask`. Both functions process data in 64-bit chunks for speed and fall back to a byte-sized tail for the last `0`–`7` bytes. Performance is best when the buffer is aligned. ## Popcount and Hamming distance ```c int fio_popcount(uint64_t n); ``` Returns the number of `1` bits in `n`. Uses `__builtin_popcountll` or MSVC `__popcnt64` when available; otherwise falls back to a portable parallel bit-count implementation. ```c #define fio_hemming_dist(n1, n2) \ fio_popcount(((uint64_t)(n1) ^ (uint64_t)(n2))) ``` Returns the Hamming distance between `n1` and `n2` — the number of bits that differ. The macro is named `fio_hemming_dist` in the source (the conventional spelling is "Hamming"). ## Bit isolation and indexing ```c uint64_t fio_bits_lsb(uint64_t i); uint64_t fio_bits_msb(uint64_t i); ``` * `fio_bits_lsb(i)` isolates the least-significant set bit. * `fio_bits_msb(i)` isolates the most-significant set bit. ```c size_t fio_bits_lsb_index(uint64_t i); size_t fio_bits_msb_index(uint64_t i); ``` Return the bit index of the least-significant or most-significant set bit. Both return `(size_t)-1` when `i` is `0`. ```c size_t fio_lsb_index_unsafe(uint64_t i); size_t fio_msb_index_unsafe(uint64_t i); ``` Same as above but assume `i != 0`; calling them with `0` is undefined behavior. They use `__builtin_ctzll`/`__builtin_clzll` when available and fall back to `fio___single_bit_index_unsafe` — a `switch` over 64 power-of-two constants — otherwise. ## Byte detection in vectors These helpers detect special byte values inside a packed integer. In the result, a matching byte lane is set to `0x80`; all other lanes are `0x00`. ### 32-bit vectors ```c uint32_t fio_has_zero_byte32(uint32_t row); uint32_t fio_has_byte32(uint32_t row, uint8_t byte); uint32_t fio_has_full_byte32(uint32_t row); ``` * `fio_has_zero_byte32` detects `0x00` bytes. * `fio_has_byte32` detects an arbitrary byte value. * `fio_has_full_byte32` detects `0x00` bytes — the source's inline comment claims `0xFF`, but the implementation calls `fio_has_zero_byte32(row)`. ### 64-bit vectors ```c uint64_t fio_has_zero_byte64(uint64_t row); uint64_t fio_has_zero_byte_alt64(uint64_t row); uint64_t fio_has_byte64(uint64_t row, uint8_t byte); uint64_t fio_has_full_byte64(uint64_t row); uint64_t fio_has_byte2bitmap(uint64_t result); ``` * `fio_has_zero_byte64` detects `0x00` bytes. * `fio_has_zero_byte_alt64` is an alternate entry point with the same behavior as `fio_has_zero_byte64`; the source's warning about bitmap safety may be stale. * `fio_has_byte64` detects an arbitrary byte value. * `fio_has_full_byte64` detects `0xFF` bytes. * `fio_has_byte2bitmap` packs a `fio_has_byte*` result into a single 8-bit bitmap. ## Bitmap access Operate on a single bit inside a byte array. These are **not** atomic; use the [`fio_atomic_bit_*`](https://facil.io/0.8.x/atomics-and-locks/#atomic-bitmap-access) helpers for shared mutable bitmaps. ```c uint8_t fio_bit_get(void *map, size_t bit); void fio_bit_set(void *map, size_t bit); void fio_bit_unset(void *map, size_t bit); void fio_bit_flip(void *map, size_t bit); ``` * `fio_bit_get` returns `0` or `1`. * `fio_bit_set` sets the bit to `1`. * `fio_bit_unset` sets the bit to `0`. * `fio_bit_flip` toggles the bit. ## See also - [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) — source of truth for all macros and functions above. - [`./001 atomics and locks.md`](https://facil.io/0.8.x/atomics-and-locks/) — atomic bitmap helpers and locks. - [`./001 constant time.md`](https://facil.io/0.8.x/constant-time-helpers/) — branchless bitwise primitives. - [`./001 endian.md`](https://facil.io/0.8.x/endian-and-byte-ordering/) — byte ordering helpers. # Compiler Attributes The macros in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) and [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) paper over differences between C and C++, compilers, and inclusion models. This page is a quick reference for that portability scaffolding. ## C++ compatibility Both core slices open an `extern "C"` block when compiled as C++. [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) also neutralizes C-only keywords inside that block: - `register` is defined away (deprecated in C++). - `restrict` is defined away (not valid in C++). - `_Bool` is mapped to `bool`. No action is required; including the headers gives C++ code a C-compatible surface. ## Keyword shims Some compilers need a little help with C99/C11 keywords: - **`inline`** — mapped to `__inline` when the compiler does not support the C99 keyword directly (mainly older MSVC). - **`__thread`** — mapped to `__declspec(thread)` on MSVC and `_Thread_local` on C11 compilers; otherwise left as `__thread` when the compiler supports it. ## Compiler detection and no-op macros When the compiler is not GCC or Clang, `__attribute__`, `__has_include`, `__has_builtin`, and `__has_attribute` are defined as no-ops so the rest of the code can use them unconditionally. - **`FIO_NOOP`** — empty macro that adds whitespace. Useful to stop a function-like macro from being expanded. - **`FIO_NOOP_FN(...)`** — empty variadic macro, handy as a default callback. - **`FIO_NOOP_FN_NAME`** — expands to `(void)`. ## OS detection Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/); see [`./001 os detection.md`](https://facil.io/0.8.x/os-detection/) for full details. - **`FIO_OS_POSIX`** — 1 on POSIX systems. - **`FIO_OS_WIN`** — 1 on Windows. - **`FIO_HAVE_UNIX_TOOLS`** — 0 (none), 1 (POSIX), 2 (MinGW), or 3 (Cygwin). ## Function attributes Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) and used throughout the library. - **`FIO_MAYBE_UNUSED`** — `__attribute__((unused))` on GCC/Clang, empty otherwise. - **`FIO_SFUNC`** — `static FIO_MAYBE_UNUSED`. Default internal linkage. - **`FIO_IFUNC`** — `FIO_SFUNC inline`. Static inline helper. - **`FIO_WARN_UNUSED`** — `__attribute__((warn_unused_result))` or empty. - **`FIO_MIFN`** — `FIO_IFUNC FIO_WARN_UNUSED`. Inline math-style function that warns if the return value is ignored. - **`FIO_CONST`** — function result depends only on arguments; no globals or side effects. - **`FIO_PURE`** — no side effects, but may read global memory. - **`FIO_WEAK`** — weak symbol. Empty on MinGW. - **`DEPRECATED(reason)`** — `__attribute__((deprecated(reason)))`, with a fallback for GCC older than 4.5. - **`FIO_CONSTRUCTOR(fname)`** — runs before `main` on GCC/Clang; on MSVC it places a pointer in `.CRT$XCU`. - **`FIO_DESTRUCTOR(fname)`** — runs after `main` on GCC/Clang; on MSVC it is implemented as a constructor that registers `atexit(fname)`. - **`FIO_LIKELY(cond)`** / **`FIO_UNLIKELY(cond)`** — branch-prediction hints. No-op on unknown compilers. - **`FIO_PREFETCH(ptr)`** / **`FIO_PREFETCH_W(ptr)`** / **`FIO_PREFETCH_NT(ptr)`** — cache prefetch hints for read, write-exclusive, and non-temporal streaming access. - **`FIO___PRINTF_STYLE(string_index, check_index)`** — tells the compiler to check `printf`-style format arguments. Empty on pure MSVC; uses the MinGW/Cygwin format attribute on those compilers; otherwise uses the GCC/Clang `printf` format attribute. ## Variable and type attributes Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). - **`FIO_ALIGN(bytes)`** — alignment hint. Uses `__attribute__((aligned(bytes)))` on GCC/Clang; currently empty on other compilers. - **`FIO_WEAK_VAR`** — weak global variable. Uses `__declspec(selectany)` on Windows when selectany support is detected (MSVC or otherwise); `__attribute__((weak))` elsewhere. ## Macro stringifier Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). - **`FIO_MACRO2STR(macro)`** — expands `macro` and stringifies the result. Uses a two-step indirection so tokens like `__LINE__` are expanded first. ## Static assertions Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). - **`FIO_ASSERT_STATIC(cond, msg)`** — compile-time assertion. Uses C11 `_Static_assert` when available; otherwise falls back to a negative-array-size trick that produces a compile error on failure. ## Naming helpers Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). - **`FIO_NAME(prefix, postfix)`** → `prefix_postfix`. - **`FIO_NAME2(prefix, postfix)`** → `prefix2postfix` (for conversion functions). - **`FIO_NAME_BL(prefix, postfix)`** → `prefix_is_postfix` (for boolean tests). - **`FIO_NAME_TEST(prefix, postfix)`** → internal test-function name (`fio___test_prefix_postfix`). ## Pointer math All defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). - **`FIO_PTR_MATH_LMASK(T, ptr, bits)`** — keep the low `bits` of an address. - **`FIO_PTR_MATH_RMASK(T, ptr, bits)`** — keep the high bits, zero the low `bits`. - **`FIO_PTR_MATH_ADD(T, ptr, offset)`** — add `offset` bytes and cast to `T *`. - **`FIO_PTR_MATH_SUB(T, ptr, offset)`** — subtract `offset` bytes and cast to `T *`. - **`FIO_PTR_FIELD_OFFSET(T, field)`** — byte offset of `field` inside `T`. Uses `0xFF00` as a base address to keep AddressSanitizer happy. - **`FIO_PTR_FROM_FIELD(T, field, ptr)`** — recover the containing `T` object from a pointer to one of its fields. ## Pointer tagging Defined in [`./001 header.h`](https://facil.io/0.8.x/api/001-header/); see [`./001 version and settings.md`](https://facil.io/0.8.x/version-and-settings/) for full details. - **`FIO_PTR_TAG(p)`** / **`FIO_PTR_UNTAG(p)`** — default no-ops; override to embed tags in pointers. - **`FIO_PTR_TAG_TYPE`** — when defined, pointer-returning functions use this type instead. - **`FIO_PTR_TAG_VALIDATE(ptr)`** — validates a tagged pointer; default accepts non-NULL. - **`FIO_PTR_TAG_VALID_OR_RETURN(tagged_ptr, value)`** — return `value` if validation fails. - **`FIO_PTR_TAG_VALID_OR_RETURN_VOID(tagged_ptr)`** — return void if validation fails. - **`FIO_PTR_TAG_VALID_OR_GOTO(tagged_ptr, label)`** — jump to `label` if validation fails. - **`FIO_PTR_TAG_GET_UNTAGGED(untagged_type, tagged_ptr)`** — cast `FIO_PTR_UNTAG(tagged_ptr)` to `untagged_type *`. ## Get/set helpers Generate typed getters and setters for struct fields. - **`FIO_DEF_GETSET_FUNC(static, namespace, T_type, F_type, F_name, on_set)`** — defines both `namespace_F_name` (getter) and `namespace_F_name_set` (setter). - **`FIO_DEF_GETSET_FUNC_DEC(static, namespace, T_type, F_type, F_name)`** — declarations only. - **`FIO_IFUNC_DEF_GETSET(namespace, T_type, F_type, F_name, on_set)`** — same, but uses `FIO_IFUNC` as the linkage. `FIO_IFUNC_DEF_GET` and `FIO_IFUNC_DEF_SET` exist for the individual pieces. The setter returns the old value and calls `on_set(o)` after assignment. ## Recursive inclusion and linkage facil.io headers are designed to be included more than once to generate code. [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) manages whether generated functions are `static` or `extern`. - **`SFUNC_`** / **`IFUNC_`** — internal linkage selectors. On the first include they become `FIO_SFUNC`/`FIO_IFUNC` unless `FIO_EXTERN` is defined, in which case they are empty. - **`SFUNC`** / **`IFUNC`** — public aliases for the current linkage selectors. - **`FIO_EXTERN`** — define before including a module to emit non-static declarations and inline functions. - **`FIO___RECURSIVE_INCLUDE`** — internal flag. Setting it to `99` before a recursive `#include` preserves the original `SFUNC`/`IFUNC` linkage instead of forcing it back to `static`. ## Compiler memory barriers - **`FIO_COMPILER_GUARD`** — clobber memory and prevent the compiler from reordering instructions around it. - **`FIO_COMPILER_GUARD_INSTRUCTION`** — prevent instruction reordering without the full memory clobber. On GCC/Clang these are inline `asm volatile` barriers; on MSVC they map to `_ReadWriteBarrier` and `_WriteBarrier`. ## Thread scheduling Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/); see [`./001 atomics and locks.md`](https://facil.io/0.8.x/atomics-and-locks/) for full details. - **`FIO_THREAD_WAIT(nano_sec)`** — sleep for about `nano_sec` nanoseconds (`Sleep` on Windows, `nanosleep` on POSIX). - **`FIO_THREAD_YIELD()`** — yield the CPU (`pause`/`yield` instruction or `sched_yield`). - **`FIO_THREAD_RESCHEDULE()`** — short sleep via `FIO_THREAD_WAIT(4)`. ## Address-sanitizer scaffolding - **`FIO___ASAN_DETECTED`** — set when AddressSanitizer is active. - **`FIO___ASAN_AVOID`** — expands to `no_sanitize_address` when ASan is detected; otherwise empty. ## Loop helpers - **`FIO_FOR(i, count)`** — `for (size_t i = 0; i < (count); ++i)`. - **`FIO_FOR_UNROLL(iterations, size_of_loop, i, action)`** — splits a loop into a small remainder and 256-byte chunks to nudge the compiler toward auto-vectorization. Used heavily by the memory primitives and vector-math templates. The SIMD file covers the portable x86 intrinsics that build on these helpers. # Constant-Time Helpers Branchless, timing-aware primitives defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). These helpers avoid data-dependent branches and early exits so that execution time does not leak information about the values being processed. They are used by the cryptographic modules, authentication code, and anywhere else a compiler might otherwise turn a secret into a branch. ## Boolean constants ```c uintmax_t fio_ct_true(uintmax_t cond); uintmax_t fio_ct_false(uintmax_t cond); ``` * `fio_ct_true(cond)` returns `1` if `cond` is non-zero, else `0`. * `fio_ct_false(cond)` returns `1` if `cond` is zero, else `0`. Both return only the value `0` or `1`, suitable for feeding into `fio_ct_if_bool`. ## Conditional selection ```c uintmax_t fio_ct_if_bool(uintmax_t cond, uintmax_t a, uintmax_t b); uintmax_t fio_ct_if(uintmax_t cond, uintmax_t a, uintmax_t b); ``` * `fio_ct_if_bool` returns `a` when `cond == 1`, otherwise `b`. * `fio_ct_if` first normalizes `cond` with `fio_ct_true`, then returns `a` when `cond` is non-zero, otherwise `b`. Both are branchless: they compute `b ^ (mask & (a ^ b))`. ## Min, max, and absolute value ```c intmax_t fio_ct_max(intmax_t a, intmax_t b); intmax_t fio_ct_min(intmax_t a, intmax_t b); uintmax_t fio_ct_abs(intmax_t i); ``` * `fio_ct_max` / `fio_ct_min` perform signed comparisons without branches. * `fio_ct_abs` returns the absolute value of `i`. ## ASCII case conversion ```c char fio_ct_tolower(char c); ``` Returns the lowercase form of `c` when `c` is an ASCII uppercase letter (`A`–`Z`); otherwise returns `c` unchanged. Branchless and locale-independent. ## Bitwise mux, majority, and three-way XOR ```c uint32_t fio_ct_mux32(uint32_t x, uint32_t y, uint32_t z); uint64_t fio_ct_mux64(uint64_t x, uint64_t y, uint64_t z); uint32_t fio_ct_maj32(uint32_t x, uint32_t y, uint32_t z); uint64_t fio_ct_maj64(uint64_t x, uint64_t y, uint64_t z); uint32_t fio_ct_xor3_32(uint32_t x, uint32_t y, uint32_t z); uint64_t fio_ct_xor3_64(uint64_t x, uint64_t y, uint64_t z); ``` | Function | Operation | Formula | |----------|-----------|---------| | `fio_ct_mux32/64` | bitwise choose | `z ^ (x & (y ^ z))` | | `fio_ct_maj32/64` | bitwise majority | `(x & y) \| (z & (x \| y))` | | `fio_ct_xor3_32/64` | bitwise parity | `x ^ y ^ z` | These are the SHA-style `Ch`, `Maj`, and parity functions, implemented without branches. ## Constant-time equality ```c _Bool fio_ct_is_eq(const void *a, const void *b, size_t bytes); ``` Compares two memory regions and returns `1` if they are byte-for-byte identical, `0` otherwise. The comparison is timing-attack resistant: it always reads every byte and accumulates differences into a single flag before returning, regardless of where the first mismatch occurs. ## Secure zero and stack wipe ```c void fio_secure_zero(void *a_, size_t bytes); ``` Zeros `bytes` starting at `a_`. The write is performed through a `volatile` pointer so the compiler cannot optimize it away. Use this for passwords, keys, and other sensitive material that must be erased from memory. ```c #define FIO_MEM_STACK_WIPE(pages) ``` Allocates a volatile stack array of `(pages) * 4096` bytes and initializes it to zero. Useful for clearing sensitive stack scratch space; the `(void)stack_mem` use keeps the array alive through the scope. # Endian and Byte Ordering Macros and inline helpers for detecting and converting byte order. All are defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). facil.io assumes one of the two common orderings: big-endian or little-endian. Mixed-endian systems are not supported. ## Compile-Time Detection The library normalizes two common compiler-provided symbols so downstream code can test endianness with simple `#if` checks. - **`__BIG_ENDIAN__`** — set to `1` on big-endian systems, `0` on little-endian systems. - **`__LITTLE_ENDIAN__`** — set to `1` on little-endian systems, `0` on big-endian systems. The library also checks `__BYTE_ORDER__` as an input hint when the compiler provides it. If the compiler already defines `__BIG_ENDIAN__`, `__LITTLE_ENDIAN__`, or `__BYTE_ORDER__`, those values are honored. Otherwise facil.io defines `FIO_ENDIAN_ORDER_TEST` (`'1234'`), `FIO_LITTLE_ENDIAN_TEST` (`0x31323334UL`), and `FIO_BIG_ENDIAN_TEST` (`0x34333231UL`), then compares the unprefixed names in `#if ENDIAN_ORDER_TEST == LITTLE_ENDIAN_TEST` and `#elif ENDIAN_ORDER_TEST == BIG_ENDIAN_TEST`. If neither matches, compilation fails with an error. The comparison deliberately uses the unprefixed names `ENDIAN_ORDER_TEST`, `LITTLE_ENDIAN_TEST`, and `BIG_ENDIAN_TEST`; those exact identifiers are not defined by facil.io. This mirrors the source. These are constants, not variables; use them in `#if __BIG_ENDIAN__` rather than runtime `if` statements when possible. ## Runtime Detection ```c unsigned int fio_is_little_endian(void); unsigned int fio_is_big_endian(void); ``` Runtime tests that return a non-zero value when the machine matches the named ordering. `fio_is_big_endian` is implemented as `!fio_is_little_endian()`. Compilers usually constant-fold the result, but the functions are provided for cases where a runtime check is clearer. ## Byte Swapping Reverse the byte order of a fixed-width integer. Each macro expands to the corresponding compiler builtin when available; otherwise it falls back to a portable bit-shifting implementation. - `fio_bswap8(i)` — identity, defined so generic code can use the `8`-bit width without special cases. - `fio_bswap16(i)` — swap bytes of a 16-bit integer. - `fio_bswap32(i)` — swap bytes of a 32-bit integer. - `fio_bswap64(i)` — swap bytes of a 64-bit integer. - `fio_bswap128(i)` — swap bytes of a 128-bit integer. Only available when the compiler supports `__int128`/`__uint128_t`. ## Host ↔ Network Conversions Network byte order is big-endian. These macros convert between the local byte order and network byte order. - `fio_lton16(i)`, `fio_lton32(i)`, `fio_lton64(i)` — local to network. - `fio_ntol16(i)`, `fio_ntol32(i)`, `fio_ntol64(i)` — network to local. - `fio_lton128(i)` — local to network, defined only on little-endian systems where the compiler supports `__int128`/`__uint128_t`. - `fio_ntol128(i)` — network to local, defined only when the compiler supports `__int128`/`__uint128_t`. On big-endian systems the 16/32/64-bit macros are no-ops. On little-endian systems they byte-swap. `fio_lton8(i)` and `fio_ntol8(i)` are also defined as identities so width-generic code can treat 8-bit values uniformly. ## Host ↔ Little-Endian Conversions These helpers force values into little-endian byte order, which is also the ordering used by several internal data structures. - `fio_ltole16(i)`, `fio_ltole32(i)`, `fio_ltole64(i)` — local to little-endian. - `fio_ltole128(i)` — local to little-endian, only available when the compiler supports `__int128`/`__uint128_t`. On little-endian systems they are no-ops. On big-endian systems they byte-swap. `fio_ltole8(i)` is defined as an identity for generic width handling. ## Endian-Aware Shifts A pair of shift macros whose direction depends on the host endianness. They are used in code that processes multi-byte values as bit patterns where the conceptual "forward" or "backward" direction reverses with endianness. On little-endian systems: ```c #define FIO_SHIFT_FORWARDS(i, bits) ((i) << (bits)) #define FIO_SHIFT_BACKWARDS(i, bits) ((i) >> (bits)) ``` On big-endian systems they are defined only when the compiler supports `__int128`/`__uint128_t`: ```c #define FIO_SHIFT_FORWARDS(i, bits) ((i) >> (bits)) #define FIO_SHIFT_BACKWARDS(i, bits) ((i) << (bits)) ``` ## See Also - [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) — source of truth for all macros and functions above. - [`./000 core.md`](https://facil.io/0.8.x/facil-io-stl-core/) — overview of core types and helpers. # Linked Lists ```c #include "fio-stl.h" ``` Small, sharp linked-list macros from [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). There are two families: - pointer-based circular doubly linked lists, using `next` / `prev` pointers; - indexed circular doubly linked lists, using integer offsets into an array. The macros do not allocate memory, do not own stored objects, and do not check that nodes are initialized. Fast tools, pointy edges. ### Types #### `fio_list_node_s` ```c typedef struct fio_list_node_s { struct fio_list_node_s *next; struct fio_list_node_s *prev; } fio_list_node_s; ``` The pointer-based list link. Embed it inside the struct you want to place in a list. #### `FIO_LIST_NODE` ```c #define FIO_LIST_NODE fio_list_node_s ``` Semantic alias for an embedded node field. #### `FIO_LIST_HEAD` ```c #define FIO_LIST_HEAD fio_list_node_s ``` Semantic alias for a list head/root. A list head is also a node, and an empty list points back to itself. #### `fio_index32_node_s` ```c typedef struct fio_index32_node_s { uint32_t next; uint32_t prev; } fio_index32_node_s; ``` A 32-bit indexed-list link. It stores indexes into a caller-owned array. #### `fio_index16_node_s` ```c typedef struct fio_index16_node_s { uint16_t next; uint16_t prev; } fio_index16_node_s; ``` A 16-bit indexed-list link. #### `fio_index8_node_s` ```c typedef struct fio_index8_node_s { uint8_t next; uint8_t prev; } fio_index8_node_s; ``` An 8-bit indexed-list link. #### `FIO_INDEXED_LIST32_NODE`, `FIO_INDEXED_LIST32_HEAD` ```c #define FIO_INDEXED_LIST32_NODE fio_index32_node_s #define FIO_INDEXED_LIST32_HEAD uint32_t ``` 32-bit indexed-list node and head index types. #### `FIO_INDEXED_LIST16_NODE`, `FIO_INDEXED_LIST16_HEAD` ```c #define FIO_INDEXED_LIST16_NODE fio_index16_node_s #define FIO_INDEXED_LIST16_HEAD uint16_t ``` 16-bit indexed-list node and head index types. #### `FIO_INDEXED_LIST8_NODE`, `FIO_INDEXED_LIST8_HEAD` ```c #define FIO_INDEXED_LIST8_NODE fio_index8_node_s #define FIO_INDEXED_LIST8_HEAD uint8_t ``` 8-bit indexed-list node and head index types. ### API Functions #### `FIO_LIST_INIT` ```c #define FIO_LIST_INIT(obj) \ (fio_list_node_s) { .next = &(obj), .prev = &(obj) } ``` Initializer for a pointer-list node or head. The node points to itself. Use it for heads: ```c FIO_LIST_HEAD list = FIO_LIST_INIT(list); ``` and for standalone nodes before insertion: ```c item_s item = {.node = FIO_LIST_INIT(item.node)}; ``` #### `FIO_LIST_EACH` ```c #define FIO_LIST_EACH(type, node_name, head, pos) \ for (type *pos = FIO_PTR_FROM_FIELD(type, node_name, (head)->next), \ *next____p_ls_##pos = \ FIO_PTR_FROM_FIELD(type, node_name, (head)->next->next); \ pos != FIO_PTR_FROM_FIELD(type, node_name, (head)); \ (pos = next____p_ls_##pos), \ (next____p_ls_##pos = \ FIO_PTR_FROM_FIELD(type, \ node_name, \ next____p_ls_##pos->node_name.next))) ``` Iterates forward over every object after `head`. `pos` is declared by the macro as `type *`. The next pointer is cached before the loop body, so removing the current `pos` is supported. #### `FIO_LIST_EACH_REVERSED` ```c #define FIO_LIST_EACH_REVERSED(type, node_name, head, pos) \ for (type *pos = FIO_PTR_FROM_FIELD(type, node_name, (head)->prev), \ *next____p_ls_##pos = \ FIO_PTR_FROM_FIELD(type, node_name, (head)->next->prev); \ pos != FIO_PTR_FROM_FIELD(type, node_name, (head)); \ (pos = next____p_ls_##pos), \ (next____p_ls_##pos = \ FIO_PTR_FROM_FIELD(type, \ node_name, \ next____p_ls_##pos->node_name.prev))) ``` Attempts to iterate backward over every object before `head`. `pos` is declared by the macro as `type *`. **Note:** the current implementation caches the next pointer from `(head)->next->prev`, which equals `head` for a normal list, so the loop stops after visiting the first reversed node. Treat full reverse iteration as limited/buggy until the source macro is fixed. #### `FIO_LIST_PUSH` ```c #define FIO_LIST_PUSH(head, n) \ do { \ (n)->prev = (head)->prev; \ (n)->next = (head); \ (head)->prev->next = (n); \ (head)->prev = (n); \ } while (0) ``` Pushes node `n` to the end of the circular list, just before `head`. Both `head` and `n` must point to initialized `FIO_LIST_NODE` / `FIO_LIST_HEAD` objects. The macro does not check whether `n` already belongs to another list. #### `FIO_LIST_REMOVE` ```c #define FIO_LIST_REMOVE(n) \ do { \ (n)->prev->next = (n)->next; \ (n)->next->prev = (n)->prev; \ } while (0) ``` Removes `n` from its current list. The node's own `next` and `prev` fields are left pointing at the old neighbors. #### `FIO_LIST_REMOVE_RESET` ```c #define FIO_LIST_REMOVE_RESET(n) \ do { \ (n)->prev->next = (n)->next; \ (n)->next->prev = (n)->prev; \ (n)->next = (n)->prev = (n); \ } while (0) ``` Removes `n`, then resets it to a self-linked node. #### `FIO_LIST_POP` ```c #define FIO_LIST_POP(type, node_name, dest_ptr, head) \ do { \ (dest_ptr) = FIO_PTR_FROM_FIELD(type, node_name, ((head)->next)); \ FIO_LIST_REMOVE(&(dest_ptr)->node_name); \ } while (0) ``` Removes the first object after `head` and stores the containing object pointer in `dest_ptr`. Do not call this on an empty list. It will treat the head as a stored object and wander into undefined behavior. #### `FIO_LIST_IS_EMPTY` ```c #define FIO_LIST_IS_EMPTY(head) \ ((!(head)) || ((!(head)->next) | ((head)->next == (head)))) ``` Returns non-zero if `head` is `NULL`, `head->next` is `NULL`, or the head points to itself. #### `FIO_INDEXED_LIST_PUSH` ```c #define FIO_INDEXED_LIST_PUSH(root, node_name, head, i) \ do { \ register const size_t n__ = (i); \ (root)[n__].node_name.prev = (root)[(head)].node_name.prev; \ (root)[n__].node_name.next = (head); \ (root)[(root)[(head)].node_name.prev].node_name.next = (n__); \ (root)[(head)].node_name.prev = (n__); \ } while (0) ``` Adds index `i` to the end of the indexed circular list, just before `head`. `head` is not changed. `root` is the array containing all elements, and `node_name` is the embedded indexed-list node field. #### `FIO_INDEXED_LIST_UNSHIFT` ```c #define FIO_INDEXED_LIST_UNSHIFT(root, node_name, head, i) \ do { \ register const size_t n__ = (i); \ (root)[n__].node_name.next = (root)[(head)].node_name.next; \ (root)[n__].node_name.prev = (head); \ (root)[(root)[(head)].node_name.next].node_name.prev = (n__); \ (root)[(head)].node_name.next = (n__); \ (head) = (n__); \ } while (0) ``` Adds index `i` to the beginning of the list and assigns `head = i`. The `head` argument must be an assignable lvalue. #### `FIO_INDEXED_LIST_REMOVE` ```c #define FIO_INDEXED_LIST_REMOVE(root, node_name, i) \ do { \ register const size_t n__ = (i); \ (root)[(root)[n__].node_name.prev].node_name.next = \ (root)[n__].node_name.next; \ (root)[(root)[n__].node_name.next].node_name.prev = \ (root)[n__].node_name.prev; \ } while (0) ``` Removes index `i` from its current indexed list. The removed node keeps its previous links. #### `FIO_INDEXED_LIST_REMOVE_RESET` ```c #define FIO_INDEXED_LIST_REMOVE_RESET(root, node_name, i) \ do { \ register const size_t n__ = (i); \ (root)[(root)[n__].node_name.prev].node_name.next = \ (root)[n__].node_name.next; \ (root)[(root)[n__].node_name.next].node_name.prev = \ (root)[n__].node_name.prev; \ (root)[n__].node_name.next = (root)[n__].node_name.prev = (n__); \ } while (0) ``` Removes index `i`, then resets its indexed node so `next == prev == i`. #### `FIO_INDEXED_LIST_EACH` ```c #define FIO_INDEXED_LIST_EACH(root, node_name, head, pos) \ for (size_t pos = (head), \ stooper___hd = (head), \ stopper___ils___ = 0, \ pos##___nxt = (root)[(head)].node_name.next; \ !stopper___ils___; \ (stopper___ils___ = ((pos = pos##___nxt) == stooper___hd)), \ pos##___nxt = (root)[pos].node_name.next) ``` Iterates forward over an indexed circular list, including `head`. `pos` is declared by the macro as `size_t`. `head` must be a valid index. Empty-list sentinels are a caller convention, not something this macro handles. #### `FIO_INDEXED_LIST_EACH_REVERSED` ```c #define FIO_INDEXED_LIST_EACH_REVERSED(root, node_name, head, pos) \ for (size_t pos = ((root)[(head)].node_name.prev), \ pos##___nxt = \ ((root)[((root)[(head)].node_name.prev)].node_name.prev), \ stooper___hd = (head), \ stopper___ils___ = 0; \ !stopper___ils___; \ ((stopper___ils___ = (pos == stooper___hd)), \ (pos = pos##___nxt), \ (pos##___nxt = (root)[pos##___nxt].node_name.prev))) ``` Iterates backward over an indexed circular list, including the tail and eventually the head. ### Examples #### Pointer list ```c #include "fio-stl.h" typedef struct item_s { FIO_LIST_NODE node; int value; } item_s; int main(void) { FIO_LIST_HEAD list = FIO_LIST_INIT(list); item_s a = {.node = FIO_LIST_INIT(a.node), .value = 1}; item_s b = {.node = FIO_LIST_INIT(b.node), .value = 2}; FIO_LIST_PUSH(&list, &a.node); FIO_LIST_PUSH(&list, &b.node); int sum = 0; FIO_LIST_EACH(item_s, node, &list, pos) { sum += pos->value; } item_s *first = 0; FIO_LIST_POP(item_s, node, first, &list); return (sum == 3 && first == &a) ? 0 : 1; } ``` #### Safe removal while iterating ```c #include "fio-stl.h" typedef struct item_s { FIO_LIST_NODE node; int value; } item_s; int main(void) { FIO_LIST_HEAD list = FIO_LIST_INIT(list); item_s a = {.node = FIO_LIST_INIT(a.node), .value = 1}; item_s b = {.node = FIO_LIST_INIT(b.node), .value = 2}; FIO_LIST_PUSH(&list, &a.node); FIO_LIST_PUSH(&list, &b.node); FIO_LIST_EACH(item_s, node, &list, pos) { if (pos->value == 1) FIO_LIST_REMOVE_RESET(&pos->node); } return FIO_LIST_IS_EMPTY(&list) ? 1 : 0; } ``` #### Indexed list ```c #include "fio-stl.h" typedef struct slot_s { FIO_INDEXED_LIST16_NODE node; int value; } slot_s; int main(void) { slot_s slots[3] = {{{0}}}; FIO_INDEXED_LIST16_HEAD head = 0; slots[0].node.next = slots[0].node.prev = 0; slots[0].value = 10; slots[1].node.next = slots[1].node.prev = 1; slots[1].value = 20; slots[2].node.next = slots[2].node.prev = 2; slots[2].value = 30; FIO_INDEXED_LIST_PUSH(slots, node, head, 1); FIO_INDEXED_LIST_PUSH(slots, node, head, 2); int sum = 0; FIO_INDEXED_LIST_EACH(slots, node, head, pos) { sum += slots[pos].value; } FIO_INDEXED_LIST_REMOVE_RESET(slots, node, 1); return (sum == 60 && slots[1].node.next == 1) ? 0 : 1; } ``` ### Safety Notes #### Initialization Every head or node must be initialized before use. Pointer lists have `FIO_LIST_INIT`; indexed lists do not have a dedicated init macro, so initialize an indexed node with: ```c root[i].node.next = root[i].node.prev = i; ``` #### Empty lists Pointer lists have a natural empty state: `head->next == head`. `FIO_LIST_IS_EMPTY` checks it. Indexed lists usually reserve an out-of-range value, often `(type)-1`, to mean empty. The indexed macros do not check for this sentinel; call them only when `head` is a valid index. #### Index width The indexed macros use `size_t` temporaries, then store into `uint32_t`, `uint16_t`, or `uint8_t` node fields. Keep every index representable by the chosen node type. #### Removal and iteration The iteration macros cache the next node/index before running the loop body, so removing the current non-head item is supported. If you remove or reset the current head in an indexed list, update your `head` value deliberately. #### Memory ownership Lists own no memory. Removing or popping unlinks nodes only; it does not destroy containing objects. #### Thread safety Mutating a list is not atomic. Multiple threads may read immutable lists, but concurrent mutation needs external synchronization. #### Macro expansion All APIs here are macros. Arguments may be evaluated more than once, and helper variable names are introduced inside loops. Pass simple lvalues/pointers, not expressions with side effects. # Logging and Assertions ```c #define FIO_LOG #include "fio-stl.h" ``` Heap-allocation-free logging macros and assertion utilities. `FIO_LOG2STDERR` and the `FIO_LOG_*` macros are functional when `FIO_LOG` or `FIO_LEAK_COUNTER` is defined; otherwise they are no-ops. `FIO_LOG` uses `libc` functions (`vsnprintf`, `fwrite`) and the library helper `fio_memcpy32`. If you are building without `libc`, provide your own implementations or shadowing macros before including the module. ## Log Levels ```c #define FIO_LOG_LEVEL_NONE 0 /* no logging */ #define FIO_LOG_LEVEL_FATAL 1 /* fatal errors */ #define FIO_LOG_LEVEL_ERROR 2 /* errors and above */ #define FIO_LOG_LEVEL_WARNING 3 /* warnings and above */ #define FIO_LOG_LEVEL_INFO 4 /* info and above */ #define FIO_LOG_LEVEL_DEBUG 5 /* everything, including debug */ ``` ## Log Level Control ### `FIO_LOG_LEVEL_SET` ```c #define FIO_LOG_LEVEL_SET(new_level) fio___log_level_set(new_level) ``` Sets the application-wide logging level. Returns the new level. ### `FIO_LOG_LEVEL_GET` ```c #define FIO_LOG_LEVEL_GET() ((fio___log_level())) ``` Returns the current logging level as an integer. ```c FIO_LOG_LEVEL_SET(FIO_LOG_LEVEL_WARNING); int level = FIO_LOG_LEVEL_GET(); /* 3 */ ``` ## Configuration ### `FIO_LOG_LEVEL_DEFAULT` Initial level. Defaults to `FIO_LOG_LEVEL_INFO` unless `DEBUG` is defined and truthy (`defined(DEBUG) && DEBUG`), in which case it is `FIO_LOG_LEVEL_DEBUG`. ### `FIO_STDERR_FILE` ```c #define FIO_STDERR_FILE stderr ``` Output destination for all logging. Define it before including `./fio-stl.h` to redirect logs to another `FILE*`. ### `FIO_LOG_LENGTH_LIMIT` ```c #define FIO_LOG_LENGTH_LIMIT 1024 ``` Log buffer size. Keep this above `128`. - When `FIO_LOG_LENGTH_LIMIT > 128`, the formatted message is limited to `FIO_LOG_LENGTH_LIMIT - 34` bytes, followed by a 32-byte truncation warning. - When `FIO_LOG_LENGTH_LIMIT <= 128`, `vsnprintf` is capped at `FIO_LOG_LENGTH_LIMIT - 2` bytes; the truncation warning is appended afterward. - Values `<= 2` are dangerous: the `vsnprintf` size argument underflows/overflows. ## Core Logging Function ### `FIO_LOG2STDERR` ```c void FIO_LOG2STDERR(const char *format, ...); ``` `printf`-style output to `FIO_STDERR_FILE` using only stack-allocated memory. Truncation follows the two `FIO_LOG_LENGTH_LIMIT` paths described above. In `./fio-stl/001 logging.h` the name is `#undef`-ed before the `static` function definition, so a pre-include macro is removed and a post-inclusion function definition would conflict. Override it after inclusion only by defining `FIO_LOG2STDERR` as a macro. ## Log Macros Most macros check the current log level before printing. `FIO_LOG_WRITE` has no level check and always prefixes output with the file and line number. | Macro | Level | Prefix | |-------|-------|--------| | `FIO_LOG_FATAL(...)` | ≥ `FATAL` | `FATAL:` (bold inverse) | | `FIO_LOG_ERROR(...)` | ≥ `ERROR` | `ERROR:` (bold) | | `FIO_LOG_SECURITY(...)` | ≥ `ERROR` | `SECURITY:` (bold) | | `FIO_LOG_WARNING(...)` | ≥ `WARNING` | `WARNING:` (dim) | | `FIO_LOG_INFO(...)` | ≥ `INFO` | `INFO:` | | `FIO_LOG_DEBUG(...)` | ≥ `DEBUG` | `DEBUG:` + file:line | | `FIO_LOG_DEBUG2(...)` | ≥ `DEBUG` | `DEBUG:` | | `FIO_LOG_WRITE(...)` | always | file:line | ## Debug-Only Log Macros These expand to their non-`D` counterparts when `DEBUG` is defined, and to no-ops otherwise. - `FIO_LOG_DDEBUG(...)` - `FIO_LOG_DDEBUG2(...)` - `FIO_LOG_DERROR(...)` - `FIO_LOG_DSECURITY(...)` - `FIO_LOG_DWARNING(...)` - `FIO_LOG_DINFO(...)` ## Assertions ### `FIO_ASSERT` ```c #define FIO_ASSERT(cond, ...) ``` If `cond` is false, prints a fatal message, prints `errno` and its string, sends `SIGINT` in debug builds, and calls `exit(-1)`. ### `FIO_ASSERT_ALLOC` ```c #define FIO_ASSERT_ALLOC(ptr) FIO_ASSERT((ptr), "memory allocation failed.") ``` Convenience wrapper for allocation failures. ### `FIO_ASSERT_DEBUG` ```c #define FIO_ASSERT_DEBUG(cond, ...) ``` Active only when `DEBUG` is defined. On failure it behaves like `FIO_ASSERT`, printing the file and line number before terminating. ### `FIO_ASSERT_STATIC` ```c #define FIO_ASSERT_STATIC(cond, msg) ``` Compile-time assertion. `cond` must be a constant expression; `msg` is a string literal with no format specifiers. Falls back to a sized-array trick on pre-C11 compilers. See also: [Compiler Attributes](./001 compiler attributes.md). ## Leak Counter Helpers Enabled by `FIO_LEAK_COUNTER` (default `1`). `FIO_NO_LOG` and `FIO_LEAK_COUNTER` are mutually exclusive because leak reports are printed through the log. ### `FIO_LEAK_COUNTER_DEF` ```c #define FIO_LEAK_COUNTER_DEF(name) ``` Defines a named counter and a cleanup function that reports remaining allocations at process exit. ### `FIO_LEAK_COUNTER_ON_ALLOC` ```c #define FIO_LEAK_COUNTER_ON_ALLOC(name) ``` Increment the named counter. Call after a successful allocation. ### `FIO_LEAK_COUNTER_ON_FREE` ```c #define FIO_LEAK_COUNTER_ON_FREE(name) ``` Decrement the named counter. Call when memory is freed. Detects double-free. ### `FIO_LEAK_COUNTER_COUNT` ```c #define FIO_LEAK_COUNTER_COUNT(name) ``` Returns the current value of the named counter. ### `FIO_LEAK_COUNTER_SKIP_EXIT` ```c #define FIO_LEAK_COUNTER_SKIP_EXIT 0 ``` Set to `1` to prevent automatic leak reporting at exit. ## Example ```c #define FIO_LOG #include "fio-stl.h" int main(void) { FIO_LOG_LEVEL_SET(FIO_LOG_LEVEL_WARNING); FIO_LOG_INFO("not printed"); FIO_LOG_WARNING("number invalid: %d", 42); FIO_LOG2STDERR("direct message"); void *ptr = malloc(100); FIO_ASSERT_ALLOC(ptr); FIO_ASSERT(ptr != NULL, "expected a pointer"); free(ptr); return 0; } ``` # Core Math Helpers Low-level integer arithmetic, prime testing, and multi-precision primitives defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). These functions work on plain `uint64_t` arrays. The higher-level division, shift, and inversion helpers live in [`./002 math.h`](https://facil.io/0.8.x/api/002-math/), and the wide-union wrappers are documented in [`./001 vector math.md`](https://facil.io/0.8.x/vector-math/). ## Hash primes Four families of pre-selected prime constants, indexed `0` through `31`: ```c FIO_U8_HASH_PRIME0 ... FIO_U8_HASH_PRIME31 FIO_U16_HASH_PRIME0 ... FIO_U16_HASH_PRIME31 FIO_U32_HASH_PRIME0 ... FIO_U32_HASH_PRIME31 FIO_U64_HASH_PRIME0 ... FIO_U64_HASH_PRIME31 ``` Each fits in the named unsigned type and has about half of its bits set (`3`–`5` bits for the 8-bit primes). They are useful as hash mixers and for picking small prime bases. ## Modular arithmetic ```c uint64_t fio_math_mod_mul64(uint64_t a, uint64_t b, uint64_t mod); ``` Computes `(a * b) % mod` without overflowing 64 bits. Uses `__uint128_t` when available; otherwise falls back to a double-and-add loop. ```c uint64_t fio_math_mod_expo64(uint64_t base, uint64_t exp, uint64_t mod); ``` Right-to-left binary modular exponentiation. Calls `fio_math_mod_mul64` for each set bit of `exp`. ## Primality tests ```c bool fio_math_is_uprime(uint64_t n); bool fio_math_is_iprime(int64_t n); ``` `fio_math_is_uprime` tests whether `n` is probably prime. `fio_math_is_iprime` tests the absolute value of `n`. For `n < FIO_PRIME_TABLE_LIMIT` (default `1024`) the test is deterministic using a small sieve. Larger values use Miller–Rabin with `10` rounds. Internal helpers `fio___is_prime_table` and `fio___is_prime_maybe` implement the two paths. ## 64-bit add / sub / mul with carry ```c uint64_t fio_math_addc64(uint64_t a, uint64_t b, uint64_t carry_in, uint64_t *carry_out); uint64_t fio_math_subc64(uint64_t a, uint64_t b, uint64_t borrow_in, uint64_t *borrow_out); uint64_t fio_math_mulc64(uint64_t a, uint64_t b, uint64_t *carry_out); ``` Return the low 64 bits of `a + b + carry_in`, `a - b - borrow_in`, or `a * b`; the outgoing carry/borrow/high word is written to `*carry_out`. They use compiler builtins where available and fall back to portable word arithmetic. ## Multi-precision add / sub / mul All multi-precision routines treat arrays as little-endian `uint64_t` words: index `0` holds the least-significant word. Inputs are expected to have the same `len` (pad shorter inputs with zero). ```c bool fio_math_add(uint64_t *dest, const uint64_t *a, const uint64_t *b, size_t len); uint64_t fio_math_sub(uint64_t *dest, const uint64_t *a, const uint64_t *b, size_t len); void fio_math_mul(uint64_t *restrict dest, const uint64_t *a, const uint64_t *b, size_t len); ``` * `fio_math_add` writes `a + b` to `dest` and returns the final carry. * `fio_math_sub` writes `a - b` to `dest` and returns the final borrow. * `fio_math_mul` writes the full `len * 2` word product of `a` and `b` to `dest`. When `__uint128_t` is available, `fio_math_addc128` and `fio_math_add2` provide the same carry-style addition for 128-bit words. ## Internal multiplication helpers `fio_math_mul` dispatches to specialized helpers depending on `len`: * `fio___math_mul_long` — optimized schoolbook multiplication, loop-unrolled, with a `__uint128_t` fast path. * `fio___math_mul_256` — fully unrolled 4-word (256-bit) multiply. * `fio___math_mul_512` — semi-unrolled 8-word (512-bit) multiply. * `fio___math_mul_1024` — semi-unrolled 16-word (1024-bit) multiply, mostly used as a fallback. For large numbers: ```c #define FIO___MATH_KARATSUBA_THRESHOLD 32 ``` When `len` is at least this threshold and even, `fio_math_mul` calls `fio___math_mul_karatsuba`. ```c void fio___math_mul_karatsuba(uint64_t *restrict dest, const uint64_t *a, const uint64_t *b, size_t len); ``` `fio___math_mul_karatsuba` splits `a` and `b` into low/high halves, computes `z0`, `z1`, and `z2`, then combines them as `z2*B^2 + z1*B + z0`. It uses `fio___math_mul_norecurse` for the three half-sized sub-products and allocates scratch buffers on the stack. `len` must be even; on MSVC or pre-C++14 the stack buffers are fixed at 256 words, so inputs are limited accordingly. ## See also * [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) — source of truth. * [`./002 math.h`](https://facil.io/0.8.x/api/002-math/) — higher-level division, shift, and inversion helpers. * [`./001 vector math.md`](https://facil.io/0.8.x/vector-math/) — wide-union `fio_uXXX` arithmetic built on these primitives. # Memory Alternatives and OS Patches Defines portable fallback memory routines and small OS compatibility patches. Implemented in [`./001 memalt.h`](https://facil.io/0.8.x/api/001-memalt/) and [`./001 patches.h`](https://facil.io/0.8.x/api/001-patches/); wired into the selector macros in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). ## When `FIO_MEMALT` is used Define `FIO_MEMALT` before including the STL to substitute standard-library memory and string calls with facil.io fallbacks: ```c #define FIO_MEMALT #include "fio-stl/include.h" ``` This is useful for freestanding or restricted environments, or when you want every memory call to go through the same portable implementation. The fallbacks are correct but usually slower than a good libc, so the default is to use compiler builtins or libc instead. ## Selector wiring When `FIO_MEMALT` is defined, [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) routes any unset selector to the matching fallback: | Selector | Fallback | |----------|----------| | `FIO_MEMCPY` | `fio_memcpy` | | `FIO_MEMMOVE` | `fio_memcpy` | | `FIO_MEMCMP` | `fio_memcmp` | | `FIO_MEMCHR` | `fio_memchr` | | `FIO_MEMSET` | `fio_memset` | | `FIO_STRLEN` | `fio_strlen` | If `FIO_MEMALT` is not defined, the selectors default to `__builtin_*` when available, otherwise to the standard `memcpy`, `memset`, `memchr`, `memcmp`, and `strlen`. You can also override any selector directly without enabling `FIO_MEMALT`. ## Fallback memory routines All routines are declared in [`./001 memalt.h`](https://facil.io/0.8.x/api/001-memalt/). ### `fio_memcpy` ```c void *fio_memcpy(void *dest, const void *src, size_t bytes); ``` Copies `bytes` from `src` to `dest`. Handles overlapping regions like `memmove`. Returns `dest` immediately if `dest == src`, `bytes` is zero, or either pointer is `NULL`. For small, non-overlapping copies (≤64 bytes) it uses overlapping fixed-width copies. For larger or overlapping regions it uses a 64-byte buffered copy, choosing forward or backward direction as needed. ### `fio_memset` ```c void *fio_memset(void *restrict dest, uint64_t data, size_t bytes); ``` Fills `bytes` at `dest` with the 8-byte pattern `data`. If `data` is below `0x100`, it is broadcast to every byte to match classic `memset` behavior. Returns `dest`, or `NULL` if `dest` is `NULL`. ### `fio_memchr` ```c void *fio_memchr(const void *buffer, const char token, size_t len); ``` Finds the first byte equal to `token` in the first `len` bytes of `buffer`. Returns a pointer to the match, or `NULL` if not found, `buffer` is `NULL`, or `len` is zero. Picks the best available implementation at compile time: - AVX2 on x86-64 with intrinsics - SSE2 on x86 with intrinsics - NEON on ARM with intrinsics - Scalar 128-bit block fallback otherwise ### `fio_memcmp` ```c int fio_memcmp(const void *a, const void *b, size_t len); ``` Compares the first `len` bytes of `a` and `b`. Returns `1` if `a > b`, `-1` if `a < b`, and `0` if equal. Returns `0` immediately if `a == b` or `len` is zero. Processes 64-byte blocks for large buffers, 8-byte blocks for medium buffers, and byte-by-byte for the tail. ### `fio_strlen` ```c size_t fio_strlen(const char *str); ``` Returns the length of the NUL-terminated string `str`, or `0` if `str` is `NULL`. The implementation is a simple byte loop the compiler can auto-vectorize. Building with Address Sanitizer may flag speculative reads. ## OS patch environment helpers [`./001 patches.h`](https://facil.io/0.8.x/api/001-patches/) provides `fio_sys_env` and `fio_sys_env_set` as portable replacements for `getenv` and `setenv`. ```c char *fio_sys_env(const char *name); int fio_sys_env_set(const char *name, const char *value, int overwrite); ``` - `fio_sys_env(name)` returns the value of the environment variable, or `NULL`. - `fio_sys_env_set(name, value, overwrite)` sets the variable. If `overwrite` is zero and the variable already exists, it is left unchanged. Returns `0` on success, `-1` on failure. On POSIX these are thin wrappers around `getenv`/`setenv`. On Windows they call `GetEnvironmentVariableA` and `SetEnvironmentVariableA` directly, bypassing macro redefinitions such as the one in Ruby's `win32.h` and avoiding `wchar_t` conversion issues. ## Other OS patches [`./001 patches.h`](https://facil.io/0.8.x/api/001-patches/) also fills small portability gaps: - **macOS < 10.12**: defines a `clock_gettime` fallback using `gettimeofday`, plus `CLOCK_REALTIME` and `CLOCK_MONOTONIC` placeholders. - **Windows startup**: enables virtual terminal processing for stdout/stderr and sets binary file mode by default. - **Windows symbol gaps**: supplies inline patches for `strcasecmp`, `write`, `read`, `clock_gettime`, and `pwrite`; maps `O_*`/`S_*` constants, `fstat`/`stat`/`lseek`/`unlink`, and defines `PATH_MAX`. - **POSIX branch**: present but currently has no extra patches beyond the environment helpers. # Memory Primitives Low-level memory helpers used everywhere else in the STL. Most are defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/); the allocator abstraction macros live in [`./001 header.h`](https://facil.io/0.8.x/api/001-header/). ## Memory-Function Selector Macros These macros route standard-library-style calls to either compiler builtins or libc functions. Define any of them before inclusion to override the default. | Macro | Default | Use | |-------|---------|-----| | `FIO_MEMCPY(dest, src, n)` | `__builtin_memcpy` if available, else `memcpy` | Variable-length copy. | | `FIO_MEMMOVE(dest, src, n)` | `__builtin_memmove` if available, else `memmove` | Overlap-safe copy. | | `FIO_MEMSET(dest, c, n)` | `__builtin_memset` if available, else `memset` | Fill memory. | | `FIO_MEMCHR(ptr, c, n)` | `__builtin_memchr` if available, else `memchr` | Find byte. | | `FIO_STRLEN(str)` | `__builtin_strlen` if available, else `strlen` | C string length. | | `FIO_MEMCMP(a, b, n)` | `__builtin_memcmp` if available, else `memcmp` | Compare memory. | If `FIO_MEMALT` is defined, any selector that was not already set is routed to the matching facil.io fallback: ```c #define FIO_MEMCPY fio_memcpy #define FIO_MEMMOVE fio_memcpy #define FIO_MEMCMP fio_memcmp #define FIO_MEMCHR fio_memchr #define FIO_MEMSET fio_memset #define FIO_STRLEN fio_strlen ``` Those fallback routines are documented in `./001 memalt.h` (the memalt module). ## Fixed-Size Copy Helpers These functions copy an exact number of bytes from `src` to `dest`. They are implemented with the fixed-size `FIO_MEMCPY` path when builtins are available, otherwise with a compact byte loop. - When `__builtin_memcpy` is available, each helper returns `dest` (the `__builtin_memcpy` return value). - In the non-builtin fallback, each helper returns `dest + n`. `fio_memcpy0` always returns `dest`. ```c void *fio_memcpy0 (void *restrict dest, const void *restrict src); void *fio_memcpy1 (void *restrict dest, const void *restrict src); void *fio_memcpy2 (void *restrict dest, const void *restrict src); void *fio_memcpy3 (void *restrict dest, const void *restrict src); void *fio_memcpy4 (void *restrict dest, const void *restrict src); void *fio_memcpy5 (void *restrict dest, const void *restrict src); void *fio_memcpy6 (void *restrict dest, const void *restrict src); void *fio_memcpy7 (void *restrict dest, const void *restrict src); void *fio_memcpy8 (void *restrict dest, const void *restrict src); void *fio_memcpy16(void *restrict dest, const void *restrict src); void *fio_memcpy32(void *restrict dest, const void *restrict src); void *fio_memcpy64(void *restrict dest, const void *restrict src); void *fio_memcpy128(void *restrict dest, const void *restrict src); void *fio_memcpy256(void *restrict dest, const void *restrict src); void *fio_memcpy512(void *restrict dest, const void *restrict src); void *fio_memcpy1024(void *restrict dest, const void *restrict src); void *fio_memcpy2048(void *restrict dest, const void *restrict src); void *fio_memcpy4096(void *restrict dest, const void *restrict src); ``` ### Tail helpers `fio_memcpyNx` copies `len & N` bytes — useful for handling the tail end of a larger copy without explicit branches. ```c void *fio_memcpy0x (void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy7x (void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy15x (void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy31x (void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy63x (void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy127x(void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy255x(void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy511x(void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy1023x(void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy2047x(void *restrict dest, const void *restrict src, size_t len); void *fio_memcpy4095x(void *restrict dest, const void *restrict src, size_t len); ``` ### Internal unsafe helpers Two internal helpers back the tail functions. They assume the regions do not overlap and perform no validation. ```c void *fio___memcpy_unsafe_63x(void *restrict dest, const void *restrict src, size_t len); void *fio___memcpy_unsafe_x (void *restrict dest, const void *restrict src, size_t len); ``` - `fio___memcpy_unsafe_63x` is optimized for lengths up to 63 bytes. - `fio___memcpy_unsafe_x` handles arbitrary lengths, copying 128-byte blocks (or 256-byte blocks when `FIO_LIMIT_INTRINSIC_BUFFER` is `0`) and finishing with a 64-byte tail. These are `static`/`static inline` helpers; do not use them directly unless you are implementing a higher-level routine and have already checked the inputs. ## Unaligned Buffer Accessors Read and write integers from byte buffers that may not be naturally aligned. The suffix controls byte order: - `u` — native (unspecified) byte order. - `_le` — little-endian. - `_be` — big-endian / network byte order. ```c uint8_t fio_buf2u8u (const void *buf); uint8_t fio_buf2u8_le(const void *buf); uint8_t fio_buf2u8_be(const void *buf); void fio_u2buf8u (void *buf, uint8_t v); void fio_u2buf8_le(void *buf, uint8_t v); void fio_u2buf8_be(void *buf, uint8_t v); uint16_t fio_buf2u16u (const void *buf); uint16_t fio_buf2u16_le(const void *buf); uint16_t fio_buf2u16_be(const void *buf); void fio_u2buf16u (void *buf, uint16_t v); void fio_u2buf16_le(void *buf, uint16_t v); void fio_u2buf16_be(void *buf, uint16_t v); uint32_t fio_buf2u24u (const void *buf); uint32_t fio_buf2u24_le(const void *buf); uint32_t fio_buf2u24_be(const void *buf); void fio_u2buf24u (void *buf, uint32_t v); void fio_u2buf24_le(void *buf, uint32_t v); void fio_u2buf24_be(void *buf, uint32_t v); uint32_t fio_buf2u32u (const void *buf); uint32_t fio_buf2u32_le(const void *buf); uint32_t fio_buf2u32_be(const void *buf); void fio_u2buf32u (void *buf, uint32_t v); void fio_u2buf32_le(void *buf, uint32_t v); void fio_u2buf32_be(void *buf, uint32_t v); uint64_t fio_buf2u64u (const void *buf); uint64_t fio_buf2u64_le(const void *buf); uint64_t fio_buf2u64_be(const void *buf); void fio_u2buf64u (void *buf, uint64_t v); void fio_u2buf64_le(void *buf, uint64_t v); void fio_u2buf64_be(void *buf, uint64_t v); size_t fio_buf2zu(const void *buf); void fio_u2bufzu(void *buf, size_t v); ``` `fio_buf2u24u` / `fio_u2buf24u` and `fio_buf2zu` / `fio_u2bufzu` select the correct implementation at compile time based on host endianness and `size_t` width. Example: ```c uint8_t packet[8] = {0}; fio_u2buf32_be(packet + 2, 0x12345678); uint32_t n = fio_buf2u32_be(packet + 2); // 0x12345678 ``` ## Memory Allocator Abstraction The STL types use these macros instead of calling `malloc`/`free` directly, so a custom allocator can be plugged in by defining them before inclusion. They are reset and redefined in [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) for each inclusion cycle. ```c #define FIO_MEM_REALLOC(ptr, old_size, new_size, copy_len) realloc((ptr), (new_size)) #define FIO_MEM_FREE(ptr, size) free((ptr)) #define FIO_MEM_ALIGNMENT_SIZE sizeof(long double) #define FIO_MEM_REALLOC_IS_SAFE 0 ``` - `FIO_MEM_REALLOC` — reallocates `ptr` from `old_size` to `new_size`, copying at least `copy_len` bytes if the object moves. `NULL` `ptr` acts like `malloc`; `new_size == 0` acts like `free`. - `FIO_MEM_FREE` — frees memory allocated with `FIO_MEM_REALLOC`. - `FIO_MEM_ALIGNMENT_SIZE` — the alignment the allocator guarantees. - `FIO_MEM_REALLOC_IS_SAFE` — non-zero when the allocator zero-initializes returned memory. If the custom `fio_malloc` allocator has already been included (`H___FIO_MALLOC___H` is defined), the defaults are routed to it instead: ```c #define FIO_MEM_REALLOC(ptr, old_size, new_size, copy_len) fio_realloc2((ptr), (new_size), (copy_len)) #define FIO_MEM_FREE(ptr, size) fio_free((ptr)) #define FIO_MEM_ALIGNMENT_SIZE fio_malloc_alignment() #define FIO_MEM_REALLOC_IS_SAFE fio_realloc_is_safe() ``` Define `FIO_MEM_RESET` before inclusion to force these defaults to be re-evaluated. Define `FIO_MEMORY_DISABLE` or `FIO_MALLOC_TMP_USE_SYSTEM` to force the system allocator for an inclusion cycle. ### Recursive variants The underscore-suffixed macros exist for use during recursive `#include` cycles: ```c FIO_MEM_REALLOC_ FIO_MEM_FREE_ FIO_MEM_ALIGNMENT_SIZE_ FIO_MEM_REALLOC_IS_SAFE_ ``` They normally mirror the non-underscore versions. When `FIO_MALLOC_TMP_USE_SYSTEM` is set they are forced to the system `realloc`/`free` for that cycle only, while the non-underscore macros keep their outer meaning. ## Unaligned Memory Access Knob ```c #define FIO_UNALIGNED_MEMORY_ACCESS_ENABLED 1 ``` Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). It is auto-detected: if `FIO_UNALIGNED_ACCESS` is `1` (the default) and the target CPU is known to tolerate unaligned access — x86, x86_64, ARM64, or another architecture that defines `__ARM_FEATURE_UNALIGNED` — then `FIO_UNALIGNED_MEMORY_ACCESS_ENABLED` becomes `1`. Otherwise it is `0`. Set `FIO_UNALIGNED_ACCESS` to `0` before inclusion to disable the detection attempt, or define `FIO_UNALIGNED_MEMORY_ACCESS_ENABLED` directly to override the result. ## See Also - `./001 memalt.h` (the memalt module) — variable-length fallback memory routines (`fio_memcpy`, `fio_memset`, `fio_memcmp`, `fio_memchr`, `fio_strlen`). - [`./001 endian.md`](https://facil.io/0.8.x/endian-and-byte-ordering/) — byte-order detection and swap helpers. - [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) — source of truth for copy helpers and the unaligned-access knob. - [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) — source of truth for the allocator macros. # OS Detection [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) detects the host OS and defines a few portable helpers so the rest of the library can use one code path per platform. ## Platform macros | Macro | POSIX value | Windows value | Notes | |---|---|---|---| | `FIO_OS_POSIX` | `1` | `0` | Set on Linux, macOS, BSD, and other `__unix__`/`__linux__`/`__APPLE__` systems. | | `FIO_OS_WIN` | `0` | `1` | Set on Windows, including MinGW, MSYS2, Cygwin, and Borland. | The Windows check is evaluated first, because MinGW/MSYS2 define both `_WIN32` and `__unix__`. Treating the system as Windows avoids calling POSIX socket functions on Winsock `SOCKET` handles. After detection, `FIO_OS_WIN` always wins over `FIO_OS_POSIX`: ```c #if FIO_OS_WIN #undef FIO_OS_POSIX #define FIO_OS_POSIX 0 #endif ``` ## Unix-tools level `FIO_HAVE_UNIX_TOOLS` tells the library how Unix-like the environment is: - `0` — no Unix tools (pure MSVC). - `1` — full POSIX. - `2` — MinGW. - `3` — Cygwin. It is used to decide whether functions such as `pipe()`, `fcntl()`, and POSIX signal APIs are available. ## Process helpers | Macro | POSIX | Windows | |---|---|---| | `fio_getpid` | `getpid` | `_getpid` | | `FIO_KILL_SELF()` | `kill(0, SIGINT)` | `GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)` | `FIO_KILL_SELF` is guarded by `#ifndef`, so you can define your own version before including the header. The default implementation is used by the assertion/debug machinery to deliver a signal to the running process. ## Thread sleep and yield | Macro | POSIX | Windows | |---|---|---| | `FIO_THREAD_WAIT(nano_sec)` | `nanosleep` | `Sleep` | | `FIO_THREAD_YIELD()` | see below | see below | | `FIO_THREAD_RESCHEDULE()` | `FIO_THREAD_WAIT(4)` | `FIO_THREAD_WAIT(4)` | `FIO_THREAD_WAIT(nano_sec)` sleeps the current thread for the requested number of nanoseconds. On POSIX it builds a `struct timespec` and calls `nanosleep`. On Windows it converts the interval to milliseconds and calls `Sleep`; any non-zero request is rounded up to at least 1 ms. `FIO_THREAD_YIELD()` hints the processor that the thread is in a spin loop. On x86/x86-64 with GCC or Clang it emits a `pause` instruction; on ARM/AArch64 with GCC or Clang it emits a `yield` instruction; with MSVC it calls `YieldProcessor()`; otherwise it falls back to `sched_yield()` on POSIX. `FIO_THREAD_RESCHEDULE()` yields the thread by sleeping for 4 nanoseconds (`FIO_THREAD_WAIT(4)`). It is used by the lock code to back off busy waits without fully suspending the thread. ## Portable type patches On pure MSVC `ssize_t` is missing, so the core header defines it from `SSIZE_T`: ```c typedef SSIZE_T ssize_t; ``` `SSIZE_MAX` and `SSIZE_MIN` are also provided if the system headers do not: ```c #ifndef SSIZE_MAX #define SSIZE_MAX ((ssize_t)((~(size_t)0) >> 1)) #endif #ifndef SSIZE_MIN #define SSIZE_MIN ((ssize_t)(~((~(size_t)0) >> 1))) #endif ``` ## `printf`-style format checking The `FIO___PRINTF_STYLE(string_index, check_index)` macro is defined differently on each toolchain so the compiler can check `printf`-style arguments. MinGW/Cygwin use the MinGW format attribute, pure MSVC leaves it empty, and POSIX/GCC/Clang use the standard `printf` format attribute. It is documented in [`./001 compiler attributes.md`](https://facil.io/0.8.x/compiler-attributes/). # Core Random PRNG An optionally-deterministic 128-bit pseudo-random number generator defined by a macro. It is a low-level building block for hash functions, test harnesses, and other internals that need an optionally repeatable, fast stream of bits. For the high-level random module — `fio_rand64`, secure bytes, hashing, etc. — see [`./002 random.h`](https://facil.io/0.8.x/api/002-random/). The implementation is in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). ## `fio_cycle_counter` ```c uint64_t fio_cycle_counter(void); ``` Returns a hardware cycle counter when one is available, or a number otherwise. In non-deterministic mode, the PRNG uses this for timing-based jitter during reseeding. ## `FIO_DEFINE_RANDOM128_FN` ```c #define FIO_DEFINE_RANDOM128_FN(extern, name, reseed_log, seed_offset) ``` Defines a named PRNG instance and its API. The most common deterministic form looks like: ```c FIO_DEFINE_RANDOM128_FN(static, my_rng, 0, 0x1234) ``` Parameters: - `extern` — linkage prefix, e.g. `static` or `FIO_SFUNC`. - `name` — base name for all generated functions and internal state. - `reseed_log` — automatic reseed interval, `2^reseed_log` calls. Set to `0` for a fully deterministic stream. - `seed_offset` — arbitrary offset added to the default seed constants. ### Non-Deterministic Mode ```c FIO_DEFINE_RANDOM128_FN(static, my_rng, 5, 0x1234) ``` When `reseed_log` is non-zero (and less than `64`), the generator calls `name_reseed()` automatically every `2^reseed_log` calls, mixing in timing data and the cycle counter. This makes the stream non-deterministic, and improves entropy. In this example, reseeding is set to `5`, resulting in reseeding every 32 calls (collecting new entropy every 4,096 bytes). ## Generated API For a definition `FIO_DEFINE_RANDOM128_FN(static, my_rng, 0, 0)` the following functions are generated: ### `my_rng128` ```c fio_u128 my_rng128(void); ``` Returns 128 pseudo-random bits. The return type is [`fio_u128`](https://facil.io/0.8.x/vector-math/), so the result can also be read through `.u64[0]`, `.u64[1]`, `.u8`, etc. ### `my_rng64` ```c uint64_t my_rng64(void); ``` Returns 64 pseudo-random bits. Each call consumes half of a 128-bit result; a new 128-bit value is generated only every other call. ### `my_rng_bytes` ```c void my_rng_bytes(void *dest, size_t len); ``` Fills `dest` with `len` pseudo-random bytes. Does nothing if `dest` is `NULL` or `len` is zero. ### `my_rng_reset` ```c void my_rng_reset(void); ``` Resets the generator to its initial seeded state. ### `my_rng_reseed` ```c void my_rng_reseed(void); ``` Re-seeds the generator using `CLOCK_MONOTONIC`, address/bits of local state, and `fio_cycle_counter()`. Useful after `fork()` or when you want to de-correlate separate processes. ### `my_rng_on_fork` ```c void my_rng_on_fork(void *is_null); ``` Calls `my_rng_reseed()`. Fork-handler helper that takes an ignored `void *` argument. ## Example ```c FIO_DEFINE_RANDOM128_FN(static, my_rng, 0, 0) void roll_die(int n) { my_rng_reset(); for (int i = 0; i < n; ++i) { uint64_t r = my_rng64(); printf("roll %d: %llu\n", i, (unsigned long long)(r % 6 + 1)); } } ``` Because `reseed_log` is `0`, the same seed always produces the same sequence. ## Determinism and fork safety - Set `reseed_log = 0` for deterministic, repeatable output. - Set `reseed_log` to a practical value such as `31` for a non-deterministic stream that reseeds automatically. - After `fork()`, call `name_reseed()` or register a wrapper that calls `name_on_fork()` with `pthread_atfork()` to avoid duplicated sequences in child processes. ## Testing Test results for this generator are recorded in [`./721 rand128 macro.md`](https://facil.io/0.8.x/pseudo-random-and-hashing/). ## See also - [`./002 random.h`](https://facil.io/0.8.x/api/002-random/) — high-level random/hashing module. - [`./001 vector math.md`](https://facil.io/0.8.x/vector-math/) — the `fio_u128` return type. - [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) — source of truth. # Fake x86 SIMD ```c #define FIO_FAKE_X86 /* or FIO_FAKE_X86_SHADOW */ #include "fio-stl.h" ``` Portable x86 SIMD intrinsics for code that wants the x86 spell book even when the CPU does not. The implementation lives in [`./001 fx86.h`](https://github.com/facil-io/cstl001%20fx86.h) and uses the core wide unions from [`./000 core.h`](https://facil.io/0.8.x/api/000-core/), especially `fio_u128` and `fio_u256`. The module is compiled only when `FIO_FAKE_X86` or `FIO_FAKE_X86_SHADOW` is defined before including the STL. ### Configuration Macros #### `FIO_FAKE_X86` ```c #define FIO_FAKE_X86 ``` Enables the namespaced fake-intrinsic API: `fio_fx86_*` and `fio_fx86_256_*`. On real x86 with `FIO___HAS_X86_INTRIN`, supported functions pass through to native intrinsics when the matching feature macro exists (`__SSE2__`, `__AES__`, `__AVX2__`, and friends). Otherwise the fallback code copies through `fio_u128` / `fio_u256` and performs lane-wise C operations. #### `FIO_FAKE_X86_SHADOW` ```c #define FIO_FAKE_X86_SHADOW ``` Enables the same API as `FIO_FAKE_X86`, then shadows real intrinsic names with macros: ```c #define __m128i fio_fx86_m128i #define __m256i fio_fx86_m256i #define _mm_xor_si128(a, b) fio_fx86_xor_si128((a), (b)) #define _mm256_xor_si256(a, b) fio_fx86_256_xor_si256((a), (b)) ``` Shadow mode is meant for compiling x86 code paths on non-x86 machines. It also undefines ARM intrinsic guard macros such as `FIO___HAS_ARM_INTRIN`, `FIO___HAS_ARM_SHA3_INTRIN`, `__ARM_NEON`, and `__ARM_FEATURE_CRYPTO`, then defines the x86 feature guards used by downstream code (`FIO___HAS_X86_INTRIN`, `__SSE2__`, `__SSSE3__`, `__SSE4_1__`, `__AES__`, `__PCLMUL__`, `__SHA__`, `__SHA512__`, `__AVX2__`, etc.). Shadowed names are macros. Keep arguments simple and avoid depending on compiler-specific intrinsic types outside the code path being shadowed. #### `FIO___HAS_X86_INTRIN` ```c #define FIO___HAS_X86_INTRIN 1 #include ``` Core detection flag from `000 core.h`. It is defined on x86_64 when intrinsics are allowed and `immintrin.h` is available. Intrinsic detection is disabled when `DEBUG` or `NO_INTRIN` is defined. #### `FIO___HAS_ARM_INTRIN` ```c #define FIO___HAS_ARM_INTRIN 1 #include #include ``` Core detection flag from `000 core.h`. It is defined when ARM crypto + NEON headers are available. `FIO_FAKE_X86_SHADOW` undefines it so fake x86 code paths can be selected instead of ARM paths. #### `FIO_FOR_UNROLL_GROUP_SIZE` ```c #define FIO_FOR_UNROLL_GROUP_SIZE ((size_t)256U) ``` The byte stride used by `FIO_FOR_UNROLL` for compiler-friendly batches. Most CPUs should be able to hold at least 512 bytes in their registers. The default value assumes half that is available for the optimized loop, as the action within each loop will likely require some available registers. #### `FIO_FOR_UNROLL` ```c #define FIO_FOR_UNROLL(iterations, size_of_loop, i, action) \ do { \ size_t i = 0; \ const size_t fio___unroll_remainder__ = \ ((iterations) & ((FIO_FOR_UNROLL_GROUP_SIZE / (size_of_loop)) - 1)); \ if (fio___unroll_remainder__) \ for (; i < fio___unroll_remainder__; ++i) \ action; \ if (iterations) \ for (; !((iterations) + 1) || (i < (iterations));) \ for (size_t j__loop__ = 0; \ j__loop__ < (FIO_FOR_UNROLL_GROUP_SIZE / (size_of_loop)); \ ++j__loop__, ++i) \ action; \ } while (0) ``` Runs a remainder loop, then fixed 256-byte batches. The macro declares `size_t i` inside the expansion; `action` may use that name. #### `_MM_HINT_T0`, `_MM_HINT_T1`, `_MM_HINT_T2`, `_MM_HINT_NTA` ```c #define _MM_HINT_T0 3 #define _MM_HINT_T1 2 #define _MM_HINT_T2 1 #define _MM_HINT_NTA 0 ``` Defined in fake mode when native x86 types are not present, so `fio_fx86_prefetch` and shadowed `_mm_prefetch` calls can compile. ### Types #### `fio_u128` ```c typedef union fio_u128 { FIO___UXXX_UGRP_DEF(128); #if defined(__SIZEOF_INT128__) __uint128_t alignment_for_u128_[1]; #endif } fio_u128 FIO_ALIGN(16); ``` The 128-bit storage union used by the fallback implementation. It exposes unsigned, signed, floating-point, and vector views (`u8`, `u16`, `u32`, `u64`, `i8`, `i16`, `i32`, `i64`, `x8`, `x16`, `x32`, `x64`, etc.). #### `fio_u256` ```c typedef union fio_u256 { fio_u128 u128[2]; FIO___UXXX_UGRP_DEF(256); #if defined(__SIZEOF_INT256__) __uint256_t alignment_for_u256_[1]; #endif } fio_u256 FIO_ALIGN(32); ``` The 256-bit storage union used by AVX2 and SHA-512 fallbacks. It includes all `fio_u128`-style views plus `u128[2]`. #### `FIO_UXXX_X64_DEF`, `FIO_UXXX_X32_DEF`, `FIO_UXXX_X16_DEF`, `FIO_UXXX_X8_DEF` ```c #define FIO_UXXX_X64_DEF(name, bits) /* platform-specific */ #define FIO_UXXX_X32_DEF(name, bits) /* platform-specific */ #define FIO_UXXX_X16_DEF(name, bits) /* platform-specific */ #define FIO_UXXX_X8_DEF(name, bits) /* platform-specific */ ``` Core vector-member definition macros. On ARM they use NEON vector arrays. On GCC/Clang with `vector_size`, they use a single full-width vector. Otherwise they are plain C arrays. The fake x86 API itself uses `fio_u128` / `fio_u256` conversions instead of assuming a specific vector layout. #### `fio_fx86_m128i` ```c typedef __m128i fio_fx86_m128i; /* or */ typedef fio_u128 fio_fx86_m128i; ``` The canonical 128-bit integer SIMD type for this module. It is native `__m128i` when available, otherwise `fio_u128`. #### `fio_fx86_m256i` ```c typedef __m256i fio_fx86_m256i; /* or */ typedef fio_u256 fio_fx86_m256i; ``` The canonical 256-bit integer SIMD type for this module. It is native `__m256i` when available, otherwise `fio_u256`. #### `FIO___FX86_HAS_NATIVE_M128I`, `FIO___FX86_HAS_NATIVE_M256I`, `FIO___FX86_HAS_NATIVE_TYPES` ```c #define FIO___FX86_HAS_NATIVE_M128I 1 /* or 0 */ #define FIO___FX86_HAS_NATIVE_M256I 1 /* or 0 */ #define FIO___FX86_HAS_NATIVE_TYPES \ (FIO___FX86_HAS_NATIVE_M128I && FIO___FX86_HAS_NATIVE_M256I) ``` Internal feature flags used to choose native x86 types vs. `fio_uXXX` unions. ### API Functions #### `fio___fx86_to_u128` ```c FIO_IFUNC fio_u128 fio___fx86_to_u128(fio_fx86_m128i v); ``` Copies a `fio_fx86_m128i` into a `fio_u128` using `fio_memcpy16`. Use this when you need portable lane access and the type might be native `__m128i`. #### `fio___fx86_from_u128` ```c FIO_IFUNC fio_fx86_m128i fio___fx86_from_u128(fio_u128 u); ``` Copies a `fio_u128` into the canonical 128-bit fake x86 type. #### `fio___fx86_to_u256` ```c FIO_IFUNC fio_u256 fio___fx86_to_u256(fio_fx86_m256i v); ``` Copies a `fio_fx86_m256i` into a `fio_u256` using `fio_memcpy32`. #### `fio___fx86_from_u256` ```c FIO_IFUNC fio_fx86_m256i fio___fx86_from_u256(fio_u256 u); ``` Copies a `fio_u256` into the canonical 256-bit fake x86 type. #### `fio_fx86_loadu_si128`, `fio_fx86_load_si128`, `fio_fx86_storeu_si128` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_loadu_si128(const void *p); FIO_IFUNC fio_fx86_m128i fio_fx86_load_si128(const void *p); FIO_IFUNC void fio_fx86_storeu_si128(void *p, fio_fx86_m128i a); ``` SSE2-style 128-bit load/store helpers. The fallback copies 16 bytes. The aligned load name mirrors `_mm_load_si128`; the fallback does not check alignment. #### `fio_fx86_add_epi32` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_add_epi32(fio_fx86_m128i a, fio_fx86_m128i b); ``` Adds four unsigned 32-bit lanes with normal wraparound. #### `fio_fx86_xor_si128`, `fio_fx86_or_si128`, `fio_fx86_and_si128` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_xor_si128(fio_fx86_m128i a, fio_fx86_m128i b); FIO_IFUNC fio_fx86_m128i fio_fx86_or_si128(fio_fx86_m128i a, fio_fx86_m128i b); FIO_IFUNC fio_fx86_m128i fio_fx86_and_si128(fio_fx86_m128i a, fio_fx86_m128i b); ``` Bitwise operations over the whole 128-bit value. #### `fio_fx86_slli_si128`, `fio_fx86_srli_si128` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_slli_si128(fio_fx86_m128i a, int imm8); FIO_IFUNC fio_fx86_m128i fio_fx86_srli_si128(fio_fx86_m128i a, int imm8); ``` Shifts the whole 128-bit register by bytes, filling with zero. Values greater than or equal to 16 return zero in the fallback. #### `fio_fx86_slli_epi32`, `fio_fx86_srli_epi32`, `fio_fx86_slli_epi64`, `fio_fx86_srli_epi64` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_slli_epi32(fio_fx86_m128i a, int imm8); FIO_IFUNC fio_fx86_m128i fio_fx86_srli_epi32(fio_fx86_m128i a, int imm8); FIO_IFUNC fio_fx86_m128i fio_fx86_slli_epi64(fio_fx86_m128i a, int imm8); FIO_IFUNC fio_fx86_m128i fio_fx86_srli_epi64(fio_fx86_m128i a, int imm8); ``` Per-lane logical shifts for 32-bit and 64-bit lanes. Fallback shifts at or past the lane width produce zero. #### `fio_fx86_shuffle_epi32` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_shuffle_epi32(fio_fx86_m128i a, int imm8); ``` Selects each output 32-bit lane from `a` using two bits per lane from `imm8`. #### `fio_fx86_cmpeq_epi8`, `fio_fx86_movemask_epi8` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_cmpeq_epi8(fio_fx86_m128i a, fio_fx86_m128i b); FIO_IFUNC int fio_fx86_movemask_epi8(fio_fx86_m128i a); ``` Byte equality produces `0xFF` or `0x00` per byte. The movemask packs the high bit of each byte into a 16-bit integer result. #### `fio_fx86_set_epi32`, `fio_fx86_set_epi64x`, `fio_fx86_set_epi8`, `fio_fx86_set1_epi8`, `fio_fx86_setzero_si128`, `fio_fx86_cvtsi32_si128` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_set_epi32(int e3, int e2, int e1, int e0); FIO_IFUNC fio_fx86_m128i fio_fx86_set_epi64x(int64_t e1, int64_t e0); FIO_IFUNC fio_fx86_m128i fio_fx86_set_epi8(char e15, char e14, char e13, char e12, char e11, char e10, char e9, char e8, char e7, char e6, char e5, char e4, char e3, char e2, char e1, char e0); FIO_IFUNC fio_fx86_m128i fio_fx86_set1_epi8(char a); FIO_IFUNC fio_fx86_m128i fio_fx86_setzero_si128(void); FIO_IFUNC fio_fx86_m128i fio_fx86_cvtsi32_si128(int a); ``` SSE2 constructors. The `set` functions follow x86 argument order: high lanes first, low lanes last. #### `fio_fx86_shuffle_epi8`, `fio_fx86_alignr_epi8` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_shuffle_epi8(fio_fx86_m128i a, fio_fx86_m128i b); FIO_IFUNC fio_fx86_m128i fio_fx86_alignr_epi8(fio_fx86_m128i a, fio_fx86_m128i b, int imm8); ``` SSSE3 helpers. `shuffle_epi8` performs byte lookup, zeroing output bytes whose control byte has bit 7 set. `alignr_epi8` concatenates `b:a`, shifts right by `imm8` bytes, and returns 16 bytes. #### `fio_fx86_blend_epi16`, `fio_fx86_extract_epi32` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_blend_epi16(fio_fx86_m128i a, fio_fx86_m128i b, int imm8); FIO_IFUNC int fio_fx86_extract_epi32(fio_fx86_m128i a, int imm8); ``` SSE4.1 helpers. `blend_epi16` selects each 16-bit lane from `a` or `b` by `imm8` bits. `extract_epi32` returns lane `imm8 & 3`. #### `fio_fx86_prefetch` ```c FIO_IFUNC void fio_fx86_prefetch(const void *p, int hint); ``` Prefetches on native SSE2. It is a no-op in fake mode. #### `fio_fx86_aesenc_si128`, `fio_fx86_aesenclast_si128`, `fio_fx86_aeskeygenassist_si128` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_aesenc_si128(fio_fx86_m128i a, fio_fx86_m128i roundkey); FIO_IFUNC fio_fx86_m128i fio_fx86_aesenclast_si128(fio_fx86_m128i a, fio_fx86_m128i roundkey); FIO_IFUNC fio_fx86_m128i fio_fx86_aeskeygenassist_si128(fio_fx86_m128i a, int imm8); ``` AES-NI helpers. The fallback implements AES round logic with an S-box table. It is correct for portability tests, but the table lookups are cache-dependent; do not treat fake AES-NI as constant-time production crypto. #### `fio_fx86_clmulepi64_si128` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_clmulepi64_si128(fio_fx86_m128i a, fio_fx86_m128i b, int imm8); ``` PCLMULQDQ carry-less multiplication. `imm8 & 1` selects the 64-bit lane from `a`; `(imm8 >> 4) & 1` selects the lane from `b`. The result is a 128-bit GF(2) product. #### `fio_fx86_sha256rnds2_epu32`, `fio_fx86_sha256msg1_epu32`, `fio_fx86_sha256msg2_epu32` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_sha256rnds2_epu32(fio_fx86_m128i cdgh, fio_fx86_m128i abef, fio_fx86_m128i msg); FIO_IFUNC fio_fx86_m128i fio_fx86_sha256msg1_epu32(fio_fx86_m128i msg0, fio_fx86_m128i msg1); FIO_IFUNC fio_fx86_m128i fio_fx86_sha256msg2_epu32(fio_fx86_m128i msg4, fio_fx86_m128i msg1); ``` SHA-NI SHA-256 helpers. The round function expects `cdgh`, `abef`, and message-plus-constant layout matching Intel SHA extensions. #### `fio_fx86_sha1rnds4_epu32`, `fio_fx86_sha1nexte_epu32`, `fio_fx86_sha1msg1_epu32`, `fio_fx86_sha1msg2_epu32` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_sha1rnds4_epu32(fio_fx86_m128i abcd, fio_fx86_m128i e0, int func); FIO_IFUNC fio_fx86_m128i fio_fx86_sha1nexte_epu32(fio_fx86_m128i a, fio_fx86_m128i b); FIO_IFUNC fio_fx86_m128i fio_fx86_sha1msg1_epu32(fio_fx86_m128i msg0, fio_fx86_m128i msg1); FIO_IFUNC fio_fx86_m128i fio_fx86_sha1msg2_epu32(fio_fx86_m128i msg5, fio_fx86_m128i msg1); ``` SHA-NI SHA-1 helpers. `func & 3` selects Ch, Parity, Maj, or Parity for `sha1rnds4`. #### `fio_fx86_256_loadu_si256`, `fio_fx86_256_load_si256`, `fio_fx86_256_storeu_si256` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_loadu_si256(const void *p); FIO_IFUNC fio_fx86_m256i fio_fx86_256_load_si256(const void *p); FIO_IFUNC void fio_fx86_256_storeu_si256(void *p, fio_fx86_m256i a); ``` AVX2-style 256-bit load/store helpers. The fallback copies 32 bytes. #### `fio_fx86_256_add_epi16`, `fio_fx86_256_sub_epi16`, `fio_fx86_256_mullo_epi16`, `fio_fx86_256_add_epi32`, `fio_fx86_256_sub_epi32`, `fio_fx86_256_mullo_epi32`, `fio_fx86_256_add_epi64`, `fio_fx86_256_sub_epi64` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_add_epi16(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_sub_epi16(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_mullo_epi16(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_add_epi32(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_sub_epi32(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_mullo_epi32(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_add_epi64(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_sub_epi64(fio_fx86_m256i a, fio_fx86_m256i b); ``` AVX2 packed integer arithmetic. Operations wrap by lane width; `mullo` keeps the low half of each product. #### `fio_fx86_256_slli_epi32`, `fio_fx86_256_srai_epi32` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_slli_epi32(fio_fx86_m256i a, int imm8); FIO_IFUNC fio_fx86_m256i fio_fx86_256_srai_epi32(fio_fx86_m256i a, int imm8); ``` Per-lane 32-bit shifts. `srai` is arithmetic right shift; fallback values at or past 32 bits become the sign bit extended value. #### `fio_fx86_256_xor_si256`, `fio_fx86_256_and_si256`, `fio_fx86_256_or_si256` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_xor_si256(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_and_si256(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_or_si256(fio_fx86_m256i a, fio_fx86_m256i b); ``` Bitwise operations over the whole 256-bit value. #### `fio_fx86_256_cmpeq_epi8`, `fio_fx86_256_movemask_epi8` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_cmpeq_epi8(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC int fio_fx86_256_movemask_epi8(fio_fx86_m256i a); ``` AVX2 byte equality and 32-bit byte movemask. #### `fio_fx86_256_set1_epi8`, `fio_fx86_256_set1_epi16`, `fio_fx86_256_set1_epi32`, `fio_fx86_256_set1_epi64x`, `fio_fx86_256_set_epi64x`, `fio_fx86_256_set_epi8`, `fio_fx86_256_setzero_si256` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_set1_epi8(char a); FIO_IFUNC fio_fx86_m256i fio_fx86_256_set1_epi16(short a); FIO_IFUNC fio_fx86_m256i fio_fx86_256_set1_epi32(int a); FIO_IFUNC fio_fx86_m256i fio_fx86_256_set1_epi64x(long long a); FIO_IFUNC fio_fx86_m256i fio_fx86_256_set_epi64x(long long e3, long long e2, long long e1, long long e0); FIO_IFUNC fio_fx86_m256i fio_fx86_256_set_epi8(char e31, char e30, char e29, char e28, char e27, char e26, char e25, char e24, char e23, char e22, char e21, char e20, char e19, char e18, char e17, char e16, char e15, char e14, char e13, char e12, char e11, char e10, char e9, char e8, char e7, char e6, char e5, char e4, char e3, char e2, char e1, char e0); FIO_IFUNC fio_fx86_m256i fio_fx86_256_setzero_si256(void); ``` AVX2 constructors. Like Intel intrinsics, the explicit `set` functions receive high lanes first and low lanes last. #### `fio_fx86_256_shuffle_epi8`, `fio_fx86_256_alignr_epi8`, `fio_fx86_256_permute4x64_epi64` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_shuffle_epi8(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_alignr_epi8(fio_fx86_m256i a, fio_fx86_m256i b, int imm8); FIO_IFUNC fio_fx86_m256i fio_fx86_256_permute4x64_epi64(fio_fx86_m256i a, int imm8); ``` AVX2 shuffle/permute helpers. `shuffle_epi8` and `alignr_epi8` operate inside each 128-bit lane. `permute4x64_epi64` selects among the four 64-bit lanes across the full 256-bit value. #### `fio_fx86_256_packs_epi32`, `fio_fx86_256_cvtepi16_epi32` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_packs_epi32(fio_fx86_m256i a, fio_fx86_m256i b); FIO_IFUNC fio_fx86_m256i fio_fx86_256_cvtepi16_epi32(fio_fx86_m128i a); ``` AVX2 pack/convert helpers. `packs_epi32` saturates 32-bit signed lanes to 16-bit signed lanes, per 128-bit lane. `cvtepi16_epi32` sign-extends eight 16-bit values from the low 128 bits into eight 32-bit lanes. #### `fio_fx86_256_castsi256_si128`, `fio_fx86_256_extracti128_si256` ```c FIO_IFUNC fio_fx86_m128i fio_fx86_256_castsi256_si128(fio_fx86_m256i a); FIO_IFUNC fio_fx86_m128i fio_fx86_256_extracti128_si256(fio_fx86_m256i a, int imm8); ``` Extract 128-bit halves from a 256-bit value. `extracti128` uses `imm8 & 1`. #### `fio_fx86_256_sha512rnds2_epi64`, `fio_fx86_256_sha512msg1_epi64`, `fio_fx86_256_sha512msg2_epi64` ```c FIO_IFUNC fio_fx86_m256i fio_fx86_256_sha512rnds2_epi64(fio_fx86_m256i state1, fio_fx86_m256i state0, fio_fx86_m256i wk); FIO_IFUNC fio_fx86_m256i fio_fx86_256_sha512msg1_epi64(fio_fx86_m256i w0, fio_fx86_m256i w1); FIO_IFUNC fio_fx86_m256i fio_fx86_256_sha512msg2_epi64(fio_fx86_m256i w0, fio_fx86_m256i w1); ``` Intel SHA-512 extension helpers using four 64-bit lanes. ### Shadowed Intrinsic Families #### `_mm_*` With `FIO_FAKE_X86_SHADOW`, these SSE/SHA/AES names are mapped to the matching `fio_fx86_*` functions: ```c _mm_loadu_si128, _mm_load_si128, _mm_storeu_si128, _mm_add_epi32, _mm_xor_si128, _mm_or_si128, _mm_and_si128, _mm_slli_si128, _mm_srli_si128, _mm_slli_epi32, _mm_srli_epi32, _mm_slli_epi64, _mm_srli_epi64, _mm_shuffle_epi32, _mm_shuffle_epi8, _mm_alignr_epi8, _mm_cmpeq_epi8, _mm_movemask_epi8, _mm_set_epi32, _mm_set_epi64x, _mm_set_epi8, _mm_set1_epi8, _mm_setzero_si128, _mm_cvtsi32_si128, _mm_blend_epi16, _mm_extract_epi32, _mm_prefetch, _mm_aesenc_si128, _mm_aesenclast_si128, _mm_aeskeygenassist_si128, _mm_clmulepi64_si128, _mm_sha256rnds2_epu32, _mm_sha256msg1_epu32, _mm_sha256msg2_epu32, _mm_sha1rnds4_epu32, _mm_sha1nexte_epu32, _mm_sha1msg1_epu32, _mm_sha1msg2_epu32 ``` #### `_mm256_*` With `FIO_FAKE_X86_SHADOW`, these AVX2/SHA-512 names are mapped to the matching `fio_fx86_256_*` functions: ```c _mm256_loadu_si256, _mm256_load_si256, _mm256_storeu_si256, _mm256_add_epi16, _mm256_sub_epi16, _mm256_mullo_epi16, _mm256_add_epi32, _mm256_sub_epi32, _mm256_mullo_epi32, _mm256_add_epi64, _mm256_sub_epi64, _mm256_slli_epi32, _mm256_srai_epi32, _mm256_xor_si256, _mm256_and_si256, _mm256_or_si256, _mm256_cmpeq_epi8, _mm256_movemask_epi8, _mm256_set1_epi8, _mm256_set1_epi16, _mm256_set1_epi32, _mm256_set1_epi64x, _mm256_set_epi64x, _mm256_set_epi8, _mm256_setzero_si256, _mm256_shuffle_epi8, _mm256_alignr_epi8, _mm256_permute4x64_epi64, _mm256_packs_epi32, _mm256_cvtepi16_epi32, _mm256_castsi256_si128, _mm256_extracti128_si256, _mm256_sha512rnds2_epi64, _mm256_sha512msg1_epi64, _mm256_sha512msg2_epi64 ``` ### Examples #### 128-bit add ```c #define FIO_FAKE_X86 #include "fio-stl.h" #include int main(void) { uint32_t a_in[4] = {1, 2, 3, 4}; uint32_t b_in[4] = {10, 20, 30, 40}; uint32_t out[4]; fio_fx86_m128i a = fio_fx86_loadu_si128(a_in); fio_fx86_m128i b = fio_fx86_loadu_si128(b_in); fio_fx86_m128i c = fio_fx86_add_epi32(a, b); fio_fx86_storeu_si128(out, c); return (out[0] == 11 && out[3] == 44) ? 0 : 1; } ``` #### AVX2-style byte equality ```c #define FIO_FAKE_X86 #include "fio-stl.h" #include int main(void) { uint8_t a[32] = {0}; uint8_t b[32] = {0}; b[7] = 1; fio_fx86_m256i va = fio_fx86_256_loadu_si256(a); fio_fx86_m256i vb = fio_fx86_256_loadu_si256(b); fio_fx86_m256i eq = fio_fx86_256_cmpeq_epi8(va, vb); int mask = fio_fx86_256_movemask_epi8(eq); return ((mask & (1 << 7)) == 0) ? 0 : 1; } ``` #### Shadowing x86 names ```c #define FIO_FAKE_X86_SHADOW #include "fio-stl.h" #include int main(void) { uint32_t in[4] = {0x01020304, 0, 0, 0}; uint32_t out[4]; __m128i v = _mm_loadu_si128((const __m128i *)in); v = _mm_slli_epi32(v, 8); _mm_storeu_si128((__m128i *)out, v); return out[0] == 0x02030400 ? 0 : 1; } ``` ### Safety Notes #### Memory ownership The fake x86 API does not allocate or free memory. Load and store functions copy fixed-size byte ranges from caller-owned buffers. #### Thread safety Functions are value-based and use only caller-provided memory plus read-only tables. They are thread-safe when callers synchronize shared buffers as usual. #### Constant-time behavior The portable AES fallback uses lookup tables and is not constant-time. SHA and PCLMUL fallbacks are portable C implementations intended for correctness and test coverage, not a promise of native-intrinsic timing. #### Macro expansion `FIO_FOR_UNROLL` declares loop variables inside the macro and expands `action` many times. Shadow-mode `_mm*` names are macros that call the `fio_fx86*` functions; avoid side-effect-heavy arguments even when they appear to be evaluated once today. # Static Scratch Allocator A tiny, statically-backed allocator for short-lived temporary buffers. Use it when you need a quick buffer you can return to a caller that should not be freed by the caller, or as a safer alternative to `alloca` for data that only needs to stay valid until the next few function calls. The implementation is in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). ## `FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX` ```c #ifndef FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX #define FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX 256 #endif ``` Default slot count for library allocators that explicitly pass it as the fourth argument to `FIO_STATIC_ALLOC_DEF`. `FIO_STATIC_ALLOC_DEF` does not use this setting implicitly. For each allocator, choose its `max_concurrent_allocations` argument according to the number of short-lived slots that may be outstanding at once. Raise this setting only when an allocator uses it and its slots are being reused too early. ## `FIO_STATIC_ALLOC_DEF` ```c #define FIO_STATIC_ALLOC_DEF(name, type_T, units_per_allocation, max_concurrent_allocations) ``` Defines a static allocator named `name`. ```c static type_T *name(size_t count); ``` The generated function returns an aligned `type_T *` to a slot containing `units_per_allocation` elements. Its `count` argument advances the atomic round-robin position by that number of slots; it does not change the size of the returned slot. Passing `0` returns the first slot without advancing the position. - `name` — the allocator function name. - `type_T` — the element type of each slot. - `units_per_allocation` — number of `type_T` elements in each slot. - `max_concurrent_allocations` — number of round-robin slots available before reuse. The logical arena capacity reported by `name##_size()` is: ``` max_concurrent_allocations * units_per_allocation ``` ## Generated API The macro produces two functions: - **`type_T *name(size_t count)`** — returns a pointer to a slot selected from the static round-robin buffer. No matching free exists; the memory is static and reused automatically. - **`size_t name##_size(void)`** — returns the logical arena capacity in `type_T` units. There is no generated `name##_free`, `name##_reset`, or similar helper. The buffer is managed by an atomic position counter and wraps around after `max_concurrent_allocations` slots. ## Example ```c FIO_STATIC_ALLOC_DEF(numer2hex_allocator, char, 19, FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX); char *ntos16(uint16_t n) { char *buf = numer2hex_allocator(1); buf[0] = '0'; buf[1] = 'x'; fio_ltoa16u(buf + 2, n, 16); buf[18] = 0; return buf; } ``` The returned string is valid only until the allocator wraps around. A similar pattern is used by `fiobj_num2cstr` for temporary conversions. ## Thread Safety The allocator is only "good enough" thread-safe. The atomic position counter protects the round-robin index, but the safety window is bounded by the allocator's `max_concurrent_allocations` argument. Keep slots short-lived and do not hold a returned pointer across too many calls or threads. ## `FIO_STATIC_SAFE_ALLOC_DEF` ```c #define FIO_STATIC_SAFE_ALLOC_DEF(name, type_T, units_per_allocation, max_concurrent_allocations) ``` A contention-safe variant of `FIO_STATIC_ALLOC_DEF` for short-lived scratch slots with an explicit checkout lifecycle. Where the round-robin variant silently reuses a slot once more than `max_concurrent_allocations` are outstanding (corrupting the in-flight user), this allocator returns `NULL` when every slot is busy, letting callers fall back gracefully. Each slot carries a small metadata header holding its busy byte. The header is `min(sizeof(type_T), 16)` bytes: one element for small types (data naturally aligned for `type_T`), capped at 16 bytes for larger types (data 16-byte aligned). `type_T` alignment must be ≤ 16 (enforced at compile time). ### Generated API - **`type_T *name##_try(void)`** — checks out a slot and returns a pointer to its data block (right after the metadata header), or `NULL` when all slots are busy. - **`void name##_free(type_T *ptr)`** — releases a slot returned by `name##_try` (clears the busy byte). - **`size_t name##_size(void)`** — logical arena capacity in `type_T` units. ### Example ```c FIO_STATIC_SAFE_ALLOC_DEF(my_scratch, uint8_t, 4096, 16); size_t work(const void *in, size_t len) { uint8_t *slot = my_scratch_try(); if (!slot) return 0; /* contention: caller falls back (e.g. uncompressed) */ size_t r = do_work(slot, in, len); my_scratch_free(slot); return r; } ``` ### Thread Safety Slot checkout uses an atomic busy byte per slot with an atomic round-robin start hint; a checked-out slot is exclusively owned until `name##_free`. The busy-byte protocol is exact (no safety window) — the only failure mode is `NULL` under contention, which callers must handle. # String and Buffer Information Lightweight descriptors for byte ranges. They are defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) and are used throughout the library to pass around strings and buffers without taking ownership. ## Descriptor Types ### `fio_str_info_s` A string descriptor that carries length, buffer pointer, and capacity. ```c typedef struct fio_str_info_s { size_t len; /* string length, in bytes */ char *buf; /* pointer to the first byte, or NULL on error */ size_t capa; /* buffer capacity; 0 means read-only */ } fio_str_info_s; ``` `capa` tells callers how much room is available in the underlying buffer. When `capa` is `0`, the buffer is considered read-only and must not be written to through the descriptor. ### `fio_buf_info_s` A lighter buffer descriptor with no capacity field. ```c typedef struct fio_buf_info_s { size_t len; /* buffer length, in bytes */ char *buf; /* pointer to the first byte, may be NULL */ } fio_buf_info_s; ``` Use this when only a length/buffer pair is needed, such as for input data or temporary views. ## Construction Macros ### `FIO_STR_INFO0` An all-zero `fio_str_info_s`. ```c #define FIO_STR_INFO0 ((fio_str_info_s){0}) ``` ### `FIO_STR_INFO1(str)` Build an `fio_str_info_s` from a NUL-terminated C string. Computes the length with `FIO_STRLEN`. If `str` is `NULL`, the length is `0`. ```c #define FIO_STR_INFO1(str) \ ((fio_str_info_s){.len = ((str) ? FIO_STRLEN((str)) : 0), .buf = (str)}) ``` ### `FIO_STR_INFO2(str, length)` Build an `fio_str_info_s` from a buffer and a known length. ```c #define FIO_STR_INFO2(str, length) \ ((fio_str_info_s){.len = (length), .buf = (str)}) ``` ### `FIO_STR_INFO3(str, length, capacity)` Build a fully initialized `fio_str_info_s`. ```c #define FIO_STR_INFO3(str, length, capacity) \ ((fio_str_info_s){.len = (length), .buf = (str), .capa = (capacity)}) ``` ### `FIO_BUF_INFO0` An all-zero `fio_buf_info_s`. ```c #define FIO_BUF_INFO0 ((fio_buf_info_s){0}) ``` ### `FIO_BUF_INFO1(str)` Build an `fio_buf_info_s` from a NUL-terminated C string. If `str` is `NULL`, the length is `0`. ```c #define FIO_BUF_INFO1(str) \ ((fio_buf_info_s){.len = ((str) ? FIO_STRLEN((str)) : 0), .buf = (str)}) ``` ### `FIO_BUF_INFO2(str, length)` Build an `fio_buf_info_s` from a buffer and a known length. ```c #define FIO_BUF_INFO2(str, length) \ ((fio_buf_info_s){.len = (length), .buf = (str)}) ``` ## Equality ### `FIO_STR_INFO_IS_EQ(s1, s2)` Returns a true value if two `fio_str_info_s` objects have the same content. The comparison is a fast-path short-circuit: equal length and either both empty, the same pointer, or matching first byte and `FIO_MEMCMP` equal. ```c #define FIO_STR_INFO_IS_EQ(s1, s2) \ ((s1).len == (s2).len && \ (!(s1).len || (s1).buf == (s2).buf || \ ((s1).buf && (s2).buf && (s1).buf[0] == (s2).buf[0] && \ !FIO_MEMCMP((s1).buf, (s2).buf, (s1).len)))) ``` ### `FIO_BUF_INFO_IS_EQ(s1, s2)` Same comparison, routed to `FIO_STR_INFO_IS_EQ`. ```c #define FIO_BUF_INFO_IS_EQ(s1, s2) FIO_STR_INFO_IS_EQ((s1), (s2)) ``` ## Conversion ### `FIO_BUF2STR_INFO(buf_info)` Convert an `fio_buf_info_s` to an `fio_str_info_s`. The resulting descriptor has the same `len` and `buf`; `capa` is left as `0`. ```c #define FIO_BUF2STR_INFO(buf_info) \ ((fio_str_info_s){.len = (buf_info).len, .buf = (buf_info).buf}) ``` ### `FIO_STR2BUF_INFO(str_info)` Convert an `fio_str_info_s` to an `fio_buf_info_s`, dropping the capacity. ```c #define FIO_STR2BUF_INFO(str_info) \ ((fio_buf_info_s){.len = (str_info).len, .buf = (str_info).buf}) ``` ## Temporary Stack Variables ### `FIO_STR_INFO_TMP_VAR(name, capacity)` Creates a stack-allocated `fio_str_info_s` named `name` with `capacity` writable bytes. The macro allocates `capacity + 1` bytes so a trailing NUL guard is always present, and initializes `name.buf` and `name.capa`. ```c #define FIO_STR_INFO_TMP_VAR(name, capacity) \ char fio___stack_mem___##name[(capacity) + 1]; \ fio___stack_mem___##name[0] = 0; /* guard */ \ fio_str_info_s name = (fio_str_info_s) { \ .buf = fio___stack_mem___##name, .capa = (capacity) \ } ``` Use this when a function needs a writable working buffer that normally fits on the stack but may be reallocated by downstream helpers. ### `FIO_STR_INFO_TMP_IS_REALLOCATED(name)` Returns a true value if the temporary variable `name` no longer points to its original stack buffer, which indicates that a helper reallocated the memory onto the heap. ```c #define FIO_STR_INFO_TMP_IS_REALLOCATED(name) \ (fio___stack_mem___##name != name.buf) ``` If this returns true, the buffer must eventually be freed by whatever allocator the helper used (usually the facil.io allocator or the override set via `FIO_MEM_REALLOC`). # UTF-8 Helpers Single-character encoding, decoding, and validation helpers for UTF-8 text. Defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). These functions work on one code point at a time and do not check memory bounds. Use them carefully — make sure the buffer has enough space for writes and is padded or NUL-terminated for reads. ## Compile-Time Flag ### `FIO_UTF8_ALLOW_IF` Controls whether the UTF-8 helpers may use branchy (`if`) fast paths. - **`1`** (default): ASCII-biased fast paths. Best when most input is ASCII. - **`0`**: Branchless, constant-time-ish paths. Better when input distribution is unpredictable and branch mispredictions are a concern. Defined before including [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) to override the default. ## Length Helpers ### `fio_utf8_code_len` ```c unsigned fio_utf8_code_len(uint32_t u); ``` Returns the number of bytes required to encode code point `u` in UTF-8. Returns `1`–`4` for code points up to `0x1FFFFF`, and returns `0` only above that bound. This bound is above the valid Unicode range (`0x10FFFF`), so the function accepts code points in the range `0x110000`–`0x1FFFFF` as a consequence of its bitmask. `fio_utf8_write` will encode those code points as 4-byte sequences. ### `fio_utf8_char_len_unsafe` ```c unsigned fio_utf8_char_len_unsafe(uint8_t c); ``` Classifies a single leading byte without validating continuation bytes. Returns: - `1`–`4`: the expected total byte length of the UTF-8 character. - `8`: a continuation byte (middle of a multi-byte character). - `0`: an invalid leading byte. Use this only to recover length information after a successful `fio_utf8_char_len`, `fio_utf8_write`, or similar call where validity is already known. ### `fio_utf8_char_len` ```c unsigned fio_utf8_char_len(const void *str); ``` Returns the number of valid UTF-8 bytes used by the first character at `str`, validating continuation bytes. Returns `0` if `str` does not point to a valid UTF-8-encoded code point. Returns `1`–`4` for a valid character. ## Write Helper ### `fio_utf8_write` ```c unsigned fio_utf8_write(void *dest, uint32_t u); ``` Writes the UTF-8 encoding of code point `u` to `dest`. Returns the number of bytes written (`0`–`4`). `u` is treated as described for `fio_utf8_code_len`: code points up to `0x1FFFFF` are encoded (including the non-Unicode range `0x110000`–`0x1FFFFF`), and higher values result in `0` bytes written. The caller must ensure `dest` has room for up to 4 bytes. Typical use advances the destination pointer: ```c dest += fio_utf8_write(dest, u); ``` ## Read Helper ### `fio_utf8_read` ```c uint32_t fio_utf8_read(char **str); ``` Decodes the first UTF-8 character at `*str` and returns its code point. Advances `*str` by the number of bytes consumed. Returns `0` if the byte at `*str` is not a valid UTF-8 start byte or continuation sequence. ## Peek Helper ### `fio_utf8_peek` ```c uint32_t fio_utf8_peek(const char *str); ``` Decodes the first UTF-8 character at `str` and returns its code point. Does not modify `str`. Returns `0` for invalid input, same as `fio_utf8_read`. ## See Also - [`./000 core.md`](https://facil.io/0.8.x/facil-io-stl-core/) — overview of core helpers. - [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) — source of truth for the UTF-8 helpers. - [`./001 string info.md`](https://facil.io/0.8.x/string-and-buffer-information/) — string and buffer descriptors used with text helpers. # Vector Math Wide integer unions and lane-wise arithmetic helpers defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). These types are used for SIMD-style processing, big-integer arithmetic, hashing, and cryptography. ## Wide Union Types These unions expose the same data through arrays of different widths, plus platform-specific vector views when SIMD is available. They are sized and aligned as follows: | Type | Size | Alignment | |------|------|-----------| | `fio_u128` | 16 bytes | 16 | | `fio_u256` | 32 bytes | 32 | | `fio_u512` | 64 bytes | 64 | | `fio_u1024` | 128 bytes | 64 | | `fio_u2048` | 256 bytes | 64 | | `fio_u4096` | 512 bytes | 64 | All types include these overlapping members (counts vary by element width): ```c size_t uz[N]; ssize_t iz[N]; uint64_t u64[N]; int64_t i64[N]; uint32_t u32[N]; int32_t i32[N]; uint16_t u16[N]; int16_t i16[N]; uint8_t u8[N]; int8_t i8[N]; float f[N]; double d[N]; long double ld[total_bytes / sizeof(long double)]; ``` `total_bytes` is the type size from the table above, and each `N` is `total_bytes / sizeof(element)`. Larger types also contain smaller ones, for example `fio_u1024` provides `u128[8]`, `u256[4]`, and `u512[2]` in addition to the generic arrays. When the compiler supports GCC/Clang `vector_size` attributes or ARM NEON intrinsics, the unions also expose vector views named `x64`, `x32`, `x16`, and `x8`. The internal layout adapts to the platform: - On GCC/Clang, `x64[1]` is a single full-width vector. - On ARM NEON, `x64[]` is an array of 128-bit `uint64x2_t` vectors. - On platforms without vector support, `x64[]` falls back to a plain `uint64_t` array. ## Initialization Macros Initialize a wide union from a list of lane values. Unused lanes are zeroed by the compiler. ```c fio_u128_init8(0x01, 0x02, ...) fio_u128_init16(...) fio_u128_init32(...) fio_u128_init64(...) fio_u256_init8(...) fio_u256_init16(...) fio_u256_init32(...) fio_u256_init64(...) fio_u512_init8(...) fio_u512_init16(...) fio_u512_init32(...) fio_u512_init64(...) fio_u1024_init8(...) fio_u1024_init16(...) fio_u1024_init32(...) fio_u1024_init64(...) fio_u2048_init8(...) fio_u2048_init16(...) fio_u2048_init32(...) fio_u2048_init64(...) fio_u4096_init8(...) fio_u4096_init16(...) fio_u4096_init32(...) fio_u4096_init64(...) ``` ## Load, Store, and Byte Swap All loads and stores copy whole vectors to and from memory using the fixed-size `fio_memcpyNN` helpers. ```c fio_u128 fio_u128_load(const void *buf); void fio_u128_store(void *buf, const fio_u128 a); fio_u256 fio_u256_load(const void *buf); void fio_u256_store(void *buf, const fio_u256 a); fio_u512 fio_u512_load(const void *buf); void fio_u512_store(void *buf, const fio_u512 a); fio_u1024 fio_u1024_load(const void *buf); void fio_u1024_store(void *buf, const fio_u1024 a); fio_u2048 fio_u2048_load(const void *buf); void fio_u2048_store(void *buf, const fio_u2048 a); fio_u4096 fio_u4096_load(const void *buf); void fio_u4096_store(void *buf, const fio_u4096 a); ``` Endian-aware load helpers read each lane from the given byte order and convert it to local order. Store variants are not provided here; use the byte-swap helpers before `store` if needed. ```c fio_u128 fio_u128_load_le16(const void *buf); fio_u128 fio_u128_load_be16(const void *buf); fio_u128 fio_u128_load_le32(const void *buf); fio_u128 fio_u128_load_be32(const void *buf); fio_u128 fio_u128_load_le64(const void *buf); fio_u128 fio_u128_load_be64(const void *buf); fio_u256 fio_u256_load_le16(...); fio_u256 fio_u256_load_be16(...); fio_u256 fio_u256_load_le32(...); fio_u256 fio_u256_load_be32(...); fio_u256 fio_u256_load_le64(...); fio_u256 fio_u256_load_be64(...); fio_u512 fio_u512_load_le16(...); fio_u512 fio_u512_load_be16(...); fio_u512 fio_u512_load_le32(...); fio_u512 fio_u512_load_be32(...); fio_u512 fio_u512_load_le64(...); fio_u512 fio_u512_load_be64(...); fio_u1024 fio_u1024_load_le16(...); fio_u1024 fio_u1024_load_be16(...); fio_u1024 fio_u1024_load_le32(...); fio_u1024 fio_u1024_load_be32(...); fio_u1024 fio_u1024_load_le64(...); fio_u1024 fio_u1024_load_be64(...); fio_u2048 fio_u2048_load_le16(...); fio_u2048 fio_u2048_load_be16(...); fio_u2048 fio_u2048_load_le32(...); fio_u2048 fio_u2048_load_be32(...); fio_u2048 fio_u2048_load_le64(...); fio_u2048 fio_u2048_load_be64(...); fio_u4096 fio_u4096_load_le16(...); fio_u4096 fio_u4096_load_be16(...); fio_u4096 fio_u4096_load_le32(...); fio_u4096 fio_u4096_load_be32(...); fio_u4096 fio_u4096_load_le64(...); fio_u4096 fio_u4096_load_be64(...); ``` Byte-swap every lane of a vector, returning the result by value: ```c fio_u128 fio_u128_bswap16(fio_u128 a); fio_u128 fio_u128_bswap32(fio_u128 a); fio_u128 fio_u128_bswap64(fio_u128 a); fio_u256 fio_u256_bswap16(...); fio_u256 fio_u256_bswap32(...); fio_u256 fio_u256_bswap64(...); fio_u512 fio_u512_bswap16(...); fio_u512 fio_u512_bswap32(...); fio_u512 fio_u512_bswap64(...); fio_u1024 fio_u1024_bswap16(...); fio_u1024 fio_u1024_bswap32(...); fio_u1024 fio_u1024_bswap64(...); fio_u2048 fio_u2048_bswap16(...); fio_u2048 fio_u2048_bswap32(...); fio_u2048 fio_u2048_bswap64(...); fio_u4096 fio_u4096_bswap16(...); fio_u4096 fio_u4096_bswap32(...); fio_u4096 fio_u4096_bswap64(...); ``` ## Lane-Wise Arithmetic The arithmetic functions write lane-wise results into `*target`. They work on any `fio_uXXX` type at 8, 16, 32, and 64-bit lane widths. The compiler vectorizes the loops when SIMD is available. ```c void fio_u128_add8(fio_u128 *target, const fio_u128 *a, const fio_u128 *b); void fio_u128_add16(fio_u128 *target, const fio_u128 *a, const fio_u128 *b); void fio_u128_add32(fio_u128 *target, const fio_u128 *a, const fio_u128 *b); void fio_u128_add64(fio_u128 *target, const fio_u128 *a, const fio_u128 *b); void fio_u128_sub8(...); void fio_u128_sub16(...); void fio_u128_sub32(...); void fio_u128_sub64(...); void fio_u128_mul8(...); void fio_u128_mul16(...); void fio_u128_mul32(...); void fio_u128_mul64(...); void fio_u128_and8(...); void fio_u128_and16(...); void fio_u128_and32(...); void fio_u128_and64(...); void fio_u128_or8(...); void fio_u128_or16(...); void fio_u128_or32(...); void fio_u128_or64(...); void fio_u128_xor8(...); void fio_u128_xor16(...); void fio_u128_xor32(...); void fio_u128_xor64(...); ``` These are generated for all wide types: `fio_u256`, `fio_u512`, `fio_u1024`, `fio_u2048`, and `fio_u4096`. Constant-operand variants apply the same operation with a scalar in place of `*b`: ```c void fio_u128_cadd8(fio_u128 *target, const fio_u128 *a, uint8_t b); void fio_u128_cadd16(fio_u128 *target, const fio_u128 *a, uint16_t b); void fio_u128_cadd32(fio_u128 *target, const fio_u128 *a, uint32_t b); void fio_u128_cadd64(fio_u128 *target, const fio_u128 *a, uint64_t b); void fio_u128_csub8(...); void fio_u128_csub16(...); void fio_u128_csub32(...); void fio_u128_csub64(...); void fio_u128_cmul8(...); void fio_u128_cmul16(...); void fio_u128_cmul32(...); void fio_u128_cmul64(...); void fio_u128_cand8(...); void fio_u128_cand16(...); void fio_u128_cand32(...); void fio_u128_cand64(...); void fio_u128_cor8(...); void fio_u128_cor16(...); void fio_u128_cor32(...); void fio_u128_cor64(...); void fio_u128_cxor8(...); void fio_u128_cxor16(...); void fio_u128_cxor32(...); void fio_u128_cxor64(...); ``` Reduction functions fold all lanes of the given width into a single scalar: ```c uint8_t fio_u128_reduce_add8(const fio_u128 *a); uint16_t fio_u128_reduce_add16(const fio_u128 *a); uint32_t fio_u128_reduce_add32(const fio_u128 *a); uint64_t fio_u128_reduce_add64(const fio_u128 *a); uint8_t fio_u128_reduce_sub8(...); uint16_t fio_u128_reduce_sub16(...); uint32_t fio_u128_reduce_sub32(...); uint64_t fio_u128_reduce_sub64(...); uint8_t fio_u128_reduce_mul8(...); uint16_t fio_u128_reduce_mul16(...); uint32_t fio_u128_reduce_mul32(...); uint64_t fio_u128_reduce_mul64(...); uint8_t fio_u128_reduce_and8(...); uint16_t fio_u128_reduce_and16(...); uint32_t fio_u128_reduce_and32(...); uint64_t fio_u128_reduce_and64(...); uint8_t fio_u128_reduce_or8(...); uint16_t fio_u128_reduce_or16(...); uint32_t fio_u128_reduce_or32(...); uint64_t fio_u128_reduce_or64(...); uint8_t fio_u128_reduce_xor8(...); uint16_t fio_u128_reduce_xor16(...); uint32_t fio_u128_reduce_xor32(...); uint64_t fio_u128_reduce_xor64(...); ``` Whole-vector variants that operate on 64-bit lanes without a width suffix are also provided for bitwise operations: ```c void fio_u128_and(fio_u128 *target, const fio_u128 *a, const fio_u128 *b); void fio_u128_or (fio_u128 *target, const fio_u128 *a, const fio_u128 *b); void fio_u128_xor(fio_u128 *target, const fio_u128 *a, const fio_u128 *b); ``` ## Rotations Lane-wise right and left rotations. Variable rotations read the per-lane shift from an array; constant rotations use a single `uint8_t` for all lanes. ```c void fio_u128_rrot8 (fio_u128 *target, const fio_u128 *a, const uint8_t rotations[16]); void fio_u128_rrot16(fio_u128 *target, const fio_u128 *a, const uint8_t rotations[8]); void fio_u128_rrot32(fio_u128 *target, const fio_u128 *a, const uint8_t rotations[4]); void fio_u128_rrot64(fio_u128 *target, const fio_u128 *a, const uint8_t rotations[2]); void fio_u128_lrot8 (fio_u128 *target, const fio_u128 *a, const uint8_t rotations[16]); void fio_u128_lrot16(fio_u128 *target, const fio_u128 *a, const uint8_t rotations[8]); void fio_u128_lrot32(fio_u128 *target, const fio_u128 *a, const uint8_t rotations[4]); void fio_u128_lrot64(fio_u128 *target, const fio_u128 *a, const uint8_t rotations[2]); void fio_u128_crrot8 (fio_u128 *target, const fio_u128 *a, const uint8_t bts); void fio_u128_crrot16(fio_u128 *target, const fio_u128 *a, const uint8_t bts); void fio_u128_crrot32(fio_u128 *target, const fio_u128 *a, const uint8_t bts); void fio_u128_crrot64(fio_u128 *target, const fio_u128 *a, const uint8_t bts); void fio_u128_clrot8 (fio_u128 *target, const fio_u128 *a, const uint8_t bts); void fio_u128_clrot16(fio_u128 *target, const fio_u128 *a, const uint8_t bts); void fio_u128_clrot32(fio_u128 *target, const fio_u128 *a, const uint8_t bts); void fio_u128_clrot64(fio_u128 *target, const fio_u128 *a, const uint8_t bts); ``` All rotation families exist for `fio_u256`, `fio_u512`, `fio_u1024`, `fio_u2048`, and `fio_u4096` with appropriate array sizes. ## Ternary Lane-Wise Operations These compute common three-input bit functions across each lane. ```c // target[i] = z[i] ^ (x[i] & (y[i] ^ z[i])) void fio_u128_mux32(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); void fio_u128_mux64(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); // target[i] = (x[i] & y[i]) | (z[i] & (x[i] | y[i])) void fio_u128_maj32(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); void fio_u128_maj64(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); // target[i] = x[i] ^ y[i] ^ z[i] void fio_u128_3xor32(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); void fio_u128_3xor64(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); ``` Whole-vector 64-bit variants without a width suffix are also provided: ```c void fio_u128_mux(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); void fio_u128_maj(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); void fio_u128_3xor(fio_u128 *target, const fio_u128 *x, const fio_u128 *y, const fio_u128 *z); ``` All ternary functions exist for the other wide types: `fio_u256`, `fio_u512`, `fio_u1024`, `fio_u2048`, and `fio_u4096`. ## Whole-Vector Value Operations A small set of convenience functions return results by value instead of writing through a pointer. They operate on 64-bit lanes and are designed to let the compiler use SIMD directly. ```c fio_u128 fio_u128_xorv(fio_u128 a, fio_u128 b); fio_u128 fio_u128_andv(fio_u128 a, fio_u128 b); fio_u128 fio_u128_orv (fio_u128 a, fio_u128 b); fio_u128 fio_u128_addv64(fio_u128 a, fio_u128 b); fio_u256 fio_u256_xorv(...); fio_u256 fio_u256_andv(...); fio_u256 fio_u256_orv(...); fio_u256 fio_u256_addv64(...); fio_u512 fio_u512_xorv(...); fio_u512 fio_u512_andv(...); fio_u512 fio_u512_orv(...); fio_u512 fio_u512_addv64(...); fio_u1024 fio_u1024_xorv(...); fio_u1024 fio_u1024_andv(...); fio_u1024 fio_u1024_orv(...); fio_u1024 fio_u1024_addv64(...); fio_u2048 fio_u2048_xorv(...); fio_u2048 fio_u2048_andv(...); fio_u2048 fio_u2048_orv(...); fio_u2048 fio_u2048_addv64(...); fio_u4096 fio_u4096_xorv(...); fio_u4096 fio_u4096_andv(...); fio_u4096 fio_u4096_orv(...); fio_u4096 fio_u4096_addv64(...); ``` ## Constant-Time Helpers ```c bool fio_u128_is_eq(const fio_u128 *a, const fio_u128 *b); ``` Returns `true` if every byte of `*a` equals the corresponding byte of `*b`, otherwise `false`. The comparison is branchless over the vector body. ```c void fio_u128_inv(fio_u128 *target, const fio_u128 *a); ``` Bitwise NOT: `target[i] = ~a[i]` on 64-bit lanes. ```c void fio_u128_ct_swap_if(bool cond, fio_u128 *a, fio_u128 *b); ``` Swaps `*a` and `*b` when `cond` is true, without branching on `cond`. Uses a mask derived from `(uint64_t)0 - cond`. All three helpers exist for `fio_u256`, `fio_u512`, `fio_u1024`, `fio_u2048`, and `fio_u4096`. ## Multi-Precision Math These functions treat the wide unions as unsigned big integers made of little-endian `u64` words. ```c uint64_t fio_u128_add(fio_u128 *result, const fio_u128 *a, const fio_u128 *b); uint64_t fio_u128_sub(fio_u128 *result, const fio_u128 *a, const fio_u128 *b); int fio_u128_cmp(const fio_u128 *a, const fio_u128 *b); uint64_t fio_u256_add(...); uint64_t fio_u256_sub(...); int fio_u256_cmp(...); uint64_t fio_u512_add(...); uint64_t fio_u512_sub(...); int fio_u512_cmp(...); uint64_t fio_u1024_add(...); uint64_t fio_u1024_sub(...); int fio_u1024_cmp(...); uint64_t fio_u2048_add(...); uint64_t fio_u2048_sub(...); int fio_u2048_cmp(...); uint64_t fio_u4096_add(...); uint64_t fio_u4096_sub(...); int fio_u4096_cmp(...); ``` - `fio_uXXX_add` returns the carry bit (`1` or `0`). - `fio_uXXX_sub` returns the borrow bit (`1` or `0`). - `fio_uXXX_cmp` returns `-1` if `a < b`, `0` if equal, and `1` if `a > b`. Multiplication doubles the width of the result: ```c void fio_u128_mul(fio_u256 *result, const fio_u128 *a, const fio_u128 *b); void fio_u256_mul(fio_u512 *result, const fio_u256 *a, const fio_u256 *b); void fio_u512_mul(fio_u1024 *result, const fio_u512 *a, const fio_u512 *b); void fio_u1024_mul(fio_u2048 *result, const fio_u1024 *a, const fio_u1024 *b); void fio_u2048_mul(fio_u4096 *result, const fio_u2048 *a, const fio_u2048 *b); ``` Montgomery multiplication is provided for RSA-like modular arithmetic: ```c void fio_u128_montgomery_mul(fio_u128 *result, const fio_u128 *a, const fio_u128 *b, const fio_u128 *N, const fio_u128 *N_dash); void fio_u256_montgomery_mul(fio_u256 *result, const fio_u256 *a, const fio_u256 *b, const fio_u256 *N, const fio_u256 *N_dash); void fio_u512_montgomery_mul(fio_u512 *result, const fio_u512 *a, const fio_u512 *b, const fio_u512 *N, const fio_u512 *N_dash); void fio_u1024_montgomery_mul(fio_u1024 *result, const fio_u1024 *a, const fio_u1024 *b, const fio_u1024 *N, const fio_u1024 *N_dash); void fio_u2048_montgomery_mul(fio_u2048 *result, const fio_u2048 *a, const fio_u2048 *b, const fio_u2048 *N, const fio_u2048 *N_dash); ``` The multiplication uses `fio_math_mul` from [`./002 math.h`](https://facil.io/0.8.x/api/002-math/). ## Shuffle and Reduce on Native Arrays A separate family of helpers works on plain C arrays of fixed size. They are useful when the data is not wrapped in a `fio_uXXX` union. ### Shuffle ```c void fio_u8x4_reshuffle (uint8_t *v, uint8_t indx[4]); void fio_u8x8_reshuffle (uint8_t *v, uint8_t indx[8]); void fio_u8x16_reshuffle (uint8_t *v, uint8_t indx[16]); void fio_u8x32_reshuffle (uint8_t *v, uint8_t indx[32]); void fio_u8x64_reshuffle (uint8_t *v, uint8_t indx[64]); void fio_u8x128_reshuffle(uint8_t *v, uint8_t indx[128]); void fio_u8x256_reshuffle(uint8_t *v, uint8_t indx[256]); void fio_u16x2_reshuffle (uint16_t *v, uint8_t indx[2]); void fio_u16x4_reshuffle (uint16_t *v, uint8_t indx[4]); void fio_u16x8_reshuffle (uint16_t *v, uint8_t indx[8]); void fio_u16x16_reshuffle(uint16_t *v, uint8_t indx[16]); void fio_u16x32_reshuffle(uint16_t *v, uint8_t indx[32]); void fio_u16x64_reshuffle(uint16_t *v, uint8_t indx[64]); void fio_u16x128_reshuffle(uint16_t *v, uint8_t indx[128]); void fio_u32x2_reshuffle (uint32_t *v, uint8_t indx[2]); void fio_u32x4_reshuffle (uint32_t *v, uint8_t indx[4]); void fio_u32x8_reshuffle (uint32_t *v, uint8_t indx[8]); void fio_u32x16_reshuffle(uint32_t *v, uint8_t indx[16]); void fio_u32x32_reshuffle(uint32_t *v, uint8_t indx[32]); void fio_u32x64_reshuffle(uint32_t *v, uint8_t indx[64]); void fio_u64x2_reshuffle (uint64_t *v, uint8_t indx[2]); void fio_u64x4_reshuffle (uint64_t *v, uint8_t indx[4]); void fio_u64x8_reshuffle (uint64_t *v, uint8_t indx[8]); void fio_u64x16_reshuffle(uint64_t *v, uint8_t indx[16]); void fio_u64x32_reshuffle(uint64_t *v, uint8_t indx[32]); void fio_floatx2_reshuffle (float *v, uint8_t indx[2]); void fio_floatx4_reshuffle (float *v, uint8_t indx[4]); void fio_floatx8_reshuffle (float *v, uint8_t indx[8]); void fio_floatx16_reshuffle(float *v, uint8_t indx[16]); void fio_floatx32_reshuffle(float *v, uint8_t indx[32]); void fio_floatx64_reshuffle(float *v, uint8_t indx[64]); void fio_dblx2_reshuffle (double *v, uint8_t indx[2]); void fio_dblx4_reshuffle (double *v, uint8_t indx[4]); void fio_dblx8_reshuffle (double *v, uint8_t indx[8]); void fio_dblx16_reshuffle (double *v, uint8_t indx[16]); void fio_dblx32_reshuffle (double *v, uint8_t indx[32]); ``` Indices are masked with `len - 1`, so out-of-range indices wrap safely. Variadic macro wrappers let you pass the index list inline: ```c fio_u8x4_reshuffle(v, 3, 2, 1, 0); fio_u32x4_reshuffle(v, 0, 2, 1, 3); ``` ### Reduce Integer reductions provide add, subtract, multiply, bitwise and/or/xor, and min/max: ```c uint8_t fio_u8x4_reduce_add (uint8_t *v); uint8_t fio_u8x4_reduce_sub (uint8_t *v); uint8_t fio_u8x4_reduce_mul (uint8_t *v); uint8_t fio_u8x4_reduce_and (uint8_t *v); uint8_t fio_u8x4_reduce_or (uint8_t *v); uint8_t fio_u8x4_reduce_xor (uint8_t *v); uint8_t fio_u8x4_reduce_max (uint8_t *v); uint8_t fio_u8x4_reduce_min (uint8_t *v); // Same pattern for u8 x 8, 16, 32, 64, 128, 256 // Same pattern for u16 x 2, 4, 8, 16, 32, 64, 128 // Same pattern for u32 x 2, 4, 8, 16, 32, 64 // Same pattern for u64 x 2, 4, 8, 16, 32 ``` Floating-point reductions provide add, multiply, and min/max (no bitwise operations): ```c float fio_floatx2_reduce_add(float *v); float fio_floatx2_reduce_mul(float *v); float fio_floatx2_reduce_max(float *v); float fio_floatx2_reduce_min(float *v); // Same pattern for float x 4, 8, 16, 32, 64 // Same pattern for dbl x 2, 4, 8, 16, 32 ``` ### Lane-wise operations on native arrays Each integer reduction family also defines matching in-place binary operators on the arrays themselves: ```c void fio_u8x4_add (uint8_t *dest, uint8_t *a, uint8_t *b); void fio_u8x4_sub (uint8_t *dest, uint8_t *a, uint8_t *b); void fio_u8x4_mul (uint8_t *dest, uint8_t *a, uint8_t *b); void fio_u8x4_and (uint8_t *dest, uint8_t *a, uint8_t *b); void fio_u8x4_or (uint8_t *dest, uint8_t *a, uint8_t *b); void fio_u8x4_xor (uint8_t *dest, uint8_t *a, uint8_t *b); // Same pattern for all u8/u16/u32/u64 widths and lengths. ``` Floating-point array families define add and multiply only: ```c void fio_floatx2_add(float *dest, float *a, float *b); void fio_floatx2_mul(float *dest, float *a, float *b); // Same pattern for float x 4, 8, 16, 32, 64 and dbl x 2, 4, 8, 16, 32. ``` ## See Also - [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) — source of truth for all vector definitions. - [`./000 core.md`](https://facil.io/0.8.x/facil-io-stl-core/) — core documentation index. - `001 fx86.h` — fake/portable x86 intrinsics built on these types. - [`./002 math.h`](https://facil.io/0.8.x/api/002-math/) — multi-precision `fio_math_mul` and related helpers. # Version and Settings The knobs that control how facil.io identifies itself and behaves at compile time. For core types and everyday helpers, see [`./000 core.md`](https://facil.io/0.8.x/facil-io-stl-core/). This page covers version macros and core behavioral defaults; for per-inclusion hooks such as allocator routing and pointer tagging, see [`./001 header.h`](https://facil.io/0.8.x/api/001-header/). ## Version Macros facil.io follows [semantic versioning](https://semver.org). These macros are defined in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) and can be used to detect the library version at compile time. - **`FIO_VERSION_MAJOR`** — Major version. API/ABI breaking changes bump this. Default: `0` - **`FIO_VERSION_MINOR`** — Minor version. Significant features or deprecations. May break ABI. Default: `8` - **`FIO_VERSION_PATCH`** — Patch version. Bug fixes and small additions. Default: `0` - **`FIO_VERSION_BUILD`** — Optional build metadata, such as `"rc.02"`. Default: `"rc.02"` - **`FIO_VERSION_STRING`** — Full version as a string literal, built from the macros above. The guard is `#ifdef FIO_VERSION_BUILD`, so the build metadata is included whenever `FIO_VERSION_BUILD` is defined, including when defined as an empty string: `"0.8.0-rc.02"`. If `FIO_VERSION_BUILD` is undefined, the string reads `"0.8.0"`. Because [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) unconditionally `#define`s `FIO_VERSION_BUILD "rc.02"` before the `#ifdef` guard, omitting the suffix requires `#undef FIO_VERSION_BUILD` before inclusion, not merely leaving it undefined. Override these only when you are vendoring a fork or need to report a custom build string. ## Compile-Time Behavioral Defaults Most defaults are guarded with `#ifndef`; define them before including the header to override the default. `FIO_NO_LOG` and `DEBUG` are presence flags rather than `#ifndef` defaults. ### Memory and Safety - **`FIO_LEAK_COUNTER`** — Enables memory-leak counting. When enabled, the leak counter tracks allocations made through facil.io allocators and prints a summary at exit. Default: `1` (enabled). Set to `0` to disable counting and the exit report. Note: this conflicts with `FIO_NO_LOG`, because the leak report uses the logging layer. - **`FIO_LEAK_COUNTER_SKIP_EXIT`** — Skip the automatic leak report at exit when leak counting is enabled. Default: `0` (report at exit). - **`FIO_LEAK_COUNTER_DEF(name)`** — Declares a named leak counter. - **`FIO_LEAK_COUNTER_ON_ALLOC(name)`** — Records an allocation against the named counter. - **`FIO_LEAK_COUNTER_ON_FREE(name)`** — Records a free against the named counter. - **`FIO_LEAK_COUNTER_COUNT(name)`** — Returns the current value of the named counter. - **`FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT`** — When non-zero, facil.io allocators zero-initialize returned memory by default. Default: `1` (secure by default). Set to `0` if you want raw allocations and prefer to do your own initialization. - **`FIO_MEM_PAGE_SIZE_LOG`** — Log₂ of the OS memory page size. The memory allocator uses this to round allocations and map memory efficiently. Default: `12` (4096-byte pages). Change it if you are targeting a system with a different page size. - **`FIO_STATIC_ALLOC_SAFE_CONCURRENCY_MAX`** — Default slot count for selected internal `FIO_STATIC_ALLOC_DEF` invocations (default: `256`). The macro does not use this setting implicitly; pass it explicitly as the fourth argument where appropriate. ### Concurrency - **`FIO_USE_THREAD_MUTEX`** — Choose the locking primitive used by thread-safe modules. Default: `0` (use facil.io spinlocks). Set to `1` to use OS native mutexes (`pthread_mutex_t` on POSIX) instead. If you need this decision to apply to only a single `#include`, use `FIO_USE_THREAD_MUTEX_TMP` in [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) instead. ### Performance and Alignment - **`FIO_UNALIGNED_ACCESS`** — Allows facil.io to attempt unaligned memory access on CPUs that support it. When enabled, the library may use faster paths in helpers such as `fio_buf2uXX`. Default: `1` (attempt detection). Set to `0` on strict-alignment architectures or if you want to forbid unaligned access entirely. The actual capability is reflected by `FIO_UNALIGNED_MEMORY_ACCESS_ENABLED`, which is derived at compile time. - **`FIO_LIMIT_INTRINSIC_BUFFER`** — Limits register pressure in some pseudo-intrinsic loops by processing smaller batches and looping more. Default: `1`. Set to `0` if you prefer larger unrolled buffers and do not mind the extra register use. ### Maps - **`FIO_MAP_WARNING_BITSIZE`** — Bit size at which hash-map allocations trigger an internal warning threshold in the `imap`/`map` templates. Default: `24`. Raise or lower it to tune when the library starts worrying about map size during growth. ### Logging - **`FIO_NO_LOG`** — Define this to remove all logging output. Cannot be combined with `FIO_LEAK_COUNTER`; the two are mutually exclusive. - **`FIO_LOG2STDERR`** — Low-level sink used by the logging macros. Default: no-op. Override to print to `stderr` or route messages to a custom destination. - **`FIO_LOG_LENGTH_LIMIT`** — Approximate cap on a single formatted log message, limiting stack memory use. Default: `1024`. - **`DEBUG`** — Define this to enable debug-only log macros (`FIO_LOG_D*`) and active `FIO_ASSERT_DEBUG` checks. Also disables SIMD/Neon intrinsics in [`./000 core.h`](https://facil.io/0.8.x/api/000-core/). ### Memory allocator routing These macros are defined in [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) and are re-evaluated on every inclusion cycle. Define them before the first fio-stl include to replace memory routing globally. - **`FIO_MEM_REALLOC(ptr, old_size, new_size, copy_len)`** — Reallocate a block, copying at least `copy_len` bytes if a move is required. Defaults to the system `realloc` unless the facil.io allocator is available, in which case it routes to `fio_realloc2`. Override to plug in a custom allocator. - **`FIO_MEM_FREE(ptr, size)`** — Free a block. Defaults to the system `free` or `fio_free`. Override alongside `FIO_MEM_REALLOC`. - **`FIO_MEMORY_DISABLE`** — Define to disable all custom facil.io allocators and force system `malloc`/`free` for temporary allocations. - **`FIO_MALLOC_TMP_USE_SYSTEM`** — Force the recursive allocator variant to use the system `realloc`/`free`. Useful when a custom `FIO_MEM_REALLOC` needs an allocation path that will not recurse back into facil.io. - **`FIO_MEM_REALLOC_` / `FIO_MEM_FREE_`** — Recursive-safe copies of `FIO_MEM_REALLOC`/`FIO_MEM_FREE`. Override these when the custom allocator itself needs to allocate or free memory without re-entering the override. - **`FIO_MEM_REALLOC_ALIGNED(ptr, old_size, new_size, copy_len, alignment)`** — Reallocate with a minimum pointer alignment, **assigning** the result to `ptr` (save a copy first if failure must be recoverable). `alignment` of `0` selects the allocator default; non power-of-2 values are rounded down to a power of 2; effective alignment never drops below the current alignment of `ptr` nor below the allocator default. Routes to `fio_realloc_aligned` when the facil.io allocator is available, or to the system backends (`_aligned_realloc` on Windows, `posix_memalign`-based emulation on POSIX). - **`FIO_MEM_FREE_ALIGNED(ptr, size)`** — Free a block obtained through `FIO_MEM_REALLOC_ALIGNED`. With the facil.io allocator this is simply `fio_free` (which accepts interior pointers); with the system backends it routes to `_aligned_free` on Windows or `free` on POSIX. Custom allocators that stash alignment bookkeeping near the returned pointer should override this pair together. - **`FIO_MEM_ALLOC_SIZE(size)`** — The usable bytes the allocator reserves for a `size` request (`fio_alloc_size`, or the identity function with the system allocator). - **`FIO_MEM_REALLOC_ALIGNED_` / `FIO_MEM_FREE_ALIGNED_`** — Recursive-safe copies of the aligned pair, mirroring `FIO_MEM_REALLOC_` / `FIO_MEM_FREE_`. ### Pointer tagging These macros are defined in [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) and let included types store a small marker in the unused low bits of aligned pointers. - **`FIO_PTR_TAG(p)`** — Apply the tag to a pointer. Default is the identity function. - **`FIO_PTR_UNTAG(p)`** — Remove the tag from a pointer. Default is the identity function. - **`FIO_PTR_TAG_TYPE`** — If defined, functions returning a type's pointer return this type instead. - **`FIO_PTR_TAG_VALIDATE(ptr)`** — Returns a true value if the tagged pointer is valid. Default checks for non-`NULL`. - **`FIO_PTR_TAG_VALID_OR_RETURN(tagged_ptr, value)`** — Validates the tagged pointer and returns `value` if it fails validation. - **`FIO_PTR_TAG_VALID_OR_RETURN_VOID(tagged_ptr)`** — Validates the tagged pointer and returns `void` if it fails validation. - **`FIO_PTR_TAG_VALID_OR_GOTO(tagged_ptr, label)`** — Validates the tagged pointer and jumps to `label` if it fails validation. - **`FIO_PTR_TAG_GET_UNTAGGED(untagged_type, tagged_ptr)`** — Cast the untagged pointer to `untagged_type *`. Pointer tagging is used by dynamic-type wrappers (for example, FIOBJ) to distinguish object kinds without a separate type field. ## See Also - [`./000 core.md`](https://facil.io/0.8.x/facil-io-stl-core/) — documentation for core types and helpers. - [`./000 core.h`](https://facil.io/0.8.x/api/000-core/) — the source of truth for version macros and the defaults above. - [`./001 header.h`](https://facil.io/0.8.x/api/001-header/) — per-inclusion overrides such as `FIO_USE_THREAD_MUTEX_TMP` and allocator routing macros. # String / Number Conversion ```c #define FIO_ATOL #include "fio-stl.h" ``` String-to-number and number-to-string helpers. These are the grunts that parse integers, floats, hex, binary, and arbitrary bases, then turn them back into text. Fast, greedy, and mostly guard-less — give them a valid buffer and a terminating character. **Note:** functions that write to a buffer also write a NUL terminator. `fio_atol*` functions assume the buffer ends with an invalid character (such as NUL) and that allocations are aligned enough for multi-byte reads. **Guard-byte contract:** parsing may read up to - and including - the first non-numeric byte following a number. Callers MUST keep the buffer readable through that byte: pass a NUL-terminated string or append a non-numeric guard byte. `fio_bstr` strings always satisfy this contract. Exact-size buffers with no readable byte past the number (e.g., a 1-byte allocation holding `"1"`) violate it and will trigger AddressSanitizer (sanitize with `FIO_MEMORY_DISABLE` for visibility). ### Configuration Macros #### `FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER` ```c #define FIO_ATOL_ALLOW_UNDERSCORE_DIVIDER 1 ``` When `1` (default), underscores act as digit separators: `1_000_000` parses as `1000000`. Set to `0` to disable. ### Types #### `fio_aton_s` ```c typedef struct { union { int64_t i; double f; uint64_t u; }; int is_float; int err; } fio_aton_s; ``` Result container for `fio_aton`. Read the union member that matches `is_float`, and check `err` for overflow or parse failures. ### Universal Parsing #### `fio_aton` ```c FIO_SFUNC fio_aton_s fio_aton(char **pstr); ``` Auto-detects integers and floats. Skips leading whitespace, recognizes `0x` / `0b` / octal prefixes, and accepts `inf`, `infinity`, and `nan`. Updates `*pstr` to the first unconverted character. Sets `.err` on overflow or bad format. **Note:** not an exact `strtod` replacement; rounding differences are possible. ### Strict, Bounded Parsing (wire-safe) The `fio_stol*` family parses within an explicit `[pos, end)` window and accepts **only** the wire format: no whitespace, no underscores, no `0x`/`0b` prefixes, no `inf`/`nan`. Use these for network protocols and any length/size field - the lenient `fio_atol*` semantics (e.g., `1_0` parsing as `10`) are a differential-parsing hazard when a peer or intermediary parses the same field strictly. All three advance `*pos` past the digits consumed (detect trailing junk or empty input by comparing `*pos` against expectations) and set `errno == E2BIG` on overflow, stopping at the last valid digit. Since the window is explicit, the guard-byte contract of the lenient family does not apply. #### `fio_stol10u` ```c SFUNC uint64_t fio_stol10u(char **pos, const char *end); ``` Parses an unsigned `uint64_t`, decimal digits only. No sign, no whitespace skip. #### `fio_stol10` ```c SFUNC int64_t fio_stol10(char **pos, const char *end); ``` Parses a signed `int64_t`: one optional leading `-` / `+`, then decimal digits only. `E2BIG` is set when the magnitude overflows `uint64_t` or exceeds the `int64_t` range (note: `-9223372036854775808` is exactly `INT64_MIN` and is **not** an overflow). #### `fio_stol16u` ```c SFUNC uint64_t fio_stol16u(char **pos, const char *end); ``` Parses an unsigned `uint64_t`, hex digits only - unlike `fio_atol16u`, a `0x` prefix is **not** consumed. ### Signed Conversion #### `fio_atol` ```c SFUNC int64_t fio_atol(char **pstr); ``` Parses a signed `int64_t`. Accepts base 10, octal (`0...`), hex (`0x...` or `x...`), and binary (`0b...` or `b...`). Updates `*pstr` past the number. #### `fio_atof` ```c SFUNC double fio_atof(char **pstr); ``` Parses a double. Wraps `strtod` for most inputs. The source also attempts to accept a raw `0b...` binary bit-pattern, but the detection condition looks fragile. #### `fio_ftoa` ```c SFUNC size_t fio_ftoa(char *dest, double num, uint8_t base); ``` Writes `num` to `dest` in `base` (2, 10, or 16; unsupported bases silently fall back to 10). No prefixes are added. Returns bytes written excluding NUL. **Note:** provide at least 130 bytes for base 2. Special values `inf` and `nan` produce `"Infinity"` / `"NaN"`. #### `fio_ltoa` ```c SFUNC size_t fio_ltoa(char *dest, int64_t num, uint8_t base); ``` Writes `num` to `dest` in `base` (2, 8, 10, 16, or any base up to 36). Adds `0x`, `0b`, or `0` prefixes for the built-in bases. Returns bytes written excluding NUL. If `dest` is `NULL`, writes to an internal scratch buffer and still returns the length. Logs an error and returns `0` for unsupported bases. **Note:** provide at least 68 bytes for base 2. #### `fio_ltoa10` ```c FIO_IFUNC void fio_ltoa10(char *dest, int64_t i, size_t digits); ``` Writes a signed base-10 number using exactly `digits` bytes plus NUL. Use `fio_digits10()` to compute `digits`. #### `fio_atol10` ```c SFUNC int64_t fio_atol10(char **pstr); ``` Reads a signed base-10 number. ### Unsigned Conversion #### `fio_atol8u` ```c SFUNC uint64_t fio_atol8u(char **pstr); ``` Reads an unsigned octal number. May overflow the buffer if no terminator is present. #### `fio_atol10u` ```c SFUNC uint64_t fio_atol10u(char **pstr); ``` Reads an unsigned base-10 number. #### `fio_atol16u` ```c SFUNC uint64_t fio_atol16u(char **pstr); ``` Reads an unsigned hex number, with optional `0x` prefix. #### `fio_atol_bin` ```c SFUNC uint64_t fio_atol_bin(char **pstr); ``` Reads an unsigned binary number, with optional `0b` prefix. #### `fio_atol_xbase` ```c SFUNC uint64_t fio_atol_xbase(char **pstr, size_t base); ``` Reads an unsigned number in any base up to 36. #### `fio_ltoa8u` ```c FIO_IFUNC void fio_ltoa8u(char *dest, uint64_t i, size_t digits); ``` Writes an unsigned octal number using `digits` bytes plus NUL. #### `fio_ltoa10u` ```c FIO_IFUNC void fio_ltoa10u(char *dest, uint64_t i, size_t digits); ``` Writes an unsigned base-10 number using `digits` bytes plus NUL. #### `fio_ltoa16u` ```c FIO_IFUNC void fio_ltoa16u(char *dest, uint64_t i, size_t digits); ``` Writes an unsigned hex number using `digits` bytes plus NUL. `digits` is rounded up to an even number. #### `fio_ltoa_bin` ```c FIO_IFUNC void fio_ltoa_bin(char *dest, uint64_t i, size_t digits); ``` Writes an unsigned binary number using `digits` bytes plus NUL. #### `fio_ltoa_xbase` ```c FIO_IFUNC void fio_ltoa_xbase(char *dest, uint64_t i, size_t digits, size_t base); ``` Writes an unsigned number in `base` (up to 36) using `digits` bytes plus NUL. ### Helpers #### `fio_c2i` ```c IFUNC uint8_t fio_c2i(unsigned char c); ``` Maps a character to its numeric value (`0-9` → 0-9, `A-Z`/`a-z` → 10-35). Returns `255` for out-of-range characters. #### `fio_i2c` ```c IFUNC uint8_t fio_i2c(unsigned char i); ``` Maps a numeric value `0-35` to a character (`0-9`, `A-Z`). Out-of-range values above 35 produce undefined behavior; accepts values up to 63 by masking. #### `fio_digits10` ```c FIO_IFUNC size_t fio_digits10(int64_t i); ``` Returns the number of base-10 digits needed for `i`, including the sign. #### `fio_digits10u` ```c FIO_SFUNC size_t fio_digits10u(uint64_t i); ``` Returns the number of base-10 digits needed for an unsigned number. #### `fio_digits8u` ```c FIO_SFUNC size_t fio_digits8u(uint64_t i); ``` Returns the number of base-8 digits needed for an unsigned number. #### `fio_digits16u` ```c FIO_SFUNC size_t fio_digits16u(uint64_t i); ``` Returns the number of base-16 digits needed for an unsigned number, always an even count (2, 4, 6, ... 16). #### `fio_digits_bin` ```c FIO_SFUNC size_t fio_digits_bin(uint64_t i); ``` Returns the number of base-2 digits needed for an unsigned number, rounded up to an even count. #### `fio_digits_xbase` ```c FIO_SFUNC size_t fio_digits_xbase(uint64_t i, size_t base); ``` Returns the number of digits needed for an unsigned number in `base` (must be < 65). #### `fio_u2i_limit` ```c FIO_IFUNC int64_t fio_u2i_limit(uint64_t val, size_t invert); ``` Converts unsigned `val` to signed with overflow protection. If `invert` is zero, clamps to `INT64_MAX` and sets `errno = E2BIG` on overflow. If `invert` is non-zero, produces the negative value and clamps to `INT64_MIN`. ### IEEE 754 Helpers #### `fio_i2d` ```c FIO_IFUNC double fio_i2d(int64_t mant, int64_t exponent_in_base_2); ``` Converts a signed 64-bit mantissa and base-2 exponent to a `double`. #### `fio_u2d` ```c FIO_IFUNC double fio_u2d(uint64_t mant, int64_t exponent_in_base_2); ``` Converts an unsigned 64-bit mantissa and base-2 exponent to a `double`. ### Big Number Hex Conversion #### `fio_u128_hex_read` / `fio_u256_hex_read` / `fio_u512_hex_read` ```c SFUNC fio_u128 fio_u128_hex_read(char **pstr); SFUNC fio_u256 fio_u256_hex_read(char **pstr); SFUNC fio_u512 fio_u512_hex_read(char **pstr); ``` Reads a hex string and initializes the corresponding wide integer. Updates `*pstr` past the consumed input. #### `fio_u1024_hex_read` / `fio_u2048_hex_read` / `fio_u4096_hex_read` ```c SFUNC fio_u1024 fio_u1024_hex_read(char **pstr); SFUNC fio_u2048 fio_u2048_hex_read(char **pstr); SFUNC fio_u4096 fio_u4096_hex_read(char **pstr); ``` Same as above for 1024-, 2048-, and 4096-bit integers. #### `fio_u128_hex_write` / `fio_u256_hex_write` / `fio_u512_hex_write` ```c SFUNC size_t fio_u128_hex_write(char *dest, const fio_u128 *u); SFUNC size_t fio_u256_hex_write(char *dest, const fio_u256 *u); SFUNC size_t fio_u512_hex_write(char *dest, const fio_u512 *u); ``` Writes a wide integer as a hex string to `dest`. Returns bytes written excluding NUL. #### `fio_u1024_hex_write` / `fio_u2048_hex_write` / `fio_u4096_hex_write` ```c SFUNC size_t fio_u1024_hex_write(char *dest, const fio_u1024 *u); SFUNC size_t fio_u2048_hex_write(char *dest, const fio_u2048 *u); SFUNC size_t fio_u4096_hex_write(char *dest, const fio_u4096 *u); ``` Same as above for 1024-, 2048-, and 4096-bit integers. ### Example ```c #define FIO_ATOL #include "fio-stl.h" int main(void) { char *p = "0x1F 0b1010 42"; char buf[80]; printf("hex: %lld\n", (long long)fio_atol(&p)); ++p; /* skip space */ printf("bin: %lld\n", (long long)fio_atol(&p)); ++p; printf("dec: %lld\n", (long long)fio_atol(&p)); fio_ltoa(buf, -255, 16); printf("back to hex: %s\n", buf); return 0; } ``` ------------------------------------------------------------ # CRC32 ```c #define FIO_CRC32 #include "fio-stl.h" ``` Fast CRC32 using the ITU-T V.42 / ISO 3309 / gzip polynomial (`0xEDB88320`). Useful for gzip, PNG, and anything else that expects this particular CRC32 flavor. The implementation picks the best available path at compile time: - ARM aarch64 with the CRC32 extension uses scalar CRC32 instructions, with a PMULL bulk-fold path when the crypto / PMULL feature is available. - x86 / x64 with SSE4.2 and PCLMULQDQ uses a PCLMULQDQ bulk path and the software tail path. - Other builds use the always-present slicing-by-16 software fallback. **Note:** this is **not** CRC32-C (Castagnoli, polynomial `0x82F63B78`). The ARM scalar path uses the gzip-polynomial CRC32 instructions (`__crc32b`, `__crc32w`, `__crc32d`), not the Castagnoli variants. ### API Functions #### `fio_crc32` ```c SFUNC uint32_t fio_crc32(const void *data, size_t len, uint32_t initial_crc); ``` Computes the CRC32 of `len` bytes at `data`. Pass `0` for `initial_crc` to start fresh, or pass a previous result to continue an incremental checksum across multiple buffers. **Returns:** the CRC32 checksum. ### Example — single buffer ```c #define FIO_CRC32 #include "fio-stl.h" int main(void) { const char *msg = "Hello, World!"; uint32_t crc = fio_crc32(msg, strlen(msg), 0); printf("CRC32: 0x%08X\n", crc); return 0; } ``` ### Example — incremental ```c uint32_t crc = 0; crc = fio_crc32("Hello, ", 7, crc); crc = fio_crc32("World!", 6, crc); /* crc matches a single call over "Hello, World!" */ ``` ------------------------------------------------------------ # Glob Matching ```c #define FIO_GLOB_MATCH #include "fio-stl.h" ``` Binary glob matching for wildcard patterns. Bytes in, bytes out — no UTF-8 awareness, no allocation, just `*`, `?`, `[...]`, and escapes. ### API Functions #### `fio_glob_match` ```c SFUNC uint8_t fio_glob_match(fio_str_info_s pattern, fio_str_info_s string); ``` Returns `1` if `string` matches `pattern`, otherwise `0`. ### Supported Patterns #### `*` Matches any byte sequence, including an empty one. #### `?` Matches any single byte. #### `[...]` Matches any single byte inside the brackets. Ranges work with `-`, e.g. `[a-z]`. The first character may be `]` without closing the class. Use `\` to escape `]`, `-`, and `\` itself. #### `[!...]` or `[^...]` Inverted character class: matches any single byte **not** in the brackets. #### `\` Escapes the next character so it is matched literally. ### Example ```c #define FIO_GLOB_MATCH #define FIO_STR #include "fio-stl.h" int main(void) { printf("%d\n", fio_glob_match(FIO_STR_INFO1("*.txt"), FIO_STR_INFO1("notes.txt"))); /* 1 */ printf("%d\n", fio_glob_match(FIO_STR_INFO1("log_????"), FIO_STR_INFO1("log_2024"))); /* 1 */ printf("%d\n", fio_glob_match(FIO_STR_INFO1("[!0-9]*"), FIO_STR_INFO1("abc123"))); /* 1 */ printf("%d\n", fio_glob_match(FIO_STR_INFO1("file\\*"), FIO_STR_INFO1("file*"))); /* 1 */ return 0; } ``` ------------------------------------------------------------ # Index Mapped Array (iMap) ```c #define FIO_IMAP_CORE #include "fio-stl.h" ``` A macro-generated hash-map-backed array. Keeps insertion order in a dense array while the index map provides near-O(1) lookups. Mostly used internally by other STL modules; for public map needs see [`FIO_MAP_NAME`](https://facil.io/0.8.x/hash-maps-and-sets-module-210/). **Note:** iMap does not manage object lifetimes. Clean up stored resources before calling destroy. ### Configuration Macros #### `FIO_TYPEDEF_IMAP_REALLOC` ```c #ifndef FIO_TYPEDEF_IMAP_REALLOC #define FIO_TYPEDEF_IMAP_REALLOC FIO_MEM_REALLOC #endif ``` Allocator used for iMap growth. Defaults to `FIO_MEM_REALLOC`. #### `FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE` ```c #ifndef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE #define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE FIO_MEM_REALLOC_IS_SAFE #endif ``` Set if the realloc zeroes new memory; otherwise iMap zeroes it explicitly. #### `FIO_TYPEDEF_IMAP_FREE` ```c #ifndef FIO_TYPEDEF_IMAP_FREE #define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE #endif ``` Deallocator used by `array_name_destroy`. ### Helper Macros #### `FIO_IMAP_ALWAYS_VALID` ```c #define FIO_IMAP_ALWAYS_VALID(o) (1) ``` Validity helper that treats every object as valid. #### `FIO_IMAP_VALID_NON_ZERO` ```c #define FIO_IMAP_VALID_NON_ZERO(o) (!!((o)[0])) ``` Validity helper that treats an object as valid if its first element is non-zero. #### `FIO_IMAP_ALWAYS_CMP_TRUE` / `FIO_IMAP_ALWAYS_CMP_FALSE` ```c #define FIO_IMAP_ALWAYS_CMP_TRUE(a, b) (1) #define FIO_IMAP_ALWAYS_CMP_FALSE(a, b) (0) ``` Comparison helpers that always return true or false. #### `FIO_IMAP_SIMPLE_CMP` ```c #define FIO_IMAP_SIMPLE_CMP(a, b) ((a)[0] == (b)[0]) ``` Compares two objects by their first element. #### `FIO_IMAP_EACH` ```c #define FIO_IMAP_EACH(array_name, map_ptr, i) \ for (size_t i = 0; i < (map_ptr)->w; ++i) \ if (!FIO_NAME(array_name, is_valid)((map_ptr)->ary + i)) \ continue; \ else ``` Iterates valid elements in insertion order. Use it as a loop header: ```c FIO_IMAP_EACH(my_map, &map, i) { printf("%llu\n", (unsigned long long)map.ary[i].key); } ``` ### Type Definition Macro #### `FIO_TYPEDEF_IMAP_ARRAY` ```c #define FIO_TYPEDEF_IMAP_ARRAY(array_name, \ array_type, \ imap_type, \ hash_fn, \ cmp_fn, \ is_valid_fn) ``` Generates the iMap container type and functions prefixed with `array_name`. The callbacks take **pointers** to elements: - `hash_fn(pobj)` returns an `imap_type` hash. - `cmp_fn(a_ptr, b_ptr)` returns non-zero on match. - `is_valid_fn(pobj)` returns non-zero if the element is valid. Reserved index-map values: `0` means empty, `~0` means freed. ### Generated Types #### `array_name_s` ```c typedef struct { array_type *ary; imap_type count; imap_type w; uint32_t capa_bits; } array_name_s; ``` - `ary` — dense array of elements. - `count` — number of valid (non-removed) elements. - `w` — next write position / logical size. - `capa_bits` — log2 of capacity; actual capacity is `1 << capa_bits`. #### `array_name_seeker_s` ```c typedef struct { imap_type pos; imap_type ipos; imap_type set_val; } array_name_seeker_s; ``` - `pos` — array index, or `w` if not found. - `ipos` — index-map slot, or `~0` if no room. - `set_val` — value to write into the index map on insert. ### Generated Functions #### `array_name_is_valid` ```c FIO_IFUNC int array_name_is_valid(array_type *pobj); ``` Wraps the `is_valid_fn` supplied to the macro. #### `array_name_capa` ```c FIO_IFUNC size_t array_name_capa(array_name_s *a); ``` Returns `1 << a->capa_bits`, or `0` if not allocated. #### `array_name_imap` ```c FIO_IFUNC imap_type *array_name_imap(array_name_s *a); ``` Returns a pointer to the index map stored immediately after the array. #### `array_name_destroy` ```c FIO_IFUNC void array_name_destroy(array_name_s *a); ``` Frees the array + index map and zeros the container. Does not run destructors on elements. #### `array_name_seek` ```c FIO_SFUNC array_name_seeker_s array_name_seek(array_name_s *a, array_type *pobj); ``` Finds an element or the place to insert it. #### `array_name_reserve` ```c FIO_IFUNC int array_name_reserve(array_name_s *a, imap_type min); ``` Grows the backing storage to at least `min` slots. Returns `0` on success, `-1` on failure. #### `array_name_rehash` ```c FIO_IFUNC int array_name_rehash(array_name_s *a); ``` Rebuilds the index map from the current array contents. Call after sorting or other position-changing operations. Returns `0` on success, `-1` on failure. #### `array_name_set` ```c FIO_IFUNC array_type *array_name_set(array_name_s *a, array_type obj, int overwrite); ``` Inserts or updates an element. If `overwrite` is zero and the key already exists, returns the existing element. Returns a pointer to the stored element or `NULL` on failure. #### `array_name_get` ```c FIO_IFUNC array_type *array_name_get(array_name_s *a, array_type obj); ``` Looks up an element. Returns a pointer to the match or `NULL`. #### `array_name_remove` ```c FIO_IFUNC int array_name_remove(array_name_s *a, array_type obj); ``` Zeros the matching element and marks its index-map slot as freed. Returns `0` on success, `-1` if not found. ### Internal Seeker Types #### `fio___imapN_seeker_s` ```c typedef struct { uintN_t pos; uintN_t ipos; uintN_t set_val; bool is_valid; } fio___imapN_seeker_s; ``` Where `N` is `8`, `16`, `32`, or `64`. Low-level seeker used by other STL internals. #### `fio___imapN_seek` ```c FIO_SFUNC fio___imapN_seeker_s fio___imapN_seek( void *ary, uintN_t *imap, const uintN_t capa_bits, void *pobj, uintN_t hash, bool cmp_fn(void *arry, void *obj, uintN_t indx), const size_t max_attempts); ``` Low-level seek through an index map. Generally used internally. #### `fio___imapN_set` ```c FIO_IFUNC void fio___imapN_set(uintN_t *imap, uintN_t ipos, uintN_t set_val); ``` Writes `set_val` into `imap[ipos]`. ### Example ```c #define FIO_IMAP_CORE #include "fio-stl.h" typedef struct { uint64_t key; int value; } kv_s; static uint64_t kv_hash(kv_s *p) { return fio_risky_num(p->key, 0); } static int kv_cmp(kv_s *a, kv_s *b) { return a->key == b->key; } static int kv_valid(kv_s *p) { return p->key != 0; } FIO_TYPEDEF_IMAP_ARRAY(kv, kv_s, uint32_t, kv_hash, kv_cmp, kv_valid) int main(void) { kv_s map = {0}; kv_set(&map, (kv_s){.key = 1, .value = 100}, 1); kv_set(&map, (kv_s){.key = 2, .value = 200}, 1); kv_s *found = kv_get(&map, (kv_s){.key = 2}); if (found) printf("value: %d\n", found->value); FIO_IMAP_EACH(kv, &map, i) { printf("%llu -> %d\n", (unsigned long long)map.ary[i].key, map.ary[i].value); } kv_remove(&map, (kv_s){.key = 2}); kv_destroy(&map); return 0; } ``` ------------------------------------------------------------ # Multi-Precision Math ```c #define FIO_MATH #include "fio-stl.h" ``` Extra multi-precision operations on little-endian `uint64_t` arrays: division, shifts, two's-complement inverse, and bit-index queries. The core add/sub/mul helpers (`fio_math_add`, `fio_math_sub`, `fio_math_mul`, `fio_math_addc64`, etc.) and the wide union types (`fio_u128`, `fio_u256`, …) live in [`000 core.h`](https://facil.io/0.8.x/facil-io-stl-core/) and do not require `FIO_MATH`. **Note:** this module assumes the CPU performs 64-bit multiply in constant time, which is not guaranteed on all hardware. ### API Functions #### `fio_math_div` ```c FIO_IFUNC void fio_math_div(uint64_t *dest, uint64_t *reminder, const uint64_t *a, const uint64_t *b, const size_t number_array_length); ``` Divides the `number_array_length`-word number `a` by `b`, storing the quotient in `dest` and the remainder in `reminder`. Either output may be `NULL` if you only need the other one. **Note:** not constant time. Uses binary long division, which is simple but not the fastest algorithm on earth. If `b` is zero, logs an error and fills both outputs with `0xFF`. #### `fio_math_shr` ```c FIO_IFUNC void fio_math_shr(uint64_t *dest, uint64_t *n, const size_t right_shift_bits, size_t number_array_length); ``` Shifts `n` right by `right_shift_bits` and stores the result in `dest`. #### `fio_math_shl` ```c FIO_IFUNC void fio_math_shl(uint64_t *dest, uint64_t *n, const size_t left_shift_bits, const size_t number_array_length); ``` Shifts `n` left by `left_shift_bits` and stores the result in `dest`. #### `fio_math_inv` ```c FIO_IFUNC void fio_math_inv(uint64_t *dest, uint64_t *n, size_t len); ``` Two's-complement negation of the `len`-word number `n`. #### `fio_math_msb_index` ```c FIO_MIFN size_t fio_math_msb_index(uint64_t *n, const size_t len); ``` Returns the zero-based index of the most significant set bit, or `(size_t)-1` if the number is zero. Useful for measuring bit length. #### `fio_math_lsb_index` ```c FIO_MIFN size_t fio_math_lsb_index(uint64_t *n, const size_t len); ``` Returns the zero-based index of the least significant set bit, or `(size_t)-1` if the number is zero. ### Example ```c #define FIO_MATH #include "fio-stl.h" int main(void) { uint64_t a[2] = {0, 1}; /* 2^64 */ uint64_t b[2] = {3, 0}; /* 3 */ uint64_t q[2] = {0}; uint64_t r[2] = {0}; fio_math_div(q, r, a, b, 2); printf("quot: %016llX%016llX\n", (unsigned long long)q[1], (unsigned long long)q[0]); printf("rem: %016llX%016llX\n", (unsigned long long)r[1], (unsigned long long)r[0]); return 0; } ``` ------------------------------------------------------------ # Pseudo-Random and Hashing ```c #define FIO_RAND #include "fio-stl.h" ``` A non-cryptographic pseudo-random generator plus RiskyHash and Stable Hash. The PRNG is seeded from system jitter (`getrusage`, clocks) and hashed with RiskyHash. It is faster and more random than typical `rand()` implementations, but **do not use it for cryptography** — use `fio_rand_bytes_secure` for that. RiskyHash is an ephemeral, non-cryptographic hash family that may change between releases. Stable Hash is frozen and safe for persistent data. RiskyHash 256/512 and HMAC variants are also provided. ### Pseudo-Random Functions #### `fio_rand64` ```c SFUNC uint64_t fio_rand64(void); ``` Returns 64 pseudo-random bits. #### `fio_rand128` ```c SFUNC fio_u128 fio_rand128(void); ``` Returns 128 pseudo-random bits as a `fio_u128`. #### `fio_rand_bytes` ```c SFUNC void fio_rand_bytes(void *target, size_t len); ``` Fills `target` with `len` pseudo-random bytes. #### `fio_rand_bytes_secure` ```c SFUNC int fio_rand_bytes_secure(void *target, size_t len); ``` Fills `target` with `len` cryptographically secure random bytes from the system CSPRNG: `arc4random_buf` on BSD/macOS, `BCryptGenRandom` on Windows, `getrandom` on Linux, and `/dev/urandom` as the fallback (also used when `getrandom` is unavailable or blocked). Returns `0` on success, `-1` on failure (no partial success; never falls back to the non-cryptographic PRNG). A `len` of `0` succeeds; a `NULL` `target` with `len > 0` fails. **Windows linking**: MSVC links `bcrypt.lib` automatically (`#pragma comment`); MinGW / GNU-driver builds must link `-lbcrypt`. Use this for keys, nonces, and anything security-sensitive. #### `fio_rand_reseed` ```c SFUNC void fio_rand_reseed(void); ``` Rotates the PRNG state with fresh entropy. Call after `fork` to avoid duplicated sequences. ### Risky / Stable Hash #### `FIO_USE_STABLE_HASH_WHEN_CALLING_RISKY_HASH` ```c #define FIO_USE_STABLE_HASH_WHEN_CALLING_RISKY_HASH 0 ``` When set to `1`, `fio_risky_hash` redirects to `fio_stable_hash`. #### `fio_risky_hash` ```c SFUNC uint64_t fio_risky_hash(const void *buf, size_t len, uint64_t seed); ``` 64-bit RiskyHash v.3. Ephemeral — hash values may change in future releases. Good for hash-map keys, not for storage. #### `fio_risky_ptr` ```c FIO_IFUNC uint64_t fio_risky_ptr(void *ptr); ``` Fast entropy mix for pointer values. #### `fio_risky_num` ```c FIO_IFUNC uint64_t fio_risky_num(uint64_t number, uint64_t seed); ``` Fast entropy mix for integer values. #### `fio_stable_hash` ```c SFUNC uint64_t fio_stable_hash(const void *data, size_t len, uint64_t seed); ``` 64-bit Stable Hash. Frozen after 1.0; safe for on-disk or cross-system use. #### `fio_stable_hash128` ```c SFUNC void fio_stable_hash128(void *restrict dest, const void *restrict data, size_t len, uint64_t seed); ``` Writes a 128-bit Stable Hash into `dest` (16 bytes). #### `fio_risky256` ```c SFUNC fio_u256 fio_risky256(const void *data, uint64_t len); ``` 256-bit RiskyHash. Based on the A3 (Zero-Copy ILP) design: two 512-bit states, 128 bytes per iteration, multiply-fold with cross-lane mixing. Returns a `fio_u256`. #### `fio_risky512` ```c SFUNC fio_u512 fio_risky512(const void *data, uint64_t len); ``` 512-bit RiskyHash. The first 256 bits are identical to `fio_risky256`; the rest come from an extra squeeze round. #### `fio_risky256_hmac` ```c SFUNC fio_u256 fio_risky256_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` RFC 2104 HMAC using `fio_risky256` as the hash, 64-byte block size. Keys longer than 64 bytes are hashed first. Securely zeros intermediates. **Note:** the underlying hash is non-cryptographic; standard HMAC security proofs do not apply. Use Blake2 for cryptographic HMAC. #### `fio_risky512_hmac` ```c SFUNC fio_u512 fio_risky512_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Same construction as `fio_risky256_hmac`, but with `fio_risky512` and a 64-byte digest. ### Example ```c #define FIO_RAND #include "fio-stl.h" int main(void) { printf("rand64: %016llX\n", (unsigned long long)fio_rand64()); uint8_t key[32]; fio_rand_bytes_secure(key, sizeof(key)); uint64_t h = fio_stable_hash("hello", 5, 0); printf("stable: %016llX\n", (unsigned long long)h); fio_u256 r = fio_risky256("hello", 5); printf("risky256: %016llX...\n", (unsigned long long)r.u64[0]); return 0; } ``` ------------------------------------------------------------ # Signal Monitoring ```c #define FIO_SIGNAL #include "fio-stl.h" ``` A small wrapper around OS signals. It sets a handler, records that the signal fired, and lets you call the real callback later from normal execution context — the safe pattern, since signal handlers are heavily restricted. POSIX and Windows are supported. ### Configuration Macros #### `FIO_SIGNAL_MONITOR_MAX` ```c #ifndef FIO_SIGNAL_MONITOR_MAX #define FIO_SIGNAL_MONITOR_MAX 24 #endif ``` Maximum number of signals that can be monitored at once. ### Portable Signal Aliases #### `FIO_SIGNAL_USER1` / `FIO_SIGNAL_USER2` / `FIO_SIGNAL_USER_UNREGISTERED` ```c #if FIO_OS_POSIX #define FIO_SIGNAL_USER1 SIGUSR1 #define FIO_SIGNAL_USER2 SIGUSR2 #define FIO_SIGNAL_USER_UNREGISTERED SIGUSR2 #elif FIO_OS_WIN #define FIO_SIGNAL_USER1 SIGBREAK #define FIO_SIGNAL_USER2 SIGABRT #define FIO_SIGNAL_USER_UNREGISTERED SIGBREAK #endif ``` Maps user signals across POSIX and Windows. ### Types #### `fio_signal_monitor_args_s` ```c typedef struct { int sig; void (*callback)(int sig, void *udata); void *udata; bool propagate; bool immediate; } fio_signal_monitor_args_s; ``` - `sig` — signal number to watch. - `callback` — function to run; `NULL` ignores the signal. - `udata` — opaque pointer passed to the callback. - `propagate` — if true, call any previous handler after recording the signal. - `immediate` — if true, run the callback inside the signal handler; otherwise defer to `fio_signal_review`. ### API Functions #### `fio_signal_monitor` ```c SFUNC int fio_signal_monitor(fio_signal_monitor_args_s args); #define fio_signal_monitor(...) \ fio_signal_monitor((fio_signal_monitor_args_s){__VA_ARGS__}) ``` Starts monitoring `sig`. Updating an existing monitor replaces its callback and `udata`. Returns `0` on success, `-1` on error. **Note:** `immediate` callbacks must be async-signal-safe. Most code is not. #### `fio_signal_review` ```c SFUNC int fio_signal_review(void); ``` Calls deferred callbacks for any signals that fired since the last review. Returns the number of callbacks invoked. #### `fio_signal_forget` ```c SFUNC int fio_signal_forget(int sig); ``` Stops monitoring `sig` and restores the default handler. Returns `0` on success, `-1` on error. ### Example ```c #define FIO_SIGNAL #define FIO_LOG #include "fio-stl.h" static volatile int running = 1; static void on_sigint(int sig, void *udata) { (void)sig; (void)udata; FIO_LOG_INFO("SIGINT received, shutting down..."); running = 0; } int main(void) { fio_signal_monitor(.sig = SIGINT, .callback = on_sigint, .udata = NULL); while (running) { fio_signal_review(); /* do work */ } fio_signal_forget(SIGINT); return 0; } ``` ------------------------------------------------------------ # Sorting ```c #define FIO_SORT_NAME num #define FIO_SORT_TYPE size_t #include "fio-stl.h" ``` A macro-generated quicksort + insert-sort combo. Define `FIO_SORT_NAME` and `FIO_SORT_TYPE` before including the header to get a set of sort functions named after your type. Include the header multiple times to sort different types. ### Configuration Macros #### `FIO_SORT_NAME` ```c #define FIO_SORT_NAME num ``` Prefix for generated function names: `num_sort`, `num_qsort`, `num_isort`. #### `FIO_SORT_TYPE` ```c #define FIO_SORT_TYPE size_t ``` The element type. **Required.** #### `FIO_SORT_IS_BIGGER` ```c #ifndef FIO_SORT_IS_BIGGER #define FIO_SORT_IS_BIGGER(a, b) ((a) > (b)) #endif ``` Must evaluate to `1` if `a > b`, `0` otherwise. Override for custom ordering. #### `FIO_SORT_SWAP` ```c #ifndef FIO_SORT_SWAP #define FIO_SORT_SWAP(a, b) \ do { \ FIO_SORT_TYPE tmp__ = (a); \ (a) = (b); \ (b) = tmp__; \ } while (0) #endif ``` Swaps two elements. Override if your type needs a custom swap. #### `FIO_SORT_THRESHOLD` ```c #ifndef FIO_SORT_THRESHOLD #define FIO_SORT_THRESHOLD 96 #endif ``` Partition size below which quicksort switches to insert-sort. ### API Functions #### `FIO_SORT_NAME_sort` ```c FIO_IFUNC void FIO_NAME(FIO_SORT_NAME, sort)(FIO_SORT_TYPE *array, size_t count); ``` Sorts `array[0..count-1]` in ascending order. Currently dispatches to quicksort. #### `FIO_SORT_NAME_qsort` ```c SFUNC void FIO_NAME(FIO_SORT_NAME, qsort)(FIO_SORT_TYPE *array, size_t count); ``` Non-recursive quicksort with median-of-three pivot selection. Falls back to insert-sort for small partitions. #### `FIO_SORT_NAME_isort` ```c SFUNC void FIO_NAME(FIO_SORT_NAME, isort)(FIO_SORT_TYPE *array, size_t count); ``` Insert-sort for small or nearly-sorted arrays. O(n²), so avoid large inputs. ### Example ```c #define FIO_SORT_NAME int #define FIO_SORT_TYPE int #include "fio-stl.h" int main(void) { int numbers[] = {5, 2, 8, 1, 9, 3, 7, 4, 6, 0}; size_t count = sizeof(numbers) / sizeof(numbers[0]); int_sort(numbers, count); for (size_t i = 0; i < count; ++i) printf("%d ", numbers[i]); printf("\n"); return 0; } ``` ------------------------------------------------------------ # Portable Threads ```c #define FIO_THREADS #include "fio-stl.h" ``` A thin, portable wrapper over POSIX pthreads and Windows threads. Provides thread creation, mutexes, condition variables, priorities, and a few process helpers. The wrappers intentionally return no thread exit value. ### Types #### `fio_thread_t` ```c /* POSIX */ typedef pthread_t fio_thread_t; /* Windows */ typedef uintptr_t fio_thread_t; ``` Thread handle. On Windows this is the numeric thread ID; the handle is opened transiently only when needed. #### `fio_thread_pid_t` ```c /* POSIX */ typedef pid_t fio_thread_pid_t; /* Windows */ typedef DWORD fio_thread_pid_t; ``` Process ID type. #### `fio_thread_mutex_t` ```c /* POSIX */ typedef pthread_mutex_t fio_thread_mutex_t; /* Windows */ typedef SRWLOCK fio_thread_mutex_t; ``` Mutex type. #### `fio_thread_cond_t` ```c /* POSIX */ typedef pthread_cond_t fio_thread_cond_t; /* Windows */ typedef CONDITION_VARIABLE fio_thread_cond_t; ``` Condition-variable type. #### `fio_thread_priority_e` ```c typedef enum { FIO_THREAD_PRIORITY_ERROR = -1, FIO_THREAD_PRIORITY_LOWEST = 0, FIO_THREAD_PRIORITY_LOW, FIO_THREAD_PRIORITY_NORMAL, FIO_THREAD_PRIORITY_HIGH, FIO_THREAD_PRIORITY_HIGHEST, } fio_thread_priority_e; ``` Thread priority levels. ### Configuration Macros #### `FIO_THREADS_BYO` If defined, the thread creation/join/detach/current/yield functions are declared but not implemented — bring your own. #### `FIO_THREADS_FORK_BYO` If defined, the process helpers (`fio_thread_fork`, `getpid`, `kill`, `waitpid`) are declared but not implemented. #### `FIO_THREADS_MUTEX_BYO` If defined, the mutex functions are declared but not implemented. #### `FIO_THREADS_COND_BYO` If defined, the condition-variable functions are declared but not implemented. #### `FIO_THREAD_MUTEX_INIT` ```c /* POSIX */ #define FIO_THREAD_MUTEX_INIT PTHREAD_MUTEX_INITIALIZER /* Windows */ #define FIO_THREAD_MUTEX_INIT { 0 } ``` Static initializer for a mutex. ### Process Functions #### `fio_thread_fork` ```c FIO_IFUNC fio_thread_pid_t fio_thread_fork(void); ``` Behaves like POSIX `fork`. On Windows logs an error and returns `-1`. #### `fio_thread_getpid` ```c FIO_IFUNC fio_thread_pid_t fio_thread_getpid(void); ``` Behaves like POSIX `getpid`. #### `fio_thread_kill` ```c FIO_IFUNC int fio_thread_kill(fio_thread_pid_t pid, int sig); ``` Behaves like POSIX `kill`. #### `fio_thread_waitpid` ```c FIO_IFUNC int fio_thread_waitpid(fio_thread_pid_t pid, int *stat_loc, int options); ``` Behaves like POSIX `waitpid`. ### Thread Functions #### `fio_thread_create` ```c FIO_IFUNC int fio_thread_create(fio_thread_t *t, void *(*fn)(void *), void *arg); ``` Starts a new thread running `fn(arg)`. Returns `0` on success, `-1` on failure. #### `fio_thread_join` ```c FIO_IFUNC int fio_thread_join(fio_thread_t *t); ``` Waits for the thread to finish. #### `fio_thread_detach` ```c FIO_IFUNC int fio_thread_detach(fio_thread_t *t); ``` Detaches the thread so resources are reclaimed automatically. #### `fio_thread_exit` ```c FIO_IFUNC void fio_thread_exit(void); ``` Exits the current thread. #### `fio_thread_equal` ```c FIO_IFUNC int fio_thread_equal(fio_thread_t *a, fio_thread_t *b); ``` Returns non-zero if `a` and `b` refer to the same thread. #### `fio_thread_current` ```c FIO_IFUNC fio_thread_t fio_thread_current(void); ``` Returns a handle for the current thread. #### `fio_thread_nid` ```c FIO_IFUNC uintptr_t fio_thread_nid(void); ``` Returns a process-local numeral ID for the current thread. The value is stable for the thread's lifetime and unique among live threads, but may be reused after the thread exits. It is not guaranteed to be an OS-visible kernel thread ID. #### `fio_thread_yield` ```c FIO_IFUNC void fio_thread_yield(void); ``` Yields the CPU. #### `fio_thread_priority` ```c FIO_SFUNC fio_thread_priority_e fio_thread_priority(void); ``` Returns the current thread's priority, or `FIO_THREAD_PRIORITY_ERROR` on failure. #### `fio_thread_priority_set` ```c FIO_SFUNC int fio_thread_priority_set(fio_thread_priority_e pr); ``` Sets the current thread's priority. Returns `0` on success, `-1` on error. ### Mutex Functions #### `fio_thread_mutex_init` ```c FIO_IFUNC int fio_thread_mutex_init(fio_thread_mutex_t *m); ``` Initializes a mutex. You can also use `FIO_THREAD_MUTEX_INIT` for static initialization. #### `fio_thread_mutex_lock` ```c FIO_IFUNC int fio_thread_mutex_lock(fio_thread_mutex_t *m); ``` Locks `m`, blocking if necessary. #### `fio_thread_mutex_trylock` ```c FIO_IFUNC int fio_thread_mutex_trylock(fio_thread_mutex_t *m); ``` Tries to lock `m` without blocking. Returns `0` on success. #### `fio_thread_mutex_unlock` ```c FIO_IFUNC int fio_thread_mutex_unlock(fio_thread_mutex_t *m); ``` Unlocks `m`. #### `fio_thread_mutex_destroy` ```c FIO_IFUNC void fio_thread_mutex_destroy(fio_thread_mutex_t *m); ``` Destroys `m`. ### Condition Variable Functions #### `fio_thread_cond_init` ```c FIO_IFUNC int fio_thread_cond_init(fio_thread_cond_t *c); ``` Initializes a condition variable. #### `fio_thread_cond_wait` ```c FIO_IFUNC int fio_thread_cond_wait(fio_thread_cond_t *c, fio_thread_mutex_t *m); ``` Waits on `c`. `m` must be locked; it is released while waiting and re-acquired on return. #### `fio_thread_cond_timedwait` ```c FIO_IFUNC int fio_thread_cond_timedwait(fio_thread_cond_t *c, fio_thread_mutex_t *m, size_t milliseconds); ``` Waits up to `milliseconds`. Returns `0` if signaled, non-zero on timeout/error. #### `fio_thread_cond_signal` ```c FIO_IFUNC int fio_thread_cond_signal(fio_thread_cond_t *c); ``` Wakes one waiter. #### `fio_thread_cond_broadcast` ```c FIO_IFUNC int fio_thread_cond_broadcast(fio_thread_cond_t *c); ``` Wakes all waiters. #### `fio_thread_cond_destroy` ```c FIO_IFUNC void fio_thread_cond_destroy(fio_thread_cond_t *c); ``` Destroys `c`. ### Example ```c #define FIO_THREADS #include "fio-stl.h" static fio_thread_mutex_t lock = FIO_THREAD_MUTEX_INIT; static int counter = 0; static void *worker(void *arg) { (void)arg; for (int i = 0; i < 100000; ++i) { fio_thread_mutex_lock(&lock); ++counter; fio_thread_mutex_unlock(&lock); } return NULL; } int main(void) { fio_thread_t t1, t2; fio_thread_create(&t1, worker, NULL); fio_thread_create(&t2, worker, NULL); fio_thread_join(&t1); fio_thread_join(&t2); printf("counter: %d\n", counter); fio_thread_mutex_destroy(&lock); return 0; } ``` ------------------------------------------------------------ # URL / URI Parsing ```c #define FIO_URL #include "fio-stl.h" ``` Zero-copy, zero-allocation URI parser. Returns `fio_buf_info_s` slices pointing into the original string. No decoding is performed; pass the raw, encoded URI. ### Types #### `fio_url_s` ```c typedef struct { fio_buf_info_s scheme; fio_buf_info_s user; fio_buf_info_s password; fio_buf_info_s host; fio_buf_info_s port; fio_buf_info_s path; fio_buf_info_s query; fio_buf_info_s target; } fio_url_s; ``` Parsed URL components. Empty components have `.buf == NULL` and `.len == 0`. Strings are **not** NUL terminated. #### `fio_url_tls_info_s` ```c typedef struct { fio_buf_info_s key; fio_buf_info_s cert; fio_buf_info_s pass; bool tls; } fio_url_tls_info_s; ``` TLS material extracted from scheme or query parameters. #### `fio_url_query_each_s` ```c typedef struct { fio_buf_info_s name; fio_buf_info_s value; fio_buf_info_s private___; } fio_url_query_each_s; ``` Iterator state for `FIO_URL_QUERY_EACH`. Do not touch `private___`. ### API Functions #### `fio_url_parse` ```c SFUNC fio_url_s fio_url_parse(const char *url, size_t len); ``` Parses `url` of length `len` into components. Accepts many shapes: - `/path?query#target` - `host:port/path?query#target` - `user:password@host:port/path?query#target` - `scheme://user:password@host:port/path?query#target` Invalid input produces unpredictable results — the parser does not validate. **Note:** `file`, `unix`, and `priv` schemes are treated as file paths; the parser puts the whole remainder into `path` and clears host/port/user/password. #### `fio_url_is_tls` ```c SFUNC fio_url_tls_info_s fio_url_is_tls(fio_url_s u); ``` Detects TLS from scheme or query. Implicit TLS schemes include `wss`, `https`, `sses`, `ssse`, `tcps`, `stcp`, `tls`, `ssl`, `udps`, `sudp`. Explicit TLS is enabled by query keys `tls`, `key`, `cert`, `pass`, or `password`. ### Macros #### `FIO_URL_QUERY_EACH` ```c #define FIO_URL_QUERY_EACH(query_buf, i) \ for (fio_url_query_each_s i = fio_url_query_each_next( \ (fio_url_query_each_s){.private___ = (query_buf)}); \ i.name.buf; \ i = fio_url_query_each_next(i)) ``` Iterates over query parameters. The iterator does not URL-decode names or values. ### Helper Functions #### `fio_url_query_each_next` ```c FIO_SFUNC fio_url_query_each_s fio_url_query_each_next(fio_url_query_each_s i); ``` Advances the query iterator. Usually called only through `FIO_URL_QUERY_EACH`. ### Example ```c #define FIO_URL #include "fio-stl.h" int main(void) { const char *s = "https://user:pass@example.com:8443/path?k=v#section"; fio_url_s u = fio_url_parse(s, strlen(s)); printf("scheme: %.*s\n", (int)u.scheme.len, u.scheme.buf); printf("host: %.*s\n", (int)u.host.len, u.host.buf); printf("port: %.*s\n", (int)u.port.len, u.port.buf); printf("path: %.*s\n", (int)u.path.len, u.path.buf); FIO_URL_QUERY_EACH(u.query, p) { printf("%.*s = %.*s\n", (int)p.name.len, p.name.buf, (int)p.value.len, p.value.buf); } fio_url_tls_info_s tls = fio_url_is_tls(u); printf("tls: %d\n", tls.tls); return 0; } ``` ------------------------------------------------------------ # ML Entity Decoding ```c #define FIO_ENTITY #include "fio-stl.h" ``` Decodes a single Markup Language entity (`&name;`, `&#digits;`, `&#xhex;`) into UTF-8. Handy when you need to unescape one entity at a time, for example while scanning HTML or XML text. ### API Functions #### `fio_entity` ```c SFUNC size_t fio_entity(char *dest, const char *src, size_t len); ``` Decodes the entity starting at `src` (length `len`). Writes the UTF-8 result to `dest`, which must be at least 8 bytes. A NUL terminator is written only if the result is shorter than 8 bytes. **Returns:** the number of bytes written (excluding NUL), or `0` if `src` does not start with a valid entity. Supported forms: - Named: `&name;` — case-insensitive, ~40 common entities such as `<`, `&`, `©`, `—`. - Decimal: `&#digits;` - Hex: `&#xhex;` or `&#Xhex;` `�` is replaced with the Unicode replacement character (`U+FFFD`). Code points above `0x10FFFF` are rejected. ### Example ```c #define FIO_ENTITY #include "fio-stl.h" int main(void) { const char *s = "© 2026 😀"; char buf[8]; size_t n = fio_entity(buf, s, strlen(s)); printf("decoded: %.*s\n", (int)n, buf); return 0; } ``` ------------------------------------------------------------ # File Helpers ```c #define FIO_FILES #include "fio-stl.h" ``` POSIX-style file helpers: open, read, write, remove, folder creation, size, type, path safety, filename parsing, and temporary files. Nothing fancy — just the usual paperwork. Implemented in [`./004 files.h`](https://facil.io/0.8.x/api/004-files/). ### Configuration Macros #### `FIO_FD_FIND_BLOCK` ```c #ifndef FIO_FD_FIND_BLOCK #define FIO_FD_FIND_BLOCK 4096 #endif ``` Stack buffer size used by `fio_fd_find_next` for each read cycle. Override before inclusion if you want larger or smaller scans. #### `FIO_FILENAME_PATH_CAPA` ```c #ifndef FIO_FILENAME_PATH_CAPA #define FIO_FILENAME_PATH_CAPA (PATH_MAX | 4094) #endif ``` Stack buffer capacity used for path manipulation (`"~/"` expansion in `fio_filename_open`, recursive `fio_filename_remove`, nested `fio_filename_make_path`). These functions allocate nothing — paths longer than this fail with `errno == ENAMETOOLONG`. The default guarantees at least ~4KB. ### Types #### `fio_filename_s` ```c typedef struct { fio_buf_info_s folder; fio_buf_info_s basename; fio_buf_info_s ext; } fio_filename_s; ``` Zero-copy result from `fio_filename_parse` / `fio_filename_parse2`. All members point into the original path string. **Members:** - `folder` - directory path including trailing separator, or `{NULL,0}` if none - `basename` - file name without extension, or `{NULL,0}` if empty - `ext` - extension without leading `.`, or `{NULL,0}` if none ### Macros #### `FIO_FOLDER_SEPARATOR` ```c #if FIO_OS_WIN #define FIO_FOLDER_SEPARATOR '\\' #else #define FIO_FOLDER_SEPARATOR '/' #endif ``` Native path separator. On Windows the parser also accepts `/`. #### `fio_file_dup` ```c #if FIO_OS_WIN #define fio_file_dup(fd) _dup(fd) #else #define fio_file_dup(fd) dup(fd) #endif ``` Duplicates a file descriptor. #### `fio_filename_is_folder` ```c #define fio_filename_is_folder(filename) \ (fio_filename_type((filename)) == S_IFDIR) ``` Returns non-zero if `filename` is a directory, `0` otherwise (including errors). #### `FIO_FD_FIND_EOF` ```c #define FIO_FD_FIND_EOF ((size_t)-1) ``` Sentinel returned by `fio_fd_find_next` when the token is not found before EOF. ### Opening and Creating Files #### `fio_filename_open` ```c SFUNC int fio_filename_open(const char *filename, int flags); ``` Opens `filename` with POSIX `open` semantics. If `filename` starts with `"~/"` (or `"~\\"` on Windows), it is resolved relative to `$HOME`. **Parameters:** - `filename` - path to open - `flags` - `O_RDONLY`, `O_RDWR | O_CREAT | O_TRUNC`, etc. **Returns:** file descriptor on success, `-1` on error. **Note:** uses a fixed size stack buffer (zero allocations); over-long `"~/..."` paths fail with `errno == ENAMETOOLONG` (see `FIO_FILENAME_PATH_CAPA`). #### `fio_filename_tmp` ```c SFUNC int fio_filename_tmp(void); ``` Creates a temporary file and returns its descriptor. Tries `TMPDIR`, `TMP`, `TEMP`, `P_tmpdir`, then the current directory. On Linux it attempts `O_TMPFILE` first; otherwise it builds a name prefixed with `facil_io_tmp_` and opens with `O_CREAT | O_EXCL`. **Returns:** file descriptor on success, `-1` on error. #### `fio_filename_overwrite` ```c FIO_IFUNC int fio_filename_overwrite(const char *filename, const void *buf, size_t len); ``` Opens `filename` with `O_RDWR | O_CREAT | O_TRUNC`, writes `len` bytes from `buf`, and closes the file. **Returns:** `0` on success, `-1` on error. On error the file state is undefined. ### Removing Files and Folders #### `fio_filename_remove` ```c typedef struct { const char *path; uint8_t folder; uint8_t recursive; } fio_filename_remove_args_s; SFUNC int fio_filename_remove(fio_filename_remove_args_s args); #define fio_filename_remove(...) \ fio_filename_remove((fio_filename_remove_args_s){__VA_ARGS__}) ``` Removes the file / link or folder at `path`, abstracting OS differences (`unlink` / `rmdir` vs. `DeleteFileA` / `RemoveDirectoryA`). **Parameters:** - `path` - NUL-terminated path of the file / folder to remove - `folder` - set to allow removal of an empty folder (like `rmdir`) - `recursive` - set to remove a folder and all of its content (like `rm -r`); implies `folder`. If `path` isn't a folder, the flag is ignored and `path` is removed as a file / link **Returns:** `0` on success, `-1` on error. Recursive removal stops on the first error (some content may remain). Uses a fixed size stack buffer (zero allocations); over-long paths fail with `errno == ENAMETOOLONG` (see `FIO_FILENAME_PATH_CAPA`). **Note:** links (symlinks / reparse points) are removed, never followed into. **Example:** ```c fio_filename_remove(.path = "old.log"); fio_filename_remove(.path = "empty_dir", .folder = 1); fio_filename_remove(.path = "build_tree", .recursive = 1); ``` ### Creating Folders #### `fio_filename_make_path` ```c typedef struct { const char *path; uint32_t mode; } fio_filename_make_path_args_s; SFUNC int fio_filename_make_path(fio_filename_make_path_args_s args); #define fio_filename_make_path(...) \ fio_filename_make_path((fio_filename_make_path_args_s){__VA_ARGS__}) ``` Creates the folder at `path`, including any missing parent folders (similar to `mkdir -p`). Abstracts OS differences (`mkdir` vs. `CreateDirectoryA`). **Parameters:** - `path` - NUL-terminated folder path to create; nested folders are created as needed, trailing separators are allowed - `mode` - creation mode (POSIX only, ignored on Windows); zero defaults to `0755` **Returns:** `0` on success, `-1` on error. An existing folder is NOT an error. A non-folder component along the way IS an error (`errno == ENOTDIR`). Uses a fixed size stack buffer (zero allocations); over-long paths fail with `errno == ENAMETOOLONG` (see `FIO_FILENAME_PATH_CAPA`). **Note:** root / drive / UNC `\\server\share` prefixes are never created, only the components after them. **Example:** ```c if (fio_filename_make_path(.path = "./build/out/artifacts")) perror("mkdir -p"); ``` ### Reading and Writing #### `fio_fd_write` ```c FIO_IFUNC ssize_t fio_fd_write(int fd, const void *buf, size_t len); ``` Writes `len` bytes to `fd`, fragmenting the syscall into `write` blocks of up to `1 << 17` bytes. Retries on `EINTR`. **Returns:** number of bytes written, or `-1` on error. If `fd == -1`, `buf == NULL`, or `len == 0`, returns `-1`. **Note:** for non-blocking descriptors, check `errno` for `EAGAIN` / `EWOULDBLOCK`. #### `fio_fd_read` ```c FIO_IFUNC size_t fio_fd_read(int fd, void *buf, size_t len, off_t start_at); ``` Reads up to `len` bytes from `fd` starting at `start_at`. On POSIX systems with `pread`, the file offset is preserved; otherwise it seeks. Reads are fragmented to `1 << 27` byte chunks. Retries on `EINTR`. **Parameters:** - `fd` - file descriptor - `buf` - destination buffer - `len` - maximum bytes to read - `start_at` - offset; negative values seek from end **Returns:** bytes read, or `0` on error / EOF. Sets `errno = ENOENT` if `fd == -1`, `buf == NULL`, or `len == 0`. ### File Information #### `fio_filename_size` ```c FIO_IFUNC size_t fio_filename_size(const char *filename); ``` Returns file size in bytes, or `0` on error or empty file. #### `fio_fd_size` ```c FIO_IFUNC size_t fio_fd_size(int fd); ``` Returns file size for an open descriptor, or `0` on error or empty file. #### `fio_filename_type` ```c FIO_IFUNC size_t fio_filename_type(const char *filename); ``` Returns the file type bits (`st_mode & S_IFMT`), e.g. `S_IFREG`, `S_IFDIR`, or `0` on error. #### `fio_fd_type` ```c FIO_IFUNC size_t fio_fd_type(int fd); ``` Same as `fio_filename_type`, but for an open descriptor. #### `fio_filename_stat` ```c FIO_IFUNC int fio_filename_stat(const char *filename, struct stat *stat_buf); ``` Populates `stat_buf` via `stat`. Returns `0` on success, `-1` on error (including `filename == NULL` or `stat_buf == NULL`). ### Path Safety #### `fio_filename_is_unsafe` ```c SFUNC int fio_filename_is_unsafe(const char *path); ``` Returns `1` if `path` possibly folds backwards or contains double separators: - leading `../` - `/../` or trailing `/..` - `//` Uses the OS separator (`\` on Windows, `/` elsewhere). Returns `0` for `NULL` paths. #### `fio_filename_is_unsafe_url` ```c SFUNC int fio_filename_is_unsafe_url(const char *path); ``` Same check as `fio_filename_is_unsafe`, but always uses `/` as the separator. Use this for URL paths. ### Filename Parsing #### `fio_filename_parse` ```c SFUNC fio_filename_s fio_filename_parse(const char *filename); ``` Splits a NUL-terminated path into folder, basename, and extension without copying. **Returns:** `fio_filename_s` with pointers into `filename`. #### `fio_filename_parse2` ```c SFUNC fio_filename_s fio_filename_parse2(const char *filename, size_t len); ``` Same as `fio_filename_parse`, but reads at most `len` bytes. Use when the input is not NUL-terminated. ### File Search #### `fio_fd_find_next` ```c SFUNC size_t fio_fd_find_next(int fd, char token, size_t start_at); ``` Scans `fd` for the next occurrence of `token` starting at `start_at`. Reads in `FIO_FD_FIND_BLOCK` byte chunks. **Returns:** byte offset of the match, or `FIO_FD_FIND_EOF` if not found. ### Example ```c #define FIO_FILES #define FIO_LOG #include "fio-stl.h" int main(void) { fio_filename_s parts = fio_filename_parse("/var/log/app.log"); printf("folder=%.*s base=%.*s ext=%.*s\n", (int)parts.folder.len, parts.folder.buf, (int)parts.basename.len, parts.basename.buf, (int)parts.ext.len, parts.ext.buf); const char *unsafe = "/var/www/../etc/passwd"; printf("unsafe? %s\n", fio_filename_is_unsafe(unsafe) ? "yes" : "no"); const char *msg = "hello, files"; if (fio_filename_overwrite("/tmp/fio_test.txt", msg, strlen(msg)) == 0) { printf("wrote %zu bytes\n", strlen(msg)); } size_t sz = fio_filename_size("/tmp/fio_test.txt"); printf("size = %zu\n", sz); int tmp = fio_filename_tmp(); if (tmp != -1) { fio_fd_write(tmp, "temp", 4); close(tmp); } return 0; } ``` ------------------------------------------------------------ # HTTP/1.x Parser ```c #define FIO_HTTP1_PARSER #include FIO_INCLUDE_FILE ``` Small parser, sharp teeth. Define `FIO_HTTP1_PARSER` to add the static HTTP/1.x request / response parser used by the HTTP layer. It performs no heap allocations, stores only parser state, and reports parsed data through callbacks implemented by the including translation unit. Nearby context: [IO and HTTP overview](./400 io-overview.md), the higher-level [HTTP module header](./439 http.h), and the neighboring [WebSocket parser header](./004 websocket parser.h). --- ## What Gets Added `FIO_HTTP1_PARSER` exposes: - `fio_http1_parser_s` — parser state. - `FIO_HTTP1_PARSER_INIT` — zero-initializer / reset value. - `fio_http1_parse` — incremental parser entry point. - parser state helpers: - `fio_http1_parser_is_empty` - `fio_http1_parser_is_on_header` - `fio_http1_parser_is_on_body` - `fio_http1_expected` - `fio_http1_parser_skip_body` - `fio_http1_parser_skips_body` - parse result / expected-body constants: - `FIO_HTTP1_PARSER_ERROR` - `FIO_HTTP1_EXPECTED_CHUNKED` - required user callbacks named `fio_http1_on_*`. The implementation also declares internal parsing stages named with `fio_http1___...`; these are private implementation details. --- ## Parser State ### `fio_http1_parser_s` ```c typedef struct fio_http1_parser_s fio_http1_parser_s; struct fio_http1_parser_s { int (*fn)(fio_http1_parser_s *, fio_buf_info_s *, void *); size_t expected; size_t flags; }; ``` The parser state is intentionally tiny: one function pointer for the current state-machine stage, one `expected` byte counter / sentinel, and a `flags` word for per-message state bits (e.g., an accepted `Expect: 100-continue`). Parser state is never inferred from function pointer identity, so linker identical-code-folding (MSVC / lld `/OPT:ICF`) cannot alter parsing behavior. Treat all fields as opaque. Allocate the struct wherever it fits your lifetime (stack, connection object, arena, etc.), initialize it with `FIO_HTTP1_PARSER_INIT`, and use the helper functions to inspect state. ### `FIO_HTTP1_PARSER_INIT` ```c #define FIO_HTTP1_PARSER_INIT ((fio_http1_parser_s){0}) ``` Zero-initializes a parser: ```c fio_http1_parser_s parser = FIO_HTTP1_PARSER_INIT; ``` The parser also resets itself to this empty state after a complete message is reported with `fio_http1_on_complete`. --- ## Parsing API ### `fio_http1_parse` ```c FIO_SFUNC size_t fio_http1_parse(fio_http1_parser_s *p, fio_buf_info_s buf, void *udata); ``` Parses as much HTTP/1.x data as currently possible and invokes callbacks as fields are discovered. - `p` is the parser state. - `buf` is the current readable bytes. - `udata` is passed unchanged to every callback. Returns the number of bytes consumed from `buf`, or `FIO_HTTP1_PARSER_ERROR` (`(size_t)-1`) on parse / callback error. A successful return may consume fewer bytes than supplied. Any unconsumed bytes belong to a later parse step or to the next HTTP message on a keep-alive connection. If the parser needs more bytes before it can make progress, it returns successfully with the bytes consumed so far, which can be `0`. ### State Helpers ```c FIO_IFUNC size_t fio_http1_parser_is_empty(fio_http1_parser_s *p); FIO_IFUNC size_t fio_http1_parser_is_on_header(fio_http1_parser_s *p); FIO_IFUNC size_t fio_http1_parser_is_on_body(fio_http1_parser_s *p); FIO_IFUNC size_t fio_http1_expected(fio_http1_parser_s *p); FIO_IFUNC void fio_http1_parser_skip_body(fio_http1_parser_s *p); FIO_IFUNC size_t fio_http1_parser_skips_body(fio_http1_parser_s *p); FIO_IFUNC int fio_http1_version_is_legacy(fio_buf_info_s version); ``` - `fio_http1_parser_is_empty` returns non-zero when the parser is waiting for a new request / response line. - `fio_http1_parser_is_on_header` returns non-zero while reading regular headers or chunked trailer headers. - `fio_http1_parser_is_on_body` returns non-zero while reading a known-length body or while the chunked parser is ready to read the next chunk frame. During a split chunk payload, the internal chunk-read stage may report false even though body bytes are still being drained. - `fio_http1_expected` returns the parser's current expected byte count, returns `FIO_HTTP1_EXPECTED_CHUNKED` after a chunked body is detected and before the next chunk-size line is parsed, and returns `0` when the parser's internal state marks the message as having no body. During chunked payload delivery, it may expose the current chunk size / remaining chunk bytes. - `fio_http1_parser_skip_body` marks the current message as having no body, regardless of any `content-length` / `transfer-encoding` headers. Call it from a parser callback before the headers end (normally `on_status`), e.g., when parsing the response to a `HEAD` request — the parser can't know the request method. The mark is cleared when the message completes. See *Messages Without a Body* below. - `fio_http1_parser_skips_body` returns non-zero when the current message has no body. It is known as soon as the first line is parsed (before any header callback). Test it before reserving body space. See *Messages Without a Body*. - `fio_http1_version_is_legacy` returns non-zero if a version string (as passed to `fio_http1_on_version`) is exactly `HTTP/1.0` (case-sensitive). HTTP/1.0 messages are not persistent without a `keep-alive` connection option and never use chunked transfer coding (RFC 9112 §9.3, §6.1). Every other version string gets HTTP/1.1 semantics. HTTP/0.9 is unsupported: its version-less request line is rejected, and a `HTTP/0.9` version string is treated as HTTP/1.1 (i.e., `host` is required). ### Constants ```c #define FIO_HTTP1_PARSER_ERROR ((size_t)-1) #define FIO_HTTP1_EXPECTED_CHUNKED ((size_t)(-2)) ``` `FIO_HTTP1_PARSER_ERROR` is the error return value from `fio_http1_parse`. After a parse error, close / discard the stream state rather than attempting to recover the same parser instance. `FIO_HTTP1_EXPECTED_CHUNKED` is the parser's sentinel after `transfer-encoding: chunked` is accepted and before a chunk-size line is parsed. Once chunk parsing starts, `fio_http1_expected` may instead report the current chunk size or remaining chunk bytes. The header also defines `FIO___HTTP1_BODY_NOT_ALLOWED` as an internal sentinel for methods / states where a body should not be read. User code should rely on `fio_http1_expected(p) == 0` instead of using that internal macro. --- ## Callback Contract The parser declares these callbacks as `static` prototypes. The including translation unit must define them. For portable user code, every `int` callback should return only `0` to continue or `-1` to reject the parse. The parser's internal checks are not identical for every callback: - `fio_http1_on_method`, `fio_http1_on_url`, `fio_http1_on_version`, `fio_http1_on_status`, and `fio_http1_on_body_chunk` treat any non-zero return as a parse error. - `fio_http1_on_header` during normal headers and `fio_http1_on_header_content_length` reject only an exact `-1` return. - `fio_http1_on_header` during chunked trailers is passed through; negative values become parse errors and positive values stop the current parse as incomplete. - `fio_http1_on_expect` is special: any non-zero return rejects the expectation, resets the parser, and stops the current parse without calling `fio_http1_on_complete`. All `fio_buf_info_s` values point into the `buf` memory passed to `fio_http1_parse`. They are not NUL-terminated unless the input happened to be. Copy or retain the data before the callback returns if it must outlive the input buffer. > Important: the parser lowercases header names in-place. Feed it writable > memory, not a string literal or read-only mapping. ### Completion ```c static void fio_http1_on_complete(void *udata); ``` Called after the request / response line, headers, and any body have been fully parsed. The parser is reset before this callback is invoked, so it is ready for the next message on the same connection. ### Request Line Callbacks ```c static int fio_http1_on_method(fio_buf_info_s method, void *udata); static int fio_http1_on_url(fio_buf_info_s path, void *udata); static int fio_http1_on_version(fio_buf_info_s version, void *udata); ``` For request lines, the parser calls the callbacks in this order: 1. `fio_http1_on_method` 2. `fio_http1_on_url` 3. `fio_http1_on_version` The version slice is clamped to at most 14 bytes. `GET`, `HEAD`, and `OPTIONS` are recognized case-insensitively and mark the parser as not expecting a body; body-bearing headers for these methods conflict with that marker and are rejected. ### Response Line Callbacks ```c static int fio_http1_on_version(fio_buf_info_s version, void *udata); static int fio_http1_on_status(size_t istatus, fio_buf_info_s status, void *udata); ``` For response lines, the parser calls `fio_http1_on_version` first and then `fio_http1_on_status`. `istatus` is parsed from the numeric status token. `status` is the remaining status text slice after the numeric token. The version slice is clamped to at most 14 bytes. The parser decides whether the first line is a response by checking whether the second token starts with a decimal digit. Responses with a `1xx`, `204`, or `304` status are automatically marked as having no body (see *Messages Without a Body*). The parser does not skip interim `1xx` responses: each one completes as a separate message, and callers decide whether to wait for the final response. ### Headers ```c static int fio_http1_on_header(fio_buf_info_s name, fio_buf_info_s value, void *udata); static int fio_http1_on_header_content_length(fio_buf_info_s name, fio_buf_info_s value, size_t content_length, void *udata); ``` For ordinary headers, `fio_http1_on_header` receives: - `name` lowercased in-place. - `value` trimmed of leading and trailing spaces / tabs. - an empty value as `{ .buf = NULL, .len = 0 }`. Header names must contain a valid `:` separator and may not contain the forbidden characters encoded by the parser. NUL bytes in header values are rejected. `content-length` is special: - empty values are rejected; - non-decimal / overflowing values are rejected; - values colliding with internal sentinels are rejected; - duplicate `content-length` headers must agree; - conflicting `content-length` and final `transfer-encoding: chunked` are rejected; - the first non-zero accepted value calls `fio_http1_on_header_content_length` instead of the generic header callback; a repeated matching value is accepted without calling the content-length callback again. A `content-length: 0` value marks the message as having no body and does not call `fio_http1_on_header_content_length`. `transfer-encoding` is also special. Its field lines form a single coding list (RFC 9110 §5.3), and `chunked` (case-insensitive) must be the final coding, applied exactly once (RFC 9112 §6.3, §7). Anything else can't be framed safely, so it's rejected rather than reading the body as the next message (request smuggling): - the parser switches to chunked body decoding when a field line ends with the `chunked` token; - if the value is exactly `chunked`, no generic header callback is made; - if other transfer-coding text appears before the final `chunked` token, the final `chunked` token and adjacent separators are stripped before the remaining value is passed to `fio_http1_on_header` (i.e., `gzip, chunked` is framed as chunked and forwards `transfer-encoding: gzip`; decoding it is up to the parser user, since a `501` response is only a SHOULD under RFC 9112 §6.1); - a field line without a final `chunked` token (i.e., `gzip`) is forwarded unchanged; a later field line may still end the list with `chunked`; - rejected: a list that doesn't end with `chunked` once the headers end (i.e., `gzip`, `identity`, `chunked, gzip`, an empty value, or a `chunked` line followed by any other `transfer-encoding` line), a repeated `chunked` coding (`chunked, chunked`), malformed separators before the final `chunked` token, a `content-length` header, and requests using `GET`, `HEAD`, or `OPTIONS`. Requests must carry exactly one `host` header, as required by RFC 9112 §3.2: - an HTTP/1.1 request without a `host` header is rejected once the headers end; - a request with more than one `host` header line is rejected (for any version); - `HTTP/1.0` requests may omit `host` (see `fio_http1_version_is_legacy`); - responses aren't affected. The `host` header itself is passed to `fio_http1_on_header`. `expect` is special when its value is exactly `100-continue`. Any other `Expect` value is rejected. ### Messages Without a Body The parser identifies bodyless messages from the first line, before any header callback runs, and reports them through `fio_http1_parser_skips_body`: - requests using `GET`, `HEAD`, or `OPTIONS` (a non-zero `content-length` on these is still rejected, as described above); - responses with a `1xx` / `204` / `304` status (RFC 9112 §6.3); - messages marked with `fio_http1_parser_skip_body` (i.e., a `HEAD` response, marked from `on_status`). `fio_http1_parser_skips_body` is also true after a `content-length: 0` header. A bodyless response completes at the empty line after its headers, and any bytes that follow belong to the next message. Body framing is ignored: - `content-length` is still validated and reported to `fio_http1_on_header_content_length`, but it is informational. Callbacks must test `fio_http1_parser_skips_body` before reserving space or enforcing body size limits (a `HEAD` response may describe a large body that never arrives); - `transfer-encoding` and `expect` are forwarded unchanged to `fio_http1_on_header`. ### Expect: 100-continue ```c static int fio_http1_on_expect(void *udata); ``` Called once, after headers, when an accepted `Expect: 100-continue` header requires a post-header decision and a body may follow (a `Content-Length` body or `transfer-encoding: chunked`). It is not called for messages that cannot carry a body (e.g., `GET` / `HEAD` / `OPTIONS` or a zero `Content-Length`), since there is nothing to continue (RFC 9110 §10.1.1). Return `0` to continue into the body / completion flow. Return non-zero to reset the parser and stop the current parse without calling `fio_http1_on_complete`. ### Body Chunks ```c static int fio_http1_on_body_chunk(fio_buf_info_s chunk, void *udata); ``` Called with decoded body bytes. For `Content-Length` bodies, callback chunks follow the supplied input chunks and sum to the accepted content length. For chunked bodies, framing bytes are removed before callback delivery. Large or split HTTP chunks may be delivered through more than one callback if the input arrives in smaller pieces. --- ## Parser Flow ```text start line ├─ request -> method -> url -> version └─ response -> version -> status headers ├─ no body / body not allowed -> complete (incl. 1xx/204/304, skip_body) ├─ content-length body -> body chunks -> complete └─ chunked body -> chunk chunks -> trailers -> complete ``` Details worth keeping in mind: - Leading spaces, `\r`, and `\n` before the first line are skipped. - First lines shorter than the parser's minimum accepted shape are rejected. - NUL bytes in the first line are rejected. - Header and first-line parsing waits for a newline before making progress. - The parser accepts `\n` line endings and handles an optional preceding `\r`. - Chunk size lines are hexadecimal, capped by the implementation, and do not support chunk extensions. - A zero-size chunk either completes immediately when followed by an empty line or enters trailer parsing. --- ## Chunked Trailers Allowed trailer headers are reported through `fio_http1_on_header` after the body's terminating zero-size chunk. The parser rejects the following trailer names: - `authorization` - `cache-control` - `content-encoding` - `content-length` - `content-range` - `content-type` - `expect` - `host` - `max-forwards` - `set-cookie` - `te` - `trailer` - `transfer-encoding` --- ## Ownership and Lifetime - The parser allocates no memory. - The parser does not copy callback data. - The parser mutates header names in the input buffer to lowercase. - Callback slices are valid only while the input buffer remains valid and unchanged. - `udata` is never owned by the parser; it is simply forwarded. - The parser state may live inside a connection object and be reused for keep-alive messages. It resets automatically on complete messages. - After `FIO_HTTP1_PARSER_ERROR`, discard the parser / connection state. --- ## Minimal Skeleton ```c #define FIO_HTTP1_PARSER #include FIO_INCLUDE_FILE static int fio_http1_on_method(fio_buf_info_s method, void *udata) { (void)method; (void)udata; return 0; } static int fio_http1_on_url(fio_buf_info_s path, void *udata) { (void)path; (void)udata; return 0; } static int fio_http1_on_version(fio_buf_info_s version, void *udata) { (void)version; (void)udata; return 0; } static int fio_http1_on_status(size_t status_code, fio_buf_info_s status, void *udata) { (void)status_code; (void)status; (void)udata; return 0; } static int fio_http1_on_header(fio_buf_info_s name, fio_buf_info_s value, void *udata) { (void)name; (void)value; (void)udata; return 0; } static int fio_http1_on_header_content_length(fio_buf_info_s name, fio_buf_info_s value, size_t content_length, void *udata) { (void)name; (void)value; (void)udata; return content_length > (1UL << 20) ? -1 : 0; } static int fio_http1_on_expect(void *udata) { (void)udata; return 0; } static int fio_http1_on_body_chunk(fio_buf_info_s chunk, void *udata) { (void)chunk; (void)udata; return 0; } static void fio_http1_on_complete(void *udata) { (void)udata; } size_t parse_some_http(char *data, size_t len, void *udata) { fio_http1_parser_s parser = FIO_HTTP1_PARSER_INIT; return fio_http1_parse(&parser, FIO_BUF_INFO2(data, len), udata); } ``` For real incremental parsing, keep `fio_http1_parser_s` with the connection and preserve / retry unconsumed bytes when `fio_http1_parse` returns less than the available buffer length. # JSON Parser ```c #define FIO_JSON #include "fio-stl.h" ``` A non-strict, streaming, callback-based JSON parser. It does not build a tree for you; it calls your callbacks for each value and lets you decide what to construct. Implemented in [`./004 json.h`](https://facil.io/0.8.x/api/004-json/). Depends on `FIO_ATOL`, which is included automatically. The parser tolerates trailing commas, comments (`//`, `/* */`, `#`), newlines inside strings, hex/octal/binary numbers, `NaN`, and `Infinity`. ### Configuration Macros #### `FIO_JSON_MAX_DEPTH` ```c #ifndef FIO_JSON_MAX_DEPTH #define FIO_JSON_MAX_DEPTH 128 #endif ``` Maximum JSON nesting depth. Must be less than `65536`. Deeper input aborts with an error. #### `FIO_JSON_USE_FIO_ATON` ```c #ifndef FIO_JSON_USE_FIO_ATON #define FIO_JSON_USE_FIO_ATON 0 #endif ``` Set to `1` to use `fio_aton` for number parsing instead of the built-in logic. ### Types #### `fio_json_parser_callbacks_s` ```c typedef struct { void *(*on_null)(void *udata); void *(*on_true)(void *udata); void *(*on_false)(void *udata); void *(*on_number)(void *udata, int64_t i); void *(*on_float)(void *udata, double f); void *(*on_string)(void *udata, const void *start, size_t len); void *(*on_string_simple)(void *udata, const void *start, size_t len); void *(*on_map)(void *udata, void *ctx, void *at); void *(*on_array)(void *udata, void *ctx, void *at); int (*map_push)(void *udata, void *ctx, void *key, void *value); int (*array_push)(void *udata, void *ctx, void *value); int (*array_finished)(void *udata, void *ctx); int (*map_finished)(void *udata, void *ctx); int (*is_array)(void *udata, void *ctx); int (*is_map)(void *udata, void *ctx); void (*free_unused_object)(void *udata, void *ctx); void *(*on_error)(void *udata, void *ctx); } fio_json_parser_callbacks_s; ``` Callback table for the parser. All callbacks receive `udata` as their first argument, so the same `static const` table can be shared across concurrent calls. **Required callbacks:** `on_null`, `on_true`, `on_false`, `on_number`, `on_float`, `on_string` (or `on_string_simple`), `on_map`, `on_array`, `map_push`, `array_push`, `free_unused_object`. **Optional callbacks:** `on_string_simple`, `array_finished`, `map_finished`, `is_array`, `is_map`, `on_error`. Missing optional callbacks default to no-ops. If only one of `on_string` / `on_string_simple` is provided, the other uses it as a fallback. **Ownership:** callbacks that return `void *` transfer ownership to the parser. The parser passes objects to `map_push` / `array_push` or calls `free_unused_object` for discarded objects. `on_error` receives ownership of any partial result. #### `fio_json_result_s` ```c typedef struct { void *ctx; size_t stop_pos; int err; } fio_json_result_s; ``` Parse result. **Members:** - `ctx` - root object returned by callbacks, or `NULL` - `stop_pos` - number of bytes consumed from the input - `err` - non-zero if parsing stopped because of an error ### API Functions #### `fio_json_parse` ```c SFUNC fio_json_result_s fio_json_parse(fio_json_parser_callbacks_s *settings, void *udata, const char *json_string, const size_t len); ``` Parses `json_string` up to `len` bytes. Stops as soon as a complete top-level value is consumed or an error occurs. **Parameters:** - `settings` - callback table - `udata` - per-call user data threaded through every callback - `json_string` - input buffer - `len` - input length in bytes **Returns:** `fio_json_result_s` with the root context, bytes consumed, and error flag. **Note:** a UTF-8 BOM at the start of the buffer is skipped automatically. **Guard-byte contract:** number and quote-less key scanning may read the byte at `json_string[len]` while deciding a token ended (see the `fio_atol*` guard-byte contract). Callers MUST keep that byte readable and non-numeric - pass a NUL-terminated string (any `fio_bstr` string qualifies) or append a non-numeric guard byte. Exact-size buffers with no readable byte past `len` violate the contract and will trigger AddressSanitizer. #### `fio_json_parse_update` ```c SFUNC fio_json_result_s fio_json_parse_update(fio_json_parser_callbacks_s *s, void *udata, void *ctx, const char *start, const size_t len); ``` Merges JSON data into an existing object (`ctx`). The input must be wrapped in an aggregate of the same type as `ctx`. Requires `is_array` and `is_map` callbacks. **Parameters:** - `s` - callback table - `udata` - per-call user data - `ctx` - existing object to update - `start` - input buffer - `len` - input length **Returns:** parse result with the (possibly updated) context. ### Example ```c #define FIO_JSON #define FIO_LOG #include "fio-stl.h" typedef enum { T_NULL, T_TRUE, T_FALSE, T_NUM, T_STR, T_ARR, T_MAP } type_e; typedef struct obj_s { type_e type; union { int64_t i; struct { char *buf; size_t len; } s; struct { struct obj_s **items; size_t len; } a; struct { struct obj_s **keys; struct obj_s **vals; size_t len; } m; }; } obj_s; static obj_s *mk(type_e t) { obj_s *o = calloc(1, sizeof(*o)); o->type = t; return o; } static void *on_null(void *u) { (void)u; return mk(T_NULL); } static void *on_true(void *u) { (void)u; return mk(T_TRUE); } static void *on_false(void *u) { (void)u; return mk(T_FALSE); } static void *on_number(void *u, int64_t i) { (void)u; obj_s *o = mk(T_NUM); o->i = i; return o; } static void *on_string(void *u, const void *s, size_t l) { (void)u; obj_s *o = mk(T_STR); o->s.buf = malloc(l+1); memcpy(o->s.buf, s, l); o->s.buf[l] = 0; o->s.len = l; return o; } static void *on_array(void *u, void *c, void *a) { (void)u; (void)c; (void)a; return mk(T_ARR); } static void *on_map(void *u, void *c, void *a) { (void)u; (void)c; (void)a; return mk(T_MAP); } static int array_push(void *u, void *c, void *v) { (void)u; obj_s *a = c; a->a.items = realloc(a->a.items, (a->a.len+1)*sizeof(*a->a.items)); a->a.items[a->a.len++] = v; return 0; } static int map_push(void *u, void *c, void *k, void *v) { (void)u; obj_s *m = c; m->m.keys = realloc(m->m.keys, (m->m.len+1)*sizeof(*m->m.keys)); m->m.vals = realloc(m->m.vals, (m->m.len+1)*sizeof(*m->m.vals)); m->m.keys[m->m.len] = k; m->m.vals[m->m.len] = v; m->m.len++; return 0; } static void free_obj(void *u, void *c) { (void)u; free(c); } int main(void) { static const fio_json_parser_callbacks_s cb = { .on_null = on_null, .on_true = on_true, .on_false = on_false, .on_number = on_number, .on_string = on_string, .on_string_simple = on_string, .on_array = on_array, .on_map = on_map, .array_push = array_push, .map_push = map_push, .free_unused_object = free_obj, }; const char *json = "{ \"n\": 42, \"s\": \"hi\", \"a\": [1, 2] }"; fio_json_result_s r = fio_json_parse(&cb, NULL, json, strlen(json)); if (r.err) { FIO_LOG_ERROR("parse error at %zu", r.stop_pos); return 1; } printf("parsed %zu bytes, root ctx %p\n", r.stop_pos, r.ctx); return 0; } ``` ------------------------------------------------------------ # MIME Multipart Parser ```c #define FIO_MULTIPART #include "fio-stl.h" ``` A non-allocating, streaming, callback-based `multipart/form-data` parser. It finds parts, extracts `Content-Disposition` and `Content-Type`, then calls your callbacks for each field or file chunk. Implemented in [`./004 multipart.h`](https://facil.io/0.8.x/api/004-multipart/). The parser does not decode transfer encodings or unescape headers — it just slices the original buffer into `fio_buf_info_s` ranges. ### Types #### `fio_multipart_parser_callbacks_s` ```c typedef struct { void *(*on_field)(void *udata, fio_buf_info_s name, fio_buf_info_s value, fio_buf_info_s content_type); void *(*on_field_start)(void *udata, fio_buf_info_s name, fio_buf_info_s content_type); int (*on_field_data)(void *udata, void *field_ctx, fio_buf_info_s data); void (*on_field_end)(void *udata, void *field_ctx); void *(*on_file_start)(void *udata, fio_buf_info_s name, fio_buf_info_s filename, fio_buf_info_s content_type); int (*on_file_data)(void *udata, void *file_ctx, fio_buf_info_s data); void (*on_file_end)(void *udata, void *file_ctx); void (*on_error)(void *udata); } fio_multipart_parser_callbacks_s; ``` Callback table. All callbacks are optional; unset ones become no-ops. **Members:** - `on_field` - called for regular form fields (no `filename`). Ignored if `on_field_start` is provided. - `on_field_start` - optional streaming field start. If set, `on_field` is ignored and `on_field_data` / `on_field_end` are used instead. - `on_field_data` - called with a field data chunk. Return non-zero to abort. - `on_field_end` - called when a streaming field ends. - `on_file_start` - called when a file upload starts. Returns a context pointer for this file. - `on_file_data` - called with a file data chunk. Return non-zero to abort. - `on_file_end` - called when a file upload ends. - `on_error` - called on parse error (optional). #### `fio_multipart_result_s` ```c typedef struct { size_t consumed; size_t field_count; size_t file_count; int err; } fio_multipart_result_s; ``` Parse result. **Members:** - `consumed` - bytes consumed from the input buffer - `field_count` - regular form fields parsed in this call - `file_count` - file uploads parsed in this call - `err` - `0` success, `-1` error, `-2` need more data ### API Functions #### `fio_multipart_parse` ```c SFUNC fio_multipart_result_s fio_multipart_parse(const fio_multipart_parser_callbacks_s *callbacks, void *udata, fio_buf_info_s boundary, const char *data, size_t len); ``` Parses `data` as MIME multipart input. `boundary` is the boundary string **without** the leading `"--"` and must not exceed 250 bytes. **Parameters:** - `callbacks` - callback table (typically `static const`) - `udata` - user data passed to every callback - `boundary` - boundary value from the `Content-Type` header - `data` - input buffer - `len` - input length in bytes **Returns:** parse result. **Streaming behavior:** - `err == 0` - parsing complete; closing boundary found. - `err == -2` - need more data. Keep `data + result.consumed ... data + len` and append new data before calling again. - `err == -1` - unrecoverable parse error or callback abort. ### Examples #### Basic field and file parsing ```c #define FIO_MULTIPART #include "fio-stl.h" static void *on_field(void *u, fio_buf_info_s n, fio_buf_info_s v, fio_buf_info_s ct) { (void)u; (void)ct; printf("field %.*s = %.*s\n", (int)n.len, n.buf, (int)v.len, v.buf); return NULL; } static void *on_file_start(void *u, fio_buf_info_s n, fio_buf_info_s fn, fio_buf_info_s ct) { (void)u; (void)ct; printf("file %.*s / %.*s\n", (int)n.len, n.buf, (int)fn.len, fn.buf); return NULL; } static int on_file_data(void *u, void *c, fio_buf_info_s d) { (void)u; (void)c; printf(" chunk %zu bytes\n", d.len); return 0; } static void on_file_end(void *u, void *c) { (void)u; (void)c; } int main(void) { static const fio_multipart_parser_callbacks_s cb = { .on_field = on_field, .on_file_start = on_file_start, .on_file_data = on_file_data, .on_file_end = on_file_end, }; const char *body = "--B\r\n" "Content-Disposition: form-data; name=\"x\"\r\n\r\n" "hello\r\n" "--B\r\n" "Content-Disposition: form-data; name=\"f\"; filename=\"t.txt\"\r\n" "Content-Type: text/plain\r\n\r\n" "data\r\n" "--B--\r\n"; fio_multipart_result_s r = fio_multipart_parse( &cb, NULL, FIO_BUF_INFO2("B", 1), body, strlen(body)); printf("fields=%zu files=%zu err=%d\n", r.field_count, r.file_count, r.err); return 0; } ``` #### Streaming field callbacks ```c static void *field_start(void *u, fio_buf_info_s n, fio_buf_info_s ct) { (void)u; (void)ct; printf("field start %.*s\n", (int)n.len, n.buf); return NULL; } static int field_data(void *u, void *c, fio_buf_info_s d) { (void)u; (void)c; printf(" field chunk %zu bytes\n", d.len); return 0; } static void field_end(void *u, void *c) { (void)u; (void)c; } static const fio_multipart_parser_callbacks_s cb_stream = { .on_field_start = field_start, .on_field_data = field_data, .on_field_end = field_end, }; ``` ------------------------------------------------------------ # RESP3 Parser ```c #define FIO_RESP3 #include "fio-stl.h" ``` A streaming, callback-based [RESP3](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md) parser for Redis 6+ and compatible servers. Implemented in [`./004 resp3.h`](https://facil.io/0.8.x/api/004-resp3/). Depends on `FIO_ATOL`, included automatically. The parser pushes values through a small internal stack, calling your callbacks for primitives, containers, and streaming strings. It does not allocate on its own. ### Configuration Macros #### `FIO_RESP3_MAX_NESTING` ```c #ifndef FIO_RESP3_MAX_NESTING #define FIO_RESP3_MAX_NESTING 32 #endif ``` Maximum nesting depth. Valid range is 2 to 32,768. Determines the size of `fio_resp3_parser_s.stack`. #### `FIO_RESP3_STREAM_THRESHOLD` ```c #ifndef FIO_RESP3_STREAM_THRESHOLD #define FIO_RESP3_STREAM_THRESHOLD 4096 #endif ``` Fixed-length blob strings (`$`, `!`, `=`) larger than this threshold are **streamed incrementally** when the callback table provides the streaming string callbacks (`on_start_string`, `on_string_write`, `on_string_done`): the parser starts the string as soon as its header is parsed and feeds data in chunks as it arrives (`result.consumed` advances per chunk), instead of waiting for the whole blob to be contiguous in the read window. This allows blobs larger than any consumer read buffer. Blobs at or below the threshold keep the wait-for-contiguity behavior (a single zero-copy `on_string_write`, even across split reads). Callback tables without streaming callbacks are unaffected (they always wait for contiguity). ### RESP3 Type Constants ```c #define FIO_RESP3_SIMPLE_STR '+' /* +\r\n */ #define FIO_RESP3_SIMPLE_ERR '-' /* -\r\n */ #define FIO_RESP3_NUMBER ':' /* :\r\n */ #define FIO_RESP3_NULL '_' /* _\r\n */ #define FIO_RESP3_DOUBLE ',' /* ,\r\n */ #define FIO_RESP3_BOOL '#' /* #t\r\n or #f\r\n */ #define FIO_RESP3_BIGNUM '(' /* (\r\n */ #define FIO_RESP3_BLOB_STR '$' /* $\r\n\r\n */ #define FIO_RESP3_BLOB_ERR '!' /* !\r\n\r\n */ #define FIO_RESP3_VERBATIM '=' /* =\r\n\r\n */ #define FIO_RESP3_ARRAY '*' /* *\r\n... */ #define FIO_RESP3_MAP '%' /* %\r\n... */ #define FIO_RESP3_SET '~' /* ~\r\n... */ #define FIO_RESP3_PUSH '>' /* >\r\n... */ #define FIO_RESP3_ATTR '|' /* |\r\n... */ #define FIO_RESP3_STREAM_CHUNK ';' /* ;\r\n\r\n */ #define FIO_RESP3_STREAM_END '.' /* .\r\n */ ``` ### Types #### `fio_resp3_frame_s` ```c typedef struct { void *ctx; void *key; int64_t remaining; uint8_t type; uint8_t streaming; uint8_t expecting_value; uint8_t set_as_map; } fio_resp3_frame_s; ``` Internal stack frame for a nested container. You do not need to touch this directly, but it is exposed in `fio_resp3_parser_s`. #### `fio_resp3_parser_s` ```c typedef struct { void *udata; uint32_t depth; uint8_t error; uint8_t streaming_string; uint8_t streaming_string_type; uint8_t streaming_blob_crlf; void *streaming_string_ctx; int64_t streaming_remaining; fio_resp3_frame_s stack[FIO_RESP3_MAX_NESTING]; } fio_resp3_parser_s; ``` Parser state. Initialize with `{.udata = my_context}` before the first call and reuse it for continuation after partial parses. **Members:** - `udata` - user data passed to all callbacks - `depth` - current nesting depth - `error` - set to non-zero after a protocol error; the parser will refuse further input - `streaming_string` - non-zero while a streamed string (`$?` chunked, or a fixed-length blob above `FIO_RESP3_STREAM_THRESHOLD`) is in progress - `streaming_string_type` - type of the streaming string in progress - `streaming_blob_crlf` - trailing CRLF bytes pending for a streamed fixed-length blob (0...2) - `streaming_string_ctx` - context returned by `on_start_string` - `streaming_remaining` - data bytes remaining for a streamed fixed-length blob (0 when inactive or in `$?` chunked mode) - `stack` - nested container frames #### `fio_resp3_callbacks_s` ```c typedef struct { /* primitives */ void *(*on_null)(void *udata); void *(*on_bool)(void *udata, int is_true); void *(*on_number)(void *udata, int64_t num); void *(*on_double)(void *udata, double num); void *(*on_bignum)(void *udata, const void *data, size_t len); void *(*on_string)(void *udata, const void *data, size_t len, uint8_t type); void *(*on_error)(void *udata, const void *data, size_t len, uint8_t type); /* containers */ void *(*on_array)(void *udata, void *parent_ctx, int64_t len); void *(*on_map)(void *udata, void *parent_ctx, int64_t len); void *(*on_set)(void *udata, void *parent_ctx, int64_t len); void *(*on_push)(void *udata, void *parent_ctx, int64_t len); void *(*on_attr)(void *udata, void *parent_ctx, int64_t len); /* push into containers */ int (*array_push)(void *udata, void *ctx, void *value); int (*map_push)(void *udata, void *ctx, void *key, void *value); int (*set_push)(void *udata, void *ctx, void *value); int (*push_push)(void *udata, void *ctx, void *value); int (*attr_push)(void *udata, void *ctx, void *key, void *value); /* finalize containers */ void *(*array_done)(void *udata, void *ctx); void *(*map_done)(void *udata, void *ctx); void *(*set_done)(void *udata, void *ctx); void *(*push_done)(void *udata, void *ctx); void *(*attr_done)(void *udata, void *ctx); /* errors */ void (*free_unused)(void *udata, void *obj); void *(*on_error_protocol)(void *udata); /* streaming strings */ void *(*on_start_string)(void *udata, size_t len, uint8_t type); int (*on_string_write)(void *udata, void *ctx, const void *data, size_t len); void *(*on_string_done)(void *udata, void *ctx, uint8_t type); } fio_resp3_callbacks_s; ``` Callback table. Designed to be `static const`. Non-streaming callbacks left `NULL` are replaced with safe no-ops. Primitive no-ops return a non-NULL sentinel; container no-ops do the same; push no-ops return `0`; done no-ops return the context unchanged. The streaming-string callbacks (`on_start_string`, `on_string_write`, and `on_string_done`) are not filled with no-op fallbacks. If `on_set`, `set_push`, and `set_done` are all `NULL` but map callbacks are provided, the parser treats RESP3 Sets as Maps, calling `map_push(ctx, value, value)` and `map_done`. #### `fio_resp3_result_s` ```c typedef struct { void *obj; size_t consumed; int err; } fio_resp3_result_s; ``` Parse result. **Members:** - `obj` - top-level object, or `NULL` while incomplete / on error - `consumed` - bytes consumed from the buffer - `err` - non-zero if a protocol error occurred ### API Functions #### `fio_resp3_parse` ```c SFUNC fio_resp3_result_s fio_resp3_parse(fio_resp3_parser_s *parser, const fio_resp3_callbacks_s *callbacks, const void *buf, size_t len); ``` Parses as much RESP3 data as possible. State is preserved in `parser`, so you can call again with the remaining bytes after a partial read. **Parameters:** - `parser` - parser state - `callbacks` - callback table (may be `NULL`; non-streaming callbacks become no-ops, while streaming-string callbacks remain unset) - `buf` - input buffer - `len` - input length **Returns:** parse result. **Note:** streamed strings (`$?`) require `on_start_string`, `on_string_write`, and `on_string_done` to be usable together. If `on_start_string` is missing or returns `NULL`, the parser sets `err` because it cannot buffer an unknown-length value. Once `on_start_string` returns a context, the parser enters streaming-string mode and later calls `on_string_write` and `on_string_done` directly, so those callbacks must also be supplied. For fixed-length blob, blob-error, and verbatim strings, `on_start_string` is optional; if it is missing or returns `NULL`, the parser falls back to `on_string` or `on_error`. If it returns a context for a fixed-length string, `on_string_write` and `on_string_done` are also called directly. Additionally, fixed-length blobs larger than `FIO_RESP3_STREAM_THRESHOLD` are always streamed incrementally when `on_start_string` is available (multiple `on_string_write` calls across parse calls possible), while smaller blobs complete in a single write once contiguous. **Note:** top-level attributes (`|`) are delivered via callbacks but do not become the returned `obj`; parsing continues for the following reply. ### Example ```c #define FIO_RESP3 #include "fio-stl.h" static void *on_null(void *u) { (void)u; return (void *)1; } static void *on_number(void *u, int64_t n) { (void)u; printf("num %lld\n", (long long)n); return (void *)1; } static void *on_string(void *u, const void *d, size_t l, uint8_t t) { (void)u; (void)t; printf("str %.*s\n", (int)l, (const char *)d); return (void *)1; } static void *on_array(void *u, void *c, int64_t l) { (void)u; (void)c; (void)l; return (void *)2; } static int array_push(void *u, void *c, void *v) { (void)u; (void)c; (void)v; return 0; } static void *array_done(void *u, void *c) { (void)u; return c; } int main(void) { static const fio_resp3_callbacks_s cb = { .on_null = on_null, .on_number = on_number, .on_string = on_string, .on_array = on_array, .array_push = array_push, .array_done = array_done, }; const char *data = "*2\r\n:42\r\n$5\r\nhello\r\n"; fio_resp3_parser_s p = {0}; fio_resp3_result_s r = fio_resp3_parse(&p, &cb, data, strlen(data)); printf("consumed=%zu err=%d obj=%p\n", r.consumed, r.err, r.obj); return 0; } ``` ------------------------------------------------------------ # Basic Socket Helpers ```c #define FIO_SOCK #include "fio-stl.h" ``` Portable socket helpers for POSIX and Windows: open, bind, connect, listen, wait, read, write, close, and a few sharp socket-shaped knives. Implemented in [`./004 sock.h`](https://facil.io/0.8.x/api/004-sock/). `FIO_SOCK` depends on URL parsing internally and pulls in what it needs. ### Configuration Macros #### `FIO_SOCK_DEFAULT_MAXIMIZE_LIMIT` ```c #ifndef FIO_SOCK_DEFAULT_MAXIMIZE_LIMIT #define FIO_SOCK_DEFAULT_MAXIMIZE_LIMIT (1ULL << 24) #endif ``` Default target used by `fio_sock_maximize_limits(0)`. #### `FIO_SOCK_AVOID_UMASK` ```c /* optional compile-time flag */ #define FIO_SOCK_AVOID_UMASK ``` If defined before including the implementation, Unix socket creation avoids the temporary `umask` call used when making public Unix sockets. This avoids `umask`'s process-global race, but may affect permissions on systems where `chmod` on Unix socket files is not enough. Windows behaves as if this concern does not apply. ### Types and Flags #### `fio_socket_i` ```c #if FIO_OS_WIN typedef SOCKET fio_socket_i; #else typedef int fio_socket_i; #endif ``` Native socket handle type. Use this instead of `int` when writing portable code. #### `FIO_SOCKET_INVALID` ```c #if FIO_OS_WIN #define FIO_SOCKET_INVALID INVALID_SOCKET #else #define FIO_SOCKET_INVALID (-1) #endif ``` Invalid socket sentinel. #### `FIO_SOCK_FD_ISVALID` ```c #define FIO_SOCK_FD_ISVALID(fd) ((fio_socket_i)(fd) != FIO_SOCKET_INVALID) ``` Returns non-zero if `fd` is not the invalid sentinel. #### `fio_sock_open_flags_e` ```c typedef enum { FIO_SOCK_SERVER = 0, FIO_SOCK_CLIENT = 1, FIO_SOCK_NONBLOCK = 2, FIO_SOCK_TCP = 4, FIO_SOCK_UDP = 8, #ifdef AF_UNIX FIO_SOCK_UNIX = 16, FIO_SOCK_UNIX_PRIVATE = (16 | 32), #else #define FIO_SOCK_UNIX 0 #define FIO_SOCK_UNIX_PRIVATE 0 #endif } fio_sock_open_flags_e; ``` Flags for `fio_sock_open`, `fio_sock_open2`, and `fio_sock_open_unix`. **Values:** - `FIO_SOCK_SERVER` - bind a local socket; TCP / stream sockets also call `listen` - `FIO_SOCK_CLIENT` - connect a remote socket - `FIO_SOCK_NONBLOCK` - set non-blocking mode - `FIO_SOCK_TCP` - stream socket - `FIO_SOCK_UDP` - datagram socket - `FIO_SOCK_UNIX` - Unix domain socket where supported, otherwise `0` - `FIO_SOCK_UNIX_PRIVATE` - Unix socket with private permissions where supported, otherwise `0` `FIO_SOCK_TCP` and `FIO_SOCK_UDP` are exclusive. If neither is set, network sockets default to TCP. ### Opening Sockets #### `fio_sock_open` ```c FIO_IFUNC fio_socket_i fio_sock_open(const char *restrict address, const char *restrict port, uint16_t flags); ``` Creates a socket from an address / port pair and flags. For `FIO_SOCK_UNIX`, `port` is ignored and `address` is treated as the Unix socket path. For server TCP sockets, `address == NULL` binds to all interfaces. **Returns:** a socket handle, or `FIO_SOCKET_INVALID` on error. #### `fio_sock_open2` ```c SFUNC fio_socket_i fio_sock_open2(const char *url, uint16_t flags); ``` Creates a socket from a URL-ish string. If no port is present, the URL scheme is used as the port / service name. Schemes such as `tcp`, `udp`, `http`, and `https` can also imply socket type when flags omit it. Unix path URLs are detected when supported. A `priv` scheme selects `FIO_SOCK_UNIX_PRIVATE`; other path-only Unix forms select `FIO_SOCK_UNIX`. **Returns:** a socket handle, or `FIO_SOCKET_INVALID` on error. #### `fio_sock_address_new` ```c FIO_IFUNC struct addrinfo *fio_sock_address_new(const char *restrict address, const char *restrict port, int sock_type); ``` Resolves `address` / `port` into an `addrinfo` list for `sock_type`, usually `SOCK_STREAM` or `SOCK_DGRAM`. Common service names are normalized before `getaddrinfo`: `ws` / `http` / `sse` to port `80`, and `wss` / `https` / `sses` to port `443`. **Ownership:** free the result with `fio_sock_address_free`. #### `fio_sock_address_free` ```c FIO_IFUNC void fio_sock_address_free(struct addrinfo *a); ``` Frees an address list returned by `fio_sock_address_new`. #### `fio_sock_open_local` ```c SFUNC fio_socket_i fio_sock_open_local(struct addrinfo *addr, int nonblock); ``` Creates a network socket from an `addrinfo` list and binds it locally. `SO_REUSEADDR` is enabled. If `nonblock` is non-zero, non-blocking mode is requested before binding. **Returns:** a socket handle, or `FIO_SOCKET_INVALID`. #### `fio_sock_open_remote` ```c SFUNC fio_socket_i fio_sock_open_remote(struct addrinfo *addr, int nonblock); ``` Creates a network socket from an `addrinfo` list and connects it remotely. Non-blocking connect progress (`EINPROGRESS` / Windows equivalents) returns the socket. **Returns:** a socket handle, or `FIO_SOCKET_INVALID`. #### `fio_sock_open_unix` ```c SFUNC fio_socket_i fio_sock_open_unix(const char *address, uint16_t flags); ``` Creates a Unix domain socket where supported. Server sockets unlink an existing path before binding. Non-UDP server sockets call `listen`. If Unix sockets are unsupported, the function asserts and returns `FIO_SOCKET_INVALID`. ### Portable Socket Operations Use these wrappers when code should run on both POSIX and WinSock2. #### `fio_sock_write` ```c FIO_IFUNC ssize_t fio_sock_write(fio_socket_i fd, const void *buf, size_t len); ``` Acts like POSIX `write` on POSIX and uses `send` on Windows. #### `fio_sock_read` ```c FIO_IFUNC ssize_t fio_sock_read(fio_socket_i fd, void *buf, size_t len); ``` Acts like POSIX `read` on POSIX and uses `recv` on Windows. #### `fio_sock_sendto` ```c FIO_IFUNC ssize_t fio_sock_sendto(fio_socket_i fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen); ``` Portable `sendto` wrapper. #### `fio_sock_recvfrom` ```c FIO_IFUNC ssize_t fio_sock_recvfrom(fio_socket_i fd, void *buf, size_t len, int flags, struct sockaddr *addr, socklen_t *addrlen); ``` Portable `recvfrom` wrapper. #### `fio_sock_close` ```c FIO_IFUNC int fio_sock_close(fio_socket_i fd); ``` Portable close wrapper: `close` on POSIX, `closesocket` on Windows. #### `fio_sock_accept` ```c #if FIO_OS_WIN IFUNC fio_socket_i fio_sock_accept(fio_socket_i s, struct sockaddr *addr, int *addrlen); #else #define fio_sock_accept(fd, addr, addrlen) accept(fd, addr, addrlen) #endif ``` Portable accept wrapper. #### `fio_sock_dup` ```c FIO_IFUNC fio_socket_i fio_sock_dup(fio_socket_i fd); ``` Duplicates a socket handle. POSIX uses `dup` and sets `FD_CLOEXEC`; Windows uses `WSADuplicateSocket` and `WSASocket`. #### `fio_sock_bind` ```c FIO_IFUNC int fio_sock_bind(fio_socket_i fd, const struct sockaddr *addr, socklen_t addrlen); ``` Portable `bind` wrapper. #### `fio_sock_connect` ```c FIO_IFUNC int fio_sock_connect(fio_socket_i fd, const struct sockaddr *addr, socklen_t addrlen); ``` Portable `connect` wrapper. #### `fio_sock_listen` ```c FIO_IFUNC int fio_sock_listen(fio_socket_i fd, int backlog); ``` Portable `listen` wrapper. #### `fio_sock_setsockopt` ```c FIO_IFUNC int fio_sock_setsockopt(fio_socket_i fd, int level, int optname, const void *optval, socklen_t optlen); ``` Portable `setsockopt` wrapper. Pointer and length type differences are handled internally. #### `fio_sock_socketpair` ```c FIO_IFUNC int fio_sock_socketpair(fio_socket_i fds[2]); ``` Creates a connected socket pair. POSIX uses `socketpair(AF_UNIX, SOCK_STREAM, 0, fds)`. Windows creates a loopback TCP pair. #### `fio_sock_pipe` ```c FIO_IFUNC int fio_sock_pipe(fio_socket_i fds[2]); ``` Creates a pipe-like pair. POSIX uses `pipe`; Windows delegates to `fio_sock_socketpair` so the handles are usable with WinSock polling. ### Socket State and Waiting #### `fio_sock_set_non_block` ```c SFUNC int fio_sock_set_non_block(fio_socket_i fd); ``` Sets a socket to non-blocking mode. **Returns:** `0` on success, `-1` on error. #### `fio_sock_maximize_limits` ```c SFUNC size_t fio_sock_maximize_limits(size_t maximum_limit); ``` Attempts to raise the open-file limit up to `maximum_limit`. Passing `0` uses `FIO_SOCK_DEFAULT_MAXIMIZE_LIMIT`. On Windows this returns the advisory cap because there is no `RLIMIT_NOFILE` equivalent for sockets. #### `fio_sock_wait_io` ```c SFUNC short fio_sock_wait_io(fio_socket_i fd, short events, int timeout); ``` Waits for `events` on one socket using `poll` / `WSAPoll`. `timeout` is in milliseconds; `0` returns immediately. **Returns:** `0` on timeout, `-1` on error, or event bits such as `POLLIN`, `POLLOUT`, `POLLHUP`, and `POLLNVAL`. #### `FIO_SOCK_WAIT_RW` ```c #define FIO_SOCK_WAIT_RW(fd, timeout_) \ fio_sock_wait_io(fd, POLLIN | POLLOUT, timeout_) ``` Waits for read or write readiness. #### `FIO_SOCK_WAIT_R` ```c #define FIO_SOCK_WAIT_R(fd, timeout_) fio_sock_wait_io(fd, POLLIN, timeout_) ``` Waits for read readiness. #### `FIO_SOCK_WAIT_W` ```c #define FIO_SOCK_WAIT_W(fd, timeout_) fio_sock_wait_io(fd, POLLOUT, timeout_) ``` Waits for write readiness. #### `FIO_SOCK_IS_OPEN` ```c #ifdef POLLRDHUP #define FIO_SOCK_IS_OPEN(fd) \ (!(fio_sock_wait_io(fd, (POLLOUT | POLLRDHUP), 0) & \ (POLLRDHUP | POLLHUP | POLLNVAL))) #else #define FIO_SOCK_IS_OPEN(fd) \ (!(fio_sock_wait_io(fd, POLLOUT, 0) & (POLLHUP | POLLNVAL))) #endif ``` Best-effort test for whether a socket still appears open. #### `fio_sock_peer_addr` ```c SFUNC fio_buf_info_s fio_sock_peer_addr(fio_socket_i s); ``` Returns a numeric, human-readable peer address. On error, returns `{NULL, 0}`. The returned buffer is internal static storage, limited to 63 bytes. Thread-safety is limited to 128 threads / calls. ### Windows Notes On Windows, the module initializes WinSock in a constructor and stores direct function pointers loaded from `Ws2_32.dll`. Wrapper functions translate common WinSock errors into `errno` values. The `fio___sock_wsa*` helpers are internal implementation details, not regular application API. Tiny dragons; leave them napping. ### Ownership and Thread-Safety Socket handles are owned by the caller and should be closed with `fio_sock_close`. `fio_sock_address_new` results are owned by the caller and should be freed with `fio_sock_address_free`. Socket operations are as thread-safe as the operating system calls they wrap. `fio_sock_peer_addr` uses shared static buffers and has the 128-call/thread caveat described above. Unix socket creation may touch process-global `umask` unless `FIO_SOCK_AVOID_UMASK` is defined. ### Example ```c #define FIO_SOCK #include "fio-stl.h" #include #include int main(void) { fio_socket_i fds[2]; char buf[8] = {0}; if (fio_sock_pipe(fds)) return 1; fio_sock_write(fds[1], "ping", 4); fio_sock_read(fds[0], buf, sizeof(buf) - 1); printf("%s\n", buf); fio_sock_close(fds[0]); fio_sock_close(fds[1]); return 0; } ``` ### TCP Server Sketch ```c #define FIO_SOCK #include "fio-stl.h" int main(void) { fio_socket_i s = fio_sock_open(NULL, "8080", FIO_SOCK_SERVER | FIO_SOCK_TCP | FIO_SOCK_NONBLOCK); if (!FIO_SOCK_FD_ISVALID(s)) return 1; if (FIO_SOCK_WAIT_R(s, 1000) & POLLIN) { fio_socket_i c = fio_sock_accept(s, NULL, NULL); if (FIO_SOCK_FD_ISVALID(c)) fio_sock_close(c); } fio_sock_close(s); return 0; } ``` --- # State Callbacks ```c #define FIO_STATE #include "fio-stl.h" ``` A small global event bus for lifecycle callbacks: initialize, fork, start, idle, shutdown, exit, and two user queues. Modules can listen without the caller knowing they exist. Sneaky, but polite. Implemented in [`./004 state callbacks.h`](https://facil.io/0.8.x/api/004-state-callbacks/). Use this as a global `FIO_EXTERN` module when multiple translation units should share the same callback registry. ### Types #### `fio_state_event_type_e` ```c typedef enum { FIO_CALL_ON_INITIALIZE, FIO_CALL_PRE_START, FIO_CALL_BEFORE_FORK, FIO_CALL_IN_CHILD, FIO_CALL_IN_MASTER, FIO_CALL_AFTER_FORK, FIO_CALL_ON_WORKER_THREAD_START, FIO_CALL_ON_START, FIO_CALL_RESERVED1, FIO_CALL_RESERVED2, FIO_CALL_ON_USER1, FIO_CALL_ON_USER2, FIO_CALL_ON_IDLE, FIO_CALL_ON_USER1_REVERSE, FIO_CALL_ON_USER2_REVERSE, FIO_CALL_RESERVED1_REVERSED, FIO_CALL_RESERVED2_REVERSED, FIO_CALL_ON_SHUTDOWN, FIO_CALL_ON_PARENT_CRUSH, FIO_CALL_ON_CHILD_CRUSH, FIO_CALL_ON_WORKER_THREAD_END, FIO_CALL_ON_STOP, FIO_CALL_AT_EXIT, FIO_CALL_AFTER_EXIT, FIO_CALL_NEVER } fio_state_event_type_e; ``` Event queues. `FIO_CALL_NEVER` is the sentinel and should not be used as an event. **Ordering:** - events `<= FIO_CALL_ON_IDLE` run in registration order - events `>= FIO_CALL_ON_SHUTDOWN` run in reverse registration order `FIO_CALL_ON_USER1` / `FIO_CALL_ON_USER2` and their reverse variants are available for user code. ### Callback Management #### `fio_state_callback_add` ```c SFUNC void fio_state_callback_add(fio_state_event_type_e, void (*func)(void *), void *arg); ``` Adds `func(arg)` to an event queue. Adding the same function / argument pair replaces the existing map entry rather than creating a duplicate. If `FIO_CALL_ON_INITIALIZE` already ran, adding another initialize callback calls it immediately. #### `fio_state_callback_remove` ```c SFUNC int fio_state_callback_remove(fio_state_event_type_e, void (*func)(void *), void *arg); ``` Removes `func(arg)` from an event queue. **Returns:** the internal map removal result, or `-1` if the event is invalid. #### `fio_state_callback_clear` ```c SFUNC void fio_state_callback_clear(fio_state_event_type_e e); ``` Clears all callbacks for `e`. Invalid events are ignored. This function is implemented in the header's implementation section; with `FIO_EXTERN`, expose the implementation in exactly one translation unit as usual. #### `fio_state_callback_force` ```c SFUNC void fio_state_callback_force(fio_state_event_type_e); ``` Runs the callbacks for an event as if the event happened. During a forced event, the callback list is copied first. Additions and removals for the same event do not affect the current run. Special behavior: - `FIO_CALL_ON_INITIALIZE` runs only once and reserves internal maps - `FIO_CALL_IN_CHILD` reinitializes internal locks and reseeds randomness - `FIO_CALL_AFTER_FORK` reseeds randomness ### Debug Helpers #### `fio_state_callback_print_state` ```c FIO_IFUNC void fio_state_callback_print_state(void); ``` Prints callback counts and capacities for every event queue to `stderr`. ### Thread-Safety Each event queue has its own lock. Adding, removing, clearing, and forcing callbacks are synchronized while touching the queue. Callback execution happens after the queue has been copied and unlocked. ### Example ```c #define FIO_STATE #include "fio-stl.h" #include static void hello(void *arg) { printf("hello %s\n", (const char *)arg); } int main(void) { fio_state_callback_add(FIO_CALL_ON_USER1, hello, "callbacks"); fio_state_callback_force(FIO_CALL_ON_USER1); fio_state_callback_remove(FIO_CALL_ON_USER1, hello, "callbacks"); return 0; } ``` --- # Time Helpers ```c #define FIO_TIME #include "fio-stl.h" ``` Wall-clock and monotonic time helpers, UTC calendar conversion, and a handful of date formatters. Tiny clock toolbox, no sundial required. Implemented in [`./004 time.h`](https://facil.io/0.8.x/api/004-time/). ### Collecting Time #### `fio_time_real` ```c FIO_IFUNC struct timespec fio_time_real(void); ``` Returns human / watch time using `CLOCK_REALTIME`. Good for timestamps, less good for measuring elapsed time because the system clock may jump. #### `fio_time_mono` ```c FIO_IFUNC struct timespec fio_time_mono(void); ``` Returns monotonic time using `CLOCK_MONOTONIC`. Prefer this for intervals and deadlines. #### `fio_time_nano` ```c FIO_IFUNC int64_t fio_time_nano(void); ``` Returns monotonic time in nanoseconds. #### `fio_time_micro` ```c FIO_IFUNC int64_t fio_time_micro(void); ``` Returns monotonic time in microseconds. #### `fio_time_milli` ```c FIO_IFUNC int64_t fio_time_milli(void); ``` Returns monotonic time in milliseconds. ### Conversion #### `fio_time2milli` ```c FIO_IFUNC int64_t fio_time2milli(struct timespec); ``` Converts a `struct timespec` to milliseconds. #### `fio_time2micro` ```c FIO_IFUNC int64_t fio_time2micro(struct timespec); ``` Converts a `struct timespec` to microseconds. #### `fio_time2gm` ```c SFUNC struct tm fio_time2gm(time_t time); ``` Converts `time` to a UTC `struct tm`. This is a faster, less localized alternative to `gmtime_r`. #### `fio_gm2time` ```c SFUNC time_t fio_gm2time(struct tm tm); ``` Converts a UTC `struct tm` to seconds. If `tm.tm_isdst > 0`, one hour is subtracted. On platforms with `tm_gmtoff`, that offset is added. ### Date Formatting All formatting functions write to `target`, NUL-terminate the output, and return the number of bytes written before the NUL. Provide a buffer with enough space; `64` bytes is comfortably boring. #### `fio_time2rfc7231` ```c SFUNC size_t fio_time2rfc7231(char *target, time_t time); ``` Writes an RFC 7231 / HTTP date, usually 29 characters: ```text Sun, 06 Nov 1994 08:49:37 GMT ``` #### `fio_time2rfc2109` ```c SFUNC size_t fio_time2rfc2109(char *target, time_t time); ``` Writes an RFC 2109 cookie date, usually 31 characters: ```text Sun, 06 Nov 1994 08:49:37 -0000 ``` #### `fio_time2rfc2822` ```c SFUNC size_t fio_time2rfc2822(char *target, time_t time); ``` Writes an RFC 2822 date, usually 28-29 characters: ```text Sun, 6-Nov-1994 08:49:37 GMT ``` #### `fio_time2log` ```c SFUNC size_t fio_time2log(char *target, time_t time); ``` Writes common log format: ```text [DD/MMM/yyyy:hh:mm:ss +0000] ``` Usually needs 29 bytes including the NUL terminator. #### `fio_time2iso` ```c SFUNC size_t fio_time2iso(char *target, time_t time); ``` Writes an ISO-ish representation. The header comment says `YYYY-MM-DD HH:MM:SS`; the implementation writes a 3-letter month name: ```text YYYY-MMM-DD HH:MM:SS ``` Usually needs 20 bytes including the NUL terminator. ### Arithmetic #### `fio_time_add` ```c FIO_IFUNC struct timespec fio_time_add(struct timespec t, struct timespec t2); ``` Adds two `struct timespec` values and normalizes the nanoseconds field. #### `fio_time_add_milli` ```c FIO_IFUNC struct timespec fio_time_add_milli(struct timespec t, int64_t milli); ``` Adds milliseconds to `t` and normalizes the result. The implementation splits milliseconds with a `1024`-millisecond approximation before normalization; cute, but be aware. #### `fio_time_cmp` ```c FIO_IFUNC int fio_time_cmp(struct timespec t1, struct timespec t2); ``` Compares two `struct timespec` values. **Returns:** `-1` if `t1 < t2`, `0` if equal, `1` if `t1 > t2`. ### Example ```c #define FIO_TIME #include "fio-stl.h" #include #include int main(void) { struct timespec start = fio_time_mono(); struct timespec later = fio_time_add_milli(start, 500); printf("now: %lld ms\n", (long long)fio_time_milli()); printf("later is after start: %d\n", fio_time_cmp(later, start) > 0); char date[64]; fio_time2rfc7231(date, fio_time_real().tv_sec); printf("http date: %s\n", date); return 0; } ``` --- # URL-Encoded Parser ```c #define FIO_URL_ENCODED #include "fio-stl.h" ``` A non-allocating parser for `application/x-www-form-urlencoded` data. It splits `name=value&...` pairs and leaves decoding to you. No copies, no drama. Implemented in [`./004 urlencoded.h`](https://facil.io/0.8.x/api/004-urlencoded/). ### Types #### `fio_url_encoded_parser_callbacks_s` ```c typedef struct { void *(*on_pair)(void *udata, fio_buf_info_s name, fio_buf_info_s value); void (*on_error)(void *udata); } fio_url_encoded_parser_callbacks_s; ``` Parser callbacks. `udata` is passed to callbacks and updated from `on_pair`'s return value. **Members:** - `on_pair` - called for each pair. `name` and `value` point into the original input and are **not decoded**. Return non-NULL to continue, or `NULL` to stop. - `on_error` - optional and currently unused; reserved because parsers enjoy having a rainy-day pocket. If `callbacks` or a member is `NULL`, an internal no-op callback is used. #### `fio_url_encoded_result_s` ```c typedef struct { size_t consumed; size_t count; int err; } fio_url_encoded_result_s; ``` Parse result. **Members:** - `consumed` - number of input bytes consumed - `count` - number of non-empty pairs reported - `err` - non-zero when parsing stopped because `on_pair` returned `NULL` ### Parsing #### `fio_url_encoded_parse` ```c SFUNC fio_url_encoded_result_s fio_url_encoded_parse(const fio_url_encoded_parser_callbacks_s *callbacks, void *udata, const char *data, size_t len); ``` Parses `data[0..len)` as URL-encoded form data. Rules from the implementation: - pairs are separated by `&` - the first `=` in a pair separates name and value - `name=` is valid and has an empty value - `name` is valid and has an empty value - `=value` is valid and has an empty name - empty pairs such as leading `&` or `&&` are skipped - percent escapes are not decoded Use a string helper such as `fio_string_write_url_dec` when you need decoded values. **Ownership:** callback buffers borrow the original `data`; keep `data` alive while parsing and copy anything you need later. **Thread-safety:** parsing uses only caller-provided state and stack locals. ### Example ```c #define FIO_URL_ENCODED #include "fio-stl.h" #include #include static void *on_pair(void *udata, fio_buf_info_s name, fio_buf_info_s value) { printf("%.*s = %.*s\n", (int)name.len, name.buf, (int)value.len, value.buf); return udata; } int main(void) { const char form[] = "name=Bo&empty=&encoded=a%20b&&solo"; static const fio_url_encoded_parser_callbacks_s cb = { .on_pair = on_pair, }; fio_url_encoded_result_s r = fio_url_encoded_parse(&cb, (void *)1, form, strlen(form)); printf("pairs=%zu consumed=%zu err=%d\n", r.count, r.consumed, r.err); return r.err; } ``` --- # WebSocket Parser ```c #define FIO_WEBSOCKET_PARSER #include FIO_INCLUDE_FILE ``` Small RFC 6455 frame parser and writer. It keeps 24 bytes of parser state, allocates nothing, unmasks incoming payload bytes in-place, and reports one parse event at a time. Nearby context: [IO and HTTP overview](./400 io-overview.md), the neighboring [HTTP/1.x parser](./004 http1 parser.md), the higher-level [HTTP module](./439 http.md), and WebSocket compression support in [DEFLATE / Gzip](./162 deflate.md). --- ## What Gets Added `FIO_WEBSOCKET_PARSER` exposes: - `fio_websocket_s` — incremental parser state. - `fio_websocket_event_s` — one parse event returned by `fio_websocket_parse`. - `fio_websocket_init` / `fio_websocket_reset` — parser lifecycle helpers. - `fio_websocket_parse` — incremental parser entry point. - frame writer helpers for server and client data, ping, pong, and close frames. - WebSocket close code, event type, opcode, RSV, parser state, flag, and error constants. The implementation also defines helpers named with `fio___websocket...`; those are private implementation details. --- ## Parser State ### `fio_websocket_s` ```c typedef struct fio_websocket_s { uint64_t frame_remaining; uint32_t mask; uint32_t frame_consumed; uint16_t close_code; uint8_t state; uint8_t flags; uint8_t flags2; uint8_t reserved; } fio_websocket_s; ``` The parser state is intentionally small and is asserted to fit in 24 bytes. Allocate it wherever the connection state lives, initialize it before use, and prefer the public helper macros below instead of editing fields by hand. ```c FIO_WEBSOCKET_GET_FIN(p) FIO_WEBSOCKET_GET_MASKED(p) FIO_WEBSOCKET_GET_OPCODE(p) FIO_WEBSOCKET_GET_MSG_OPCODE(p) FIO_WEBSOCKET_GET_PAUSED(p) FIO_WEBSOCKET_GET_MSG_RSV(p) ``` `GET_OPCODE` reports the current frame opcode. `GET_MSG_OPCODE` reports the open message opcode (`FIO_WEBSOCKET_OP_TEXT`, `FIO_WEBSOCKET_OP_BINARY`, or `0` when no message is open). `GET_MSG_RSV` reports the RSV bits from the opening data frame in the same 3-bit format used by the writer API. ### Lifecycle ```c FIO_IFUNC void fio_websocket_init(fio_websocket_s *p); FIO_IFUNC void fio_websocket_reset(fio_websocket_s *p); ``` Both helpers clear the struct and set the parser to the header-reading state. After a protocol error or after receiving a close frame, discard or reset the parser before reusing it. --- ## Events ### `fio_websocket_event_s` ```c typedef struct { uint8_t type; uint8_t opcode; uint8_t is_text; uint8_t rsv; uint8_t is_first; uint8_t is_last; fio_buf_info_s payload; uint16_t close_code; } fio_websocket_event_s; ``` `payload` points into the input buffer passed to `fio_websocket_parse`. If the incoming frame is masked, that memory is unmasked in-place before the event is returned. Keep the input bytes writable and alive until the event payload is no longer needed. ### Event Types ```c FIO_WEBSOCKET_EV_NONE FIO_WEBSOCKET_EV_DATA_CHUNK FIO_WEBSOCKET_EV_CONTROL FIO_WEBSOCKET_EV_MESSAGE_END FIO_WEBSOCKET_EV_ERROR ``` The current parser reports message data with `FIO_WEBSOCKET_EV_DATA_CHUNK` and marks message boundaries with `is_first` and `is_last`. Control frames are reported whole with `FIO_WEBSOCKET_EV_CONTROL`. Protocol errors are reported as `FIO_WEBSOCKET_EV_ERROR` when an event pointer is supplied. `FIO_WEBSOCKET_EV_MESSAGE_END` is defined for API completeness, but the current parse loop uses the `is_last` flag on data events instead of emitting a separate message-end event. ### Event Fields For data events: - `opcode` is the current frame opcode. Continuation frames report `FIO_WEBSOCKET_OP_CONT`. - `is_text` is true when the open message started as a text message. - `rsv` is copied from the opening data frame. - `is_first` is true for the first chunk of the opening data frame. - `is_last` is true for the final chunk of the message. - `payload` is the available payload chunk, possibly length `0`. For control events: - `opcode` is `FIO_WEBSOCKET_OP_CLOSE`, `FIO_WEBSOCKET_OP_PING`, or `FIO_WEBSOCKET_OP_PONG`. - `payload` is the complete control payload, never a partial control frame. - close frames set `close_code` to the on-wire close code, or `FIO_WEBSOCKET_CLOSE_NO_STATUS` for an empty close payload. --- ## Parsing API ```c FIO_SFUNC size_t fio_websocket_parse(fio_websocket_s *p, fio_buf_info_s buf, fio_websocket_event_s *ev); ``` Parses WebSocket bytes from `buf` and returns the number of bytes consumed, or `FIO_WEBSOCKET_PARSE_ERROR` (`(size_t)-1`) on protocol error. A successful call may consume fewer bytes than supplied. Feed the remaining bytes to the next call, usually in a loop. If more bytes are needed before an event can be produced, the function returns the bytes consumed so far, which can be `0`, and leaves `ev->type` as `FIO_WEBSOCKET_EV_NONE`. The parser is pure state plus the caller-provided buffer: - no heap allocation; - no callbacks; - no retained payload pointers; - no internal message accumulator; - no control-frame buffering beyond waiting until a complete control payload is available in the supplied input. `ev` may be `NULL` if the caller only wants to advance or validate input, but then event details and close/error codes must be read from parser state where available. ### Parser Flow ```text header ├─ incomplete header -> wait for more bytes ├─ control frame -> wait for full payload -> CONTROL event └─ data / continuation -> unmask available bytes -> DATA_CHUNK event └─ is_last marks message completion ``` The parser returns after at most one event. For fragmented messages, each data frame may produce one or more data chunks depending on the input buffer splits. Control frames may appear between fragmented data frames and are reported as control events without closing the open message. ### Protocol Checks Performed The parser rejects: - unknown opcodes; - fragmented control frames; - continuation frames without an open message; - nested text/binary messages while another data message is open; - control payloads larger than 125 bytes; - 64-bit lengths with the high bit set; - frames larger than `FIO_WEBSOCKET_DEFAULT_MAX_FRAME`; - close frames with a 1-byte payload; - close frames carrying invalid on-wire close codes. On rejection, the parser state becomes `FIO_WEBSOCKET_STATE_ERROR`, `p->close_code` is set, and `fio_websocket_parse` returns `FIO_WEBSOCKET_PARSE_ERROR`. Caller policy still includes: - enforcing client/server masking rules; - interpreting RSV bits and applying extension transforms such as `permessage-deflate`; - enforcing total message size limits; - validating UTF-8 for text messages; - accumulating message chunks if whole-message delivery is desired. --- ## Frame Writers Server writers produce unmasked frames. Client writers produce masked frames; passing `mask == 0` asks the writer to generate a non-zero PRNG mask. ### Sizing ```c FIO_IFUNC uint64_t fio_websocket_write_len(uint64_t payload_len, _Bool masked); ``` Returns the number of bytes required for one complete frame with the requested payload length and masking mode. Allocate at least this many bytes before calling a writer. ### Data Messages ```c FIO_IFUNC uint64_t fio_websocket_write_message_server(void *target, fio_buf_info_s msg, _Bool is_text, uint8_t rsv); FIO_IFUNC uint64_t fio_websocket_write_message_client(void *target, fio_buf_info_s msg, _Bool is_text, uint32_t mask, uint8_t rsv); ``` Writes one complete FIN data message to `target` and returns the bytes written. `is_text` selects text (`1`) or binary (`0`). `rsv` is the 3-bit RSV value; usually pass `0`, or `FIO_WEBSOCKET_RSV1` for a compressed `permessage-deflate` message after applying the extension transform. ### Ping / Pong ```c FIO_IFUNC uint64_t fio_websocket_write_ping_server(void *target, fio_buf_info_s payload); FIO_IFUNC uint64_t fio_websocket_write_ping_client(void *target, fio_buf_info_s payload, uint32_t mask); FIO_IFUNC uint64_t fio_websocket_write_pong_server(void *target, fio_buf_info_s payload); FIO_IFUNC uint64_t fio_websocket_write_pong_client(void *target, fio_buf_info_s payload, uint32_t mask); ``` Writes one FIN control frame. Keep ping and pong payloads at 125 bytes or less; the writer does not add a separate policy check for that RFC limit. ### Close ```c FIO_IFUNC uint64_t fio_websocket_write_close_server(void *target, uint16_t code, fio_buf_info_s reason); FIO_IFUNC uint64_t fio_websocket_write_close_client(void *target, uint16_t code, fio_buf_info_s reason, uint32_t mask); ``` Writes a close frame containing the 2-byte close code followed by the optional reason. The reason is truncated so the whole close payload fits in the 125-byte control-frame limit. Choose a close code that is valid to send on the wire. --- ## Constants ### Opcodes ```c FIO_WEBSOCKET_OP_CONT FIO_WEBSOCKET_OP_TEXT FIO_WEBSOCKET_OP_BINARY FIO_WEBSOCKET_OP_CLOSE FIO_WEBSOCKET_OP_PING FIO_WEBSOCKET_OP_PONG ``` ### Close Codes ```c FIO_WEBSOCKET_CLOSE_OK FIO_WEBSOCKET_CLOSE_GOING_AWAY FIO_WEBSOCKET_CLOSE_PROTOCOL_ERROR FIO_WEBSOCKET_CLOSE_UNSUPPORTED_DATA FIO_WEBSOCKET_CLOSE_NO_STATUS FIO_WEBSOCKET_CLOSE_INVALID_PAYLOAD FIO_WEBSOCKET_CLOSE_POLICY_VIOLATION FIO_WEBSOCKET_CLOSE_MESSAGE_TOO_BIG FIO_WEBSOCKET_CLOSE_MANDATORY_EXT FIO_WEBSOCKET_CLOSE_INTERNAL_ERROR ``` `FIO_WEBSOCKET_CLOSE_NO_STATUS` is synthesized for an empty received close payload and must not be sent as an on-wire status code. The parser also accepts valid registered wire codes such as 1012-1014 and application/library codes in the 3000-4999 range. ### RSV Bits ```c FIO_WEBSOCKET_RSV1 FIO_WEBSOCKET_RSV2 FIO_WEBSOCKET_RSV3 ``` These are 3-bit values for the writer API and event `rsv` field. The writer shifts them into byte-0 bits 4..6 on the wire. ### Parser States and Limits ```c FIO_WEBSOCKET_STATE_HEADER FIO_WEBSOCKET_STATE_PAYLOAD FIO_WEBSOCKET_STATE_CLOSED FIO_WEBSOCKET_STATE_ERROR FIO_WEBSOCKET_DEFAULT_MAX_FRAME FIO_WEBSOCKET_PARSE_ERROR ``` `FIO_WEBSOCKET_DEFAULT_MAX_FRAME` defaults to 1 GiB and may be overridden before including the header. `FIO_WEBSOCKET_PARSE_ERROR` is the parse error sentinel. The header also exposes flag bit masks used by the accessor macros: `FIO_WEBSOCKET_FLAG_FIN`, `FIO_WEBSOCKET_FLAG_MASKED`, `FIO_WEBSOCKET_FLAG_OPCODE_MASK`, `FIO_WEBSOCKET_FLAG_OPCODE_SHIFT`, `FIO_WEBSOCKET_FLAG_MSG_OPCODE_MASK`, `FIO_WEBSOCKET_FLAG2_PAUSED`, `FIO_WEBSOCKET_FLAG2_MSG_RSV_MASK`, and `FIO_WEBSOCKET_FLAG2_MSG_RSV_SHIFT`. --- ## Minimal Parse Loop ```c #define FIO_WEBSOCKET_PARSER #include FIO_INCLUDE_FILE typedef struct { fio_websocket_s ws; } connection_s; void websocket_consume(connection_s *c, char *data, size_t len) { fio_buf_info_s buf = FIO_BUF_INFO2(data, len); while (buf.len) { fio_websocket_event_s ev; size_t n = fio_websocket_parse(&c->ws, buf, &ev); if (n == FIO_WEBSOCKET_PARSE_ERROR) { /* Send/record c->ws.close_code and close the connection. */ return; } buf.buf += n; buf.len -= n; if (!n && ev.type == FIO_WEBSOCKET_EV_NONE) break; /* wait for more network bytes */ switch (ev.type) { case FIO_WEBSOCKET_EV_DATA_CHUNK: /* ev.payload is already unmasked and points into data. */ if (ev.is_first) { /* start a message accumulator, if needed */ } if (ev.payload.len) { /* copy/process this chunk before reusing data */ } if (ev.is_last) { /* finish the message */ } break; case FIO_WEBSOCKET_EV_CONTROL: if (ev.opcode == FIO_WEBSOCKET_OP_PING) { char out[128 + 14]; uint64_t written = fio_websocket_write_pong_server(out, ev.payload); (void)written; /* write out to the socket */ } else if (ev.opcode == FIO_WEBSOCKET_OP_CLOSE) { /* Echo/close according to local policy. */ return; } break; default: break; } } } void websocket_open(connection_s *c) { fio_websocket_init(&c->ws); } ``` For real IO, preserve any unconsumed bytes and retry when more data arrives. Copy payload bytes before recycling the read buffer or applying asynchronous message handling. --- ## Minimal Write Example ```c char out[14 + 1024]; fio_buf_info_s msg = FIO_BUF_INFO2("hello", 5); uint64_t len = fio_websocket_write_message_server(out, msg, 1, 0); /* write `len` bytes from out */ ``` For client frames: ```c uint64_t len = fio_websocket_write_message_client(out, msg, 1, 0, 0); ``` The fourth argument is an explicit mask; `0` lets the writer choose one. --- ## Ownership and Lifetime - The parser owns only `fio_websocket_s` state supplied by the caller. - The parser never allocates, frees, stores callbacks, or retains payload pointers after returning. - Input buffers passed to `fio_websocket_parse` must be writable because masked payloads are modified in-place. - Event payload slices are valid only while the input buffer remains valid and unchanged. - Writer targets must be large enough for `fio_websocket_write_len(payload.len, masked)` bytes. - Independent parser instances can be used concurrently by different threads; synchronize shared buffers and connection state in the caller. # CLI Helpers ```c #define FIO_CLI #include "fio-stl.h" ``` A small command-line parser with auto-generated help, typed flags, and unnamed-argument support. It is easier to set up than `getopt` and stays out of the way once you have your values. Defining `FIO_CLI` also pulls in `FIO_ATOL`, `FIO_RAND`, and `FIO_IMAP` because the parser needs them internally. **Note:** the module keeps a global data table. Parsing and setting values is **not** thread-safe. Reading values is safe after parsing is complete. --- ## Types #### `fio_cli_arg_e` ```c typedef enum { FIO_CLI_ARG_STRING, FIO_CLI_ARG_BOOL, FIO_CLI_ARG_INT, FIO_CLI_ARG_PRINT, FIO_CLI_ARG_PRINT_LINE, FIO_CLI_ARG_PRINT_HEADER, } fio_cli_arg_e; ``` Argument type tags used internally and returned by `fio_cli_type`. **Values:** - `FIO_CLI_ARG_STRING` — accepts any string value. - `FIO_CLI_ARG_BOOL` — a flag; presence means true. - `FIO_CLI_ARG_INT` — validates the value as an integer. - `FIO_CLI_ARG_PRINT` — indented help text line. - `FIO_CLI_ARG_PRINT_LINE` — unindented help text line. - `FIO_CLI_ARG_PRINT_HEADER` — section header in help output. --- ## Argument Definition Macros Use these inside the `fio_cli_start` macro to describe each flag and help line. #### `FIO_CLI_STRING` ```c #define FIO_CLI_STRING(line) /* ... */ ``` Declares a string argument. ```c FIO_CLI_STRING("-o -output (stdout) output file path") ``` #### `FIO_CLI_INT` ```c #define FIO_CLI_INT(line) /* ... */ ``` Declares an integer argument. The parser validates the value with `fio_atol`. ```c FIO_CLI_INT("-p -port (8080) the port number to use") ``` #### `FIO_CLI_BOOL` ```c #define FIO_CLI_BOOL(line) /* ... */ ``` Declares a boolean flag. Boolean flags cannot have default values. They can be chained, e.g. `-abc` is treated as `-a -b -c`. ```c FIO_CLI_BOOL("-v -verbose enable verbose logging") ``` #### `FIO_CLI_PRINT` ```c #define FIO_CLI_PRINT(line) /* ... */ ``` Prints an extra indented help line after the previous argument. ```c FIO_CLI_INT("-p -port (8080) the port number"), FIO_CLI_PRINT("Set to 0 for Unix socket mode.") ``` #### `FIO_CLI_PRINT_LINE` ```c #define FIO_CLI_PRINT_LINE(line) /* ... */ ``` Prints a help line without indentation. #### `FIO_CLI_PRINT_HEADER` ```c #define FIO_CLI_PRINT_HEADER(line) /* ... */ ``` Prints a section header in the help output. ```c FIO_CLI_PRINT_HEADER("Network Options:") ``` --- ## Initialization and Cleanup #### `fio_cli_start` ```c #define fio_cli_start(argc, argv, unnamed_min, unnamed_max, description, ...) \ fio_cli_start((argc), \ (argv), \ (unnamed_min), \ (unnamed_max), \ (description), \ (fio___cli_line_s[]){__VA_ARGS__, {0}}) SFUNC void fio_cli_start FIO_NOOP(int argc, char const *argv[], int unnamed_min, int unnamed_max, char const *description, fio___cli_line_s *arguments); ``` Parses `argv` into a dictionary of named and unnamed arguments, then prints help and exits on `-h`, `-?`, `-help`, or `--help`. **Named Arguments:** - `argc`, `argv` — values passed to `main`. - `unnamed_min` — minimum required unnamed arguments. `-1` means no limit on unnamed arguments. - `unnamed_max` — maximum allowed unnamed arguments. `-1` means unlimited. - `description` — program description shown in help. The text `NAME` is replaced with `argv[0]`. - `...` — argument description lines using the macros above. Argument names must start with `-`. The first non-`-` word begins the description. Optional defaults go in parentheses: `(default)` or `("literal default")`. Accepted input formats: ```text app -t=1 -p3000 -a localhost app -t 1 -p 3000 -a localhost app --threads=1 --port=3000 --address=localhost ``` **Note:** this function is **not** thread-safe. ```c #define FIO_CLI #include "fio-stl.h" int main(int argc, char const *argv[]) { fio_cli_start(argc, argv, 0, -1, "NAME - a small CLI example.\n", FIO_CLI_PRINT_HEADER("Options:"), FIO_CLI_STRING("-s -str (hello) a string"), FIO_CLI_INT("-i -int (42) an integer"), FIO_CLI_BOOL("-b -bool a boolean flag"), FIO_CLI_PRINT("Boolean flags do not take values.")); fprintf(stderr, "string: %s\n", fio_cli_get("-s")); fprintf(stderr, "int: %" PRId64 "\n", fio_cli_get_i("-i")); fprintf(stderr, "bool: %d\n", (int)fio_cli_get_bool("-b")); fio_cli_end(); return 0; } ``` #### `fio_cli_end` ```c SFUNC void fio_cli_end(void); ``` Frees the parsed CLI dictionary. A destructor runs this automatically at program exit, but calling it earlier lets the memory be reused. **Note:** this function is **not** thread-safe. --- ## Getting Values #### `fio_cli_get` ```c SFUNC char const *fio_cli_get(char const *name); ``` Returns the argument value as a NUL-terminated C string, or `NULL` if the argument was not provided. If `name` is `NULL`, returns the first unnamed argument. #### `fio_cli_get_str` ```c SFUNC fio_buf_info_s fio_cli_get_str(char const *name); ``` Returns the argument value as a `fio_buf_info_s` with length. If `name` is `NULL`, returns the first unnamed argument. #### `fio_cli_get_i` ```c SFUNC int64_t fio_cli_get_i(char const *name); ``` Returns the argument value parsed as an integer, or `0` if it was not provided. Integer parsing accepts decimal, hex, octal, and binary prefixes. #### `fio_cli_get_bool` ```c #define fio_cli_get_bool(name) (fio_cli_get((name)) != NULL) ``` Returns non-zero if the argument was provided, including by default. #### `fio_cli_type` ```c SFUNC fio_cli_arg_e fio_cli_type(char const *name); ``` Returns the declared argument type, or `FIO_CLI_ARG_NONE` if the name is unknown. --- ## Unnamed Arguments #### `fio_cli_unnamed_count` ```c SFUNC unsigned int fio_cli_unnamed_count(void); ``` Returns the number of unnamed arguments collected. #### `fio_cli_unnamed` ```c SFUNC char const *fio_cli_unnamed(unsigned int index); ``` Returns the unnamed argument at `index` as a NUL-terminated string, or `NULL` if `index` is out of bounds. #### `fio_cli_unnamed_str` ```c SFUNC fio_buf_info_s fio_cli_unnamed_str(unsigned int index); ``` Returns the unnamed argument at `index` as a `fio_buf_info_s`, or an empty `fio_buf_info_s` if out of bounds. --- ## Setting Values #### `fio_cli_set` ```c SFUNC void fio_cli_set(char const *name, char const *value); ``` Sets or overwrites a named argument's value. If `name` is `NULL`, appends `value` as a new unnamed argument. **Note:** this function is **not** thread-safe. #### `fio_cli_set_i` ```c SFUNC void fio_cli_set_i(char const *name, int64_t i); ``` Sets a named argument to a base-10 string representation of `i`. **Note:** this function is **not** thread-safe. #### `fio_cli_set_unnamed` ```c SFUNC unsigned int fio_cli_set_unnamed(unsigned int index, const char *value); ``` Sets or appends an unnamed argument. If `index` is past the end, the value is appended. Returns the stored index, or `(unsigned int)-1` if `value` is `NULL` or empty. **Note:** this function is **not** thread-safe. --- ## Iteration #### `fio_cli_each` ```c SFUNC size_t fio_cli_each(int (*task)(fio_buf_info_s name, fio_buf_info_s value, fio_cli_arg_e arg_type, void *udata), void *udata); ``` Calls `task` for every argument that has a value, returning the number of calls made. If `task` returns non-zero, iteration stops. For unnamed arguments, `name.buf` is `NULL` and `name.len` is `0`. ------------------------------------------------------------ # Memory Allocator ```c #define FIO_MEMORY_NAME fio #include "fio-stl.h" ``` A concurrent, per-arena memory allocator that groups objects with similar lifetimes. It tries to keep related allocations in the same chunk so cleanup is cheap and locality is decent. Define `FIO_MEMORY_DISABLE` to route everything to the system `malloc`/`free` instead. The prefix `fio` comes from `FIO_MEMORY_NAME`. All public function names are built with `FIO_NAME(FIO_MEMORY_NAME, ...)`, so a custom prefix produces `my_malloc`, `my_free`, and so on. The module also sets the temporary allocator macros (`FIO_MEM_REALLOC_`, `FIO_MEM_FREE_`, `FIO_MEM_REALLOC_IS_SAFE_`, `FIO_MEM_ALIGNMENT_SIZE_`) for any facil.io type defined in the same `include` scope. **Note:** `FIO_MALLOC` is a shortcut that defines this allocator with prefix `fio` and general-purpose tuning. --- ## Core API #### `fio_malloc` ```c SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, malloc)(size_t size); ``` Allocates `size` bytes aligned to `FIO_MEMORY_ALIGN_SIZE`. Memory is zeroed if `FIO_MEMORY_INITIALIZE_ALLOCATIONS` is enabled. Allocations above the arena limit use big-block mode or `mmap`. **Parameters:** - `size` — bytes to allocate. **Returns:** pointer to allocated memory, or `NULL` on failure. #### `fio_calloc` ```c SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, calloc)(size_t size_per_unit, size_t unit_count); ``` Equivalent to `fio_malloc(size_per_unit * unit_count)`. #### `fio_realloc` ```c SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc)(void *ptr, size_t new_size); ``` Reallocates `ptr` to `new_size`. Copy-free expansion is only attempted for large allocations. Prefer `fio_realloc2` for predictable zeroing. **Parameters:** - `ptr` — existing allocation, or `NULL`. - `new_size` — requested new size. **Returns:** pointer to the new block, or `NULL` on failure. #### `fio_realloc2` ```c SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc2)(void *ptr, size_t new_size, size_t copy_len); ``` Reallocates `ptr` to `new_size`, copying at most `copy_len` bytes. Memory beyond `copy_len` is zeroed when `FIO_MEMORY_INITIALIZE_ALLOCATIONS` is enabled. **Parameters:** - `ptr` — existing allocation, or `NULL`. - `new_size` — requested new size. - `copy_len` — maximum bytes to copy from the old block. **Returns:** pointer to the new block, or `NULL` on failure. #### `fio_free` ```c SFUNC void FIO_NAME(FIO_MEMORY_NAME, free)(void *ptr); ``` Frees memory allocated by this allocator. Freeing a pointer from a different allocator, or after cleanup, is undefined behavior. #### `fio_mmap` ```c SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, mmap)(size_t size); ``` Allocates directly from the system with `mmap` semantics. Use this for large, long-lived objects. The allocation is slower but avoids arena bookkeeping. **Parameters:** - `size` — bytes to allocate. **Returns:** pointer to allocated memory, or `NULL` on failure. #### `fio_malloc_after_fork` ```c SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_after_fork)(void); ``` Reinitializes allocator locks after `fork` in the child process. Best effort only: forking a multi-threaded process is unsafe. Prefer calling `fio_state_callback_force(FIO_CALL_IN_CHILD);` to reset all registered allocators. #### `fio_realloc_is_safe` ```c FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, realloc_is_safe)(void); ``` Returns non-zero if the allocator zeroes allocations, which makes `fio_realloc2` safe for the uncopied tail. --- ## Aligned Allocation API The aligned API requests a minimum pointer alignment. `fio_malloc_aligned` and `fio_calloc_aligned` are thin wrappers around `fio_realloc_aligned`, which is the single entry point. #### `fio_realloc_aligned` ```c SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc_aligned)(void *ptr, size_t new_size, size_t copy_len, size_t alignment); ``` Reallocates `ptr` to `new_size` (copying at most `copy_len` bytes, like `fio_realloc2`), guaranteeing the returned pointer is aligned to at least `alignment`. The `alignment` argument is normalized: - `0` selects the allocator default (`FIO_MEMORY_ALIGN_SIZE`). - Non power-of-2 values are rounded **down** to the nearest power of 2. - The effective alignment is the maximum of the (rounded) request, the current alignment of `ptr`, and `FIO_MEMORY_ALIGN_SIZE` — alignment never regresses across a realloc. - Values above `FIO_MEMORY_SYS_ALLOCATION_SIZE` fail: returns `NULL` and sets `errno` to `EINVAL` (debug builds also trip `FIO_ASSERT_DEBUG`). In-place growth only occurs when the existing pointer already satisfies the requested alignment; otherwise the data is copied to a newly aligned block. `realloc_aligned(NULL, size, 0, alignment)` allocates (like `malloc_aligned`), and `realloc_aligned(ptr, 0, copy_len, alignment)` frees `ptr`. #### `fio_malloc_aligned` ```c SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, malloc_aligned)(size_t size, size_t alignment); ``` Equivalent to `fio_realloc_aligned(NULL, size, 0, alignment)`. A zero `size` returns the shared zero-allocation pointer (aligned only to `FIO_MEMORY_ALIGN_SIZE`), which may be freed or realloc'd normally. #### `fio_calloc_aligned` ```c SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, calloc_aligned)(size_t size_per_unit, size_t unit_count, size_t alignment); ``` Same as `fio_malloc_aligned(size_per_unit * unit_count, alignment)`, but zeroes the (rounded) allocation. Returns `NULL` on size overflow. #### `fio_alloc_size` ```c FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, alloc_size)(size_t minimum_bytes); ``` Returns the number of usable bytes the allocator actually reserves for a request of `minimum_bytes` (the size class): rounded up to `FIO_MEMORY_ALIGN_SIZE` for arena/big-block allocations, and to whole pages (minus the header offset) for page-backed ones. Alignment padding is reserved in addition to the returned value. With `FIO_MEMORY_DISABLE` the system allocator exposes no rounding guarantee and `minimum_bytes` is returned unchanged. ### Alignment abstraction macros ```c FIO_MEM_REALLOC_ALIGNED(ptr, old_size, new_size, copy_len, alignment) FIO_MEM_FREE_ALIGNED(ptr, size) FIO_MEM_ALLOC_SIZE(size) ``` `FIO_MEM_REALLOC_ALIGNED` mirrors `FIO_MEM_REALLOC` with an added `alignment` argument and **assigns** the result to `ptr` (test with `FIO_ASSERT_ALLOC(ptr)` after the call, restoring a saved copy on failure). `FIO_MEM_FREE_ALIGNED` releases memory obtained through the aligned API, and `FIO_MEM_ALLOC_SIZE` mirrors `fio_alloc_size`. The macros route to the active allocator, exactly like `FIO_MEM_REALLOC` does: the fio allocator when it was included (`H___FIO_MALLOC___H`), or portable system-allocator fallbacks otherwise. Template-local variants (`FIO_MEM_REALLOC_ALIGNED_`, `FIO_MEM_FREE_ALIGNED_`) exist for `FIO_MEMORY_NAME` template scope, mirroring `FIO_MEM_REALLOC_` / `FIO_MEM_FREE_`. **Pairing rule:** with the fio allocator, `free` accepts any pointer returned by the aligned API (interior pointers included), so `FIO_MEM_FREE_ALIGNED` is identical to `FIO_MEM_FREE`. Under `FIO_MEMORY_DISABLE` on Windows the aligned API uses `_aligned_malloc` / `_aligned_realloc`, whose memory **must** be released with `FIO_MEM_FREE_ALIGNED` (`_aligned_free`) — plain `free` is heap corruption. On POSIX both map to `free`. Custom allocators that stash alignment bookkeeping near the returned pointer should hook the macro pair, not the fio functions. ### Alignment guarantees per configuration | Configuration | Default alignment | Aligned API backend | Max alignment | |---|---|---|---| | fio allocator (any OS) | `FIO_MEMORY_ALIGN_SIZE` (64) | interior-pointer slice / big-block / page-backed | `FIO_MEMORY_SYS_ALLOCATION_SIZE` | | `FIO_MEMORY_DISABLE`, POSIX | `_Alignof(max_align_t)` | `posix_memalign` + `free` | `FIO_MEMORY_SYS_ALLOCATION_SIZE` | | `FIO_MEMORY_DISABLE`, Windows | `_Alignof(max_align_t)` (16) | `_aligned_malloc` / `_aligned_realloc` / `_aligned_free` | `FIO_MEMORY_SYS_ALLOCATION_SIZE` | | `FIO_MEMORY_DISABLE`, other | `_Alignof(max_align_t)` | over-allocation with stashed raw pointer | `FIO_MEMORY_SYS_ALLOCATION_SIZE` | When `FIO_MALLOC_OVERRIDE_SYSTEM` is defined, the libc aligned-allocation family is overridden as well (`aligned_alloc`, `posix_memalign`, and on Windows `_aligned_malloc` / `_aligned_realloc` / `_aligned_free`), so libc-aligned pointers never reach the fio allocator (and vice versa). --- ## Configuration Macros #### `FIO_MEMORY_NAME` ```c #define FIO_MEMORY_NAME fio ``` Required prefix for the allocator's public symbols. Must be defined before including the header. #### `FIO_MALLOC` ```c #define FIO_MALLOC ``` Shortcut that defines the allocator with prefix `fio` and tunes it for general use. It also redefines `FIO_MEM_REALLOC`, `FIO_MEM_FREE`, `FIO_MEM_REALLOC_IS_SAFE`, and `FIO_MEM_ALIGNMENT_SIZE`. #### `FIO_MEMORY_DISABLE` ```c #define FIO_MEMORY_DISABLE ``` Bypasses the custom allocator and routes all allocations to the system `malloc`/`free`. #### `FIO_MEMORY_ALIGN_LOG` ```c #define FIO_MEMORY_ALIGN_LOG 6 ``` Log2 of the allocation alignment. Clamped to the range `3`–`10` (8 to 1024 bytes). Default is `6` (64 bytes). #### `FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG` ```c #define FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG 21 ``` Log2 of the chunk size allocated from the system. Clamped to `17`–`24`. Default is `21` (~2 MB). #### `FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG` ```c #define FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG 2 ``` Log2 of the number of blocks per system chunk. Range `0`–`4`. Affects fragmentation and the threshold for big allocations. #### `FIO_MEMORY_ENABLE_BIG_ALLOC` ```c #define FIO_MEMORY_ENABLE_BIG_ALLOC 1 ``` When enabled, large allocations can consume an entire chunk as a single big block. May increase fragmentation for long-lived objects. #### `FIO_MEMORY_ARENA_COUNT` ```c #define FIO_MEMORY_ARENA_COUNT -1 ``` Number of arenas. Negative or zero means dynamic selection based on CPU core count, capped by `FIO_MEMORY_ARENA_COUNT_MAX` and fallback `FIO_MEMORY_ARENA_COUNT_FALLBACK`. #### `FIO_MEMORY_ARENA_COUNT_FALLBACK` ```c #define FIO_MEMORY_ARENA_COUNT_FALLBACK 24 ``` Default arena count when dynamic CPU detection fails. #### `FIO_MEMORY_ARENA_COUNT_MAX` ```c #define FIO_MEMORY_ARENA_COUNT_MAX 64 ``` Upper bound for dynamic arena count. #### `FIO_MEMORY_USE_THREAD_MUTEX` ```c #define FIO_MEMORY_USE_THREAD_MUTEX 0 ``` When `1`, arenas protect themselves with `pthread` mutexes instead of spinlocks. Default is `1` when `FIO_MEMORY_ARENA_COUNT` is positive. #### `FIO_MEMORY_INITIALIZE_ALLOCATIONS` ```c #define FIO_MEMORY_INITIALIZE_ALLOCATIONS \ FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT ``` When `1`, allocations return zeroed memory and `realloc2` zeroes uncopied bytes. Default is `1`. #### `FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT` ```c #define FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT 1 ``` Default value for `FIO_MEMORY_INITIALIZE_ALLOCATIONS`. #### `FIO_MEMORY_CACHE_SLOTS` ```c #define FIO_MEMORY_CACHE_SLOTS 4 ``` Number of freed chunks to keep cached before returning memory to the system. #### `FIO_MEMORY_WARMUP` ```c #define FIO_MEMORY_WARMUP 0 ``` If set to a positive number, that many arenas pre-allocate a block at startup. Usually best left at `0`. #### `FIO_MEM_SYS_ALLOC`, `FIO_MEM_SYS_REALLOC`, `FIO_MEM_SYS_FREE` ```c #define FIO_MEM_SYS_ALLOC(pages, alignment_log) /* ... */ #define FIO_MEM_SYS_REALLOC(ptr, old_pages, new_pages, alignment_log) /* ... */ #define FIO_MEM_SYS_FREE(ptr, pages) /* ... */ ``` Override all three to replace the system allocation backend. Alignment is essential and may be large. --- ## Introspection These functions are exposed for debugging. Treat them as unstable. #### `fio_malloc_arenas` ```c SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_arenas)(void); ``` Returns the number of arenas. #### `fio_malloc_block_size` ```c SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_block_size)(void); ``` Returns the block size used for arena allocations. #### `fio_malloc_sys_alloc_size` ```c FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_sys_alloc_size)(void); ``` Returns the system chunk size in bytes. #### `fio_malloc_cache_slots` ```c FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_cache_slots)(void); ``` Returns the configured number of cache slots. #### `fio_malloc_alignment` ```c FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alignment)(void); ``` Returns the allocation alignment in bytes. #### `fio_malloc_alignment_log` ```c FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alignment_log)(void); ``` Returns log2 of the allocation alignment. #### `fio_malloc_alloc_limit` ```c FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alloc_limit)(void); ``` Returns the size above which allocations bypass arena blocks. #### `fio_malloc_arena_alloc_limit` ```c FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_arena_alloc_limit)(void); ``` Returns the arena block size limit. #### `fio_malloc_print_state` ```c SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_state)(void); ``` Prints allocator state to `stderr`. #### `fio_malloc_print_free_block_list` ```c SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_free_block_list)(void); ``` Prints the free-block list to `stderr`. #### `fio_malloc_print_settings` ```c SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_settings)(void); ``` Prints allocator settings to `stderr`. --- ## Example ```c #define FIO_MALLOC #include "fio-stl.h" int main(void) { char *buf = fio_malloc(256); if (!buf) return -1; fio_memcpy(buf, "hello", 5); fprintf(stderr, "%s\n", buf); char *buf2 = fio_realloc2(buf, 512, 5); if (!buf2) { fio_free(buf); return -1; } buf = buf2; fio_free(buf); return 0; } ``` ------------------------------------------------------------ # String Core ```c #define FIO_STR #include "fio-stl.h" ``` Binary-safe string helpers for building, editing, escaping, encoding, and comparing text. This module works with the lightweight `fio_str_info_s` and `fio_buf_info_s` descriptors, plus two owned string types: copy-on-write `fio_bstr` and hash-map key string `fio_keystr_s`. All authoring helpers take a `fio_string_realloc_fn` callback so they can grow either a stack buffer or a heap allocation. --- ## Types #### `fio_string_realloc_fn` ```c typedef int (*fio_string_realloc_fn)(fio_str_info_s *dest, size_t len); ``` Callback that grows `dest->buf` to at least `len + 1` bytes and updates `dest->capa`. Returns `0` on success, `-1` on failure. #### `fio_string_write_s` ```c typedef struct { size_t klass; union { struct { size_t len; const char *buf; } str; double f; int64_t i; uint64_t u; } info; } fio_string_write_s; ``` Argument item used by `fio_string_write2` and `fio_bstr_write2`. The `klass` field selects the union member: `1` = string, `2` = signed int, `3` = unsigned int, `4` = hex, `5` = binary, `6` = double. --- ## Authorship Helpers #### `fio_string_write` ```c SFUNC int fio_string_write(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *restrict src, size_t len); ``` Appends `len` bytes from `src` to `dest`. Grows `dest` if `reallocate` is provided; otherwise truncates when full. Always NUL-terminates on success. **Parameters:** - `dest` — target string descriptor. Must have a valid buffer and `capa >= len`. - `reallocate` — growth callback, may be `NULL`. - `src` — data to append. - `len` — bytes to append. **Returns:** `0` on success, `-1` if reallocation was needed and failed. #### `fio_string_replace` ```c SFUNC int fio_string_replace(fio_str_info_s *dest, fio_string_realloc_fn reallocate, intptr_t start_pos, size_t overwrite_len, const void *src, size_t len); ``` Inserts, overwrites, or deletes a slice. Negative `start_pos` counts from the end (`-1` = last byte). When `overwrite_len` is `0`, the data is inserted. When `len` is `0`, the slice is deleted. **Parameters:** - `start_pos` — byte offset where editing begins. - `overwrite_len` — bytes to overwrite or remove. - `src` — replacement data, ignored if `len == 0`. - `len` — replacement length. **Returns:** `0` on success, `-1` on failure. #### `fio_string_write2` ```c SFUNC int fio_string_write2(fio_str_info_s *restrict dest, fio_string_realloc_fn reallocate, const fio_string_write_s srcs[]); #define fio_string_write2(dest, reallocate, ...) \ fio_string_write2((dest), \ (reallocate), \ (fio_string_write_s[]){__VA_ARGS__, {0}}) ``` Appends a mixed list of strings and numbers in a single pass. The macro builds a temporary `fio_string_write_s[]` array and appends a zero terminator. ```c fio_string_write2(&str, my_realloc, FIO_STRING_WRITE_STR1("The answer is: "), FIO_STRING_WRITE_NUM(42), FIO_STRING_WRITE_STR2(" (0x", 4), FIO_STRING_WRITE_HEX(42), FIO_STRING_WRITE_STR2(")", 1)); ``` #### `FIO_STRING_WRITE_*` ```c #define FIO_STRING_WRITE_STR1(str_) /* string with FIO_STRLEN length */ #define FIO_STRING_WRITE_STR2(str_, len_) /* string with explicit length */ #define FIO_STRING_WRITE_STR_INFO(str_) /* from fio_str_info_s */ #define FIO_STRING_WRITE_NUM(num) /* signed integer */ #define FIO_STRING_WRITE_UNUM(num) /* unsigned integer */ #define FIO_STRING_WRITE_HEX(num) /* hex representation */ #define FIO_STRING_WRITE_BIN(num) /* binary representation */ #define FIO_STRING_WRITE_FLOAT(num) /* double */ ``` Helper macros that build `fio_string_write_s` items for `fio_string_write2`. --- ## Numeral Helpers #### `fio_string_write_i` ```c SFUNC int fio_string_write_i(fio_str_info_s *dest, fio_string_realloc_fn reallocate, int64_t i); ``` Appends a signed base-10 integer. #### `fio_string_write_u` ```c SFUNC int fio_string_write_u(fio_str_info_s *dest, fio_string_realloc_fn reallocate, uint64_t i); ``` Appends an unsigned base-10 integer. #### `fio_string_write_hex` ```c SFUNC int fio_string_write_hex(fio_str_info_s *dest, fio_string_realloc_fn reallocate, uint64_t i); ``` Appends a lowercase hex representation. #### `fio_string_write_bin` ```c SFUNC int fio_string_write_bin(fio_str_info_s *dest, fio_string_realloc_fn reallocate, uint64_t i); ``` Appends a binary representation. --- ## printf Support #### `fio_string_printf` ```c SFUNC FIO___PRINTF_STYLE(3, 0) int fio_string_printf( fio_str_info_s *dest, fio_string_realloc_fn reallocate, const char *format, ...); ``` Appends formatted output, growing the buffer as needed. #### `fio_string_vprintf` ```c SFUNC FIO___PRINTF_STYLE(3, 0) int fio_string_vprintf( fio_str_info_s *dest, fio_string_realloc_fn reallocate, const char *format, va_list argv); ``` `va_list` variant of `fio_string_printf`. --- ## Escaping and Encoding #### `fio_string_write_escape` ```c SFUNC int fio_string_write_escape(fio_str_info_s *restrict dest, fio_string_realloc_fn reallocate, const void *raw, size_t raw_len); ``` Escapes `raw` using JSON string rules and appends the result. #### `fio_string_write_unescape` ```c SFUNC int fio_string_write_unescape(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *enscaped, size_t enscaped_len); ``` Unescapes JSON-style data and appends it. #### `fio_string_write_base32enc` / `fio_string_write_base32dec` ```c SFUNC int fio_string_write_base32enc(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *raw, size_t raw_len); SFUNC int fio_string_write_base32dec(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *encoded, size_t encoded_len); ``` Base32 encode / decode. #### `fio_string_write_base64enc` / `fio_string_write_base64dec` ```c SFUNC int fio_string_write_base64enc(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *raw, size_t raw_len, uint8_t url_encoded); SFUNC int fio_string_write_base64dec(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *encoded, size_t encoded_len); ``` Base64 encode / decode. Pass `url_encoded = 1` to use URL-safe alphabet. #### `fio_string_write_url_enc` / `fio_string_write_url_dec` / `fio_string_write_path_dec` ```c SFUNC int fio_string_write_url_enc(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *raw, size_t raw_len); SFUNC int fio_string_write_url_dec(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *encoded, size_t encoded_len); SFUNC int fio_string_write_path_dec(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *encoded, size_t encoded_len); ``` URL (percent) encoding and decoding. `url_dec` converts `+` to space; `path_dec` does not. #### `fio_string_write_html_escape` / `fio_string_write_html_unescape` ```c SFUNC int fio_string_write_html_escape(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *raw, size_t raw_len); SFUNC int fio_string_write_html_unescape(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *enscaped, size_t enscaped_len); ``` HTML escape / unescape. The unescape implementation is minimal and incomplete. --- ## File Reading #### `fio_string_readfd` ```c SFUNC int fio_string_readfd(fio_str_info_s *dest, fio_string_realloc_fn reallocate, int fd, intptr_t start_at, size_t limit); ``` Reads up to `limit` bytes from seekable file descriptor `fd` starting at `start_at`. Negative `start_at` counts from EOF. `limit == 0` reads to EOF. #### `fio_string_readfile` ```c SFUNC int fio_string_readfile(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const char *filename, intptr_t start_at, size_t limit); ``` Opens `filename` and reads its contents (or a slice) into `dest`. #### `fio_string_getdelim_fd` ```c SFUNC int fio_string_getdelim_fd(fio_str_info_s *dest, fio_string_realloc_fn reallocate, int fd, intptr_t start_at, char delim, size_t limit); ``` Reads from `fd` until `delim` or EOF. #### `fio_string_getdelim_file` ```c SFUNC int fio_string_getdelim_file(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const char *filename, intptr_t start_at, char delim, size_t limit); ``` Same as `fio_string_getdelim_fd` but opens and closes the file. --- ## Memory Helpers #### `fio_string_capa4len` ```c FIO_IFUNC size_t fio_string_capa4len(size_t new_len); ``` Returns a 16-byte-aligned capacity for `new_len`. #### Default Allocator Callbacks ```c #define FIO_STRING_SYS_REALLOC fio_string_sys_reallocate #define FIO_STRING_REALLOC fio_string_default_reallocate #define FIO_STRING_ALLOC_COPY fio_string_default_allocate_copy #define FIO_STRING_ALLOC_KEY fio_string_default_key_alloc #define FIO_STRING_FREE fio_string_default_free #define FIO_STRING_FREE2 fio_string_default_free2 #define FIO_STRING_FREE_KEY fio_string_default_free_key #define FIO_STRING_FREE_NOOP fio_string_default_free_noop #define FIO_STRING_FREE_NOOP2 fio_string_default_free_noop2 ``` Convenience macros pointing to the default callbacks. #### `fio_string_default_reallocate` ```c SFUNC int fio_string_default_reallocate(fio_str_info_s *dst, size_t len); ``` Grows `dst` using the current facil.io allocator (`FIO_MEM_REALLOC_`). #### `fio_string_default_allocate_copy` ```c SFUNC int fio_string_default_allocate_copy(fio_str_info_s *dest, size_t new_capa); ``` Allocates a new buffer and copies existing data into it. #### `fio_string_default_free` ```c SFUNC void fio_string_default_free(void *); ``` Frees a buffer allocated with the default callbacks. --- ## UTF-8 Helpers #### `fio_string_utf8_valid` ```c SFUNC bool fio_string_utf8_valid(fio_str_info_s str); ``` Returns `true` if `str` is valid UTF-8. #### `fio_string_utf8_len` ```c SFUNC size_t fio_string_utf8_len(fio_str_info_s str); ``` Returns the number of Unicode code points, or `0` if invalid. #### `fio_string_utf8_valid_code_point` ```c SFUNC size_t fio_string_utf8_valid_code_point(const void *u8c, size_t buf_len); ``` Returns the byte length of the UTF-8 code point at `u8c` (`1`–`4`), or `0` if invalid. #### `fio_string_utf8_select` ```c SFUNC int fio_string_utf8_select(fio_str_info_s str, intptr_t *pos, size_t *len); ``` Converts a UTF-8 character range (`pos`, `len`) into raw byte offsets. Returns `-1` on error and sets `pos` to `-1` if the string is invalid before the selection. --- ## Comparison Helpers #### `fio_string_is_greater_buf` ```c SFUNC int fio_string_is_greater_buf(fio_buf_info_s a, fio_buf_info_s b); ``` Returns `1` if `a` is lexicographically greater than `b`; `0` otherwise (including equality). #### `fio_string_is_greater` ```c FIO_IFUNC int fio_string_is_greater(fio_str_info_s a, fio_str_info_s b); ``` Same comparison for `fio_str_info_s`. --- ## Binary String (`fio_bstr`) A reference-counted, copy-on-write, NUL-terminated binary string. The public pointer points at the first character; metadata lives just before it. #### `fio_bstr_reserve` ```c FIO_IFUNC char *fio_bstr_reserve(char *bstr, size_t len); ``` Ensures at least `len` additional bytes are available. #### `fio_bstr_copy` ```c FIO_IFUNC char *fio_bstr_copy(char *bstr); ``` Returns a shallow copy, incrementing the reference count. Falls back to a real copy if the reference count overflows. #### `fio_bstr_free` ```c FIO_IFUNC void fio_bstr_free(char *bstr); ``` Decrements the reference count and frees memory when it reaches zero. #### `fio_bstr_info` / `fio_bstr_buf` / `fio_bstr_len` / `fio_bstr_len_set` ```c FIO_IFUNC fio_str_info_s fio_bstr_info(const char *bstr); FIO_IFUNC fio_buf_info_s fio_bstr_buf(const char *bstr); FIO_IFUNC size_t fio_bstr_len(const char *bstr); FIO_IFUNC char *fio_bstr_len_set(char *bstr, size_t len); ``` Inspect or resize the string. `fio_bstr_len_set` may reallocate if the string is shared or too small. #### `fio_bstr_write` and Friends ```c FIO_IFUNC char *fio_bstr_write(char *bstr, const void *src, size_t len); FIO_IFUNC char *fio_bstr_replace(char *bstr, intptr_t start_pos, size_t overwrite_len, const void *src, size_t len); FIO_IFUNC char *fio_bstr_write2(char *bstr, const fio_string_write_s srcs[]); #define fio_bstr_write2(bstr, ...) \ fio_bstr_write2(bstr, (fio_string_write_s[]){__VA_ARGS__, {0}}) FIO_IFUNC char *fio_bstr_write_i(char *bstr, int64_t num); FIO_IFUNC char *fio_bstr_write_u(char *bstr, uint64_t num); FIO_IFUNC char *fio_bstr_write_hex(char *bstr, uint64_t num); FIO_IFUNC char *fio_bstr_write_bin(char *bstr, uint64_t num); FIO_IFUNC char *fio_bstr_write_escape(char *bstr, const void *src, size_t len); FIO_IFUNC char *fio_bstr_write_unescape(char *bstr, const void *src, size_t len); FIO_IFUNC char *fio_bstr_write_base64enc(char *bstr, const void *src, size_t len, uint8_t url_encoded); FIO_IFUNC char *fio_bstr_write_base64dec(char *bstr, const void *src, size_t len); FIO_IFUNC char *fio_bstr_write_url_enc(char *bstr, const void *data, size_t len); FIO_IFUNC char *fio_bstr_write_url_dec(char *bstr, const void *encoded, size_t len); FIO_IFUNC char *fio_bstr_write_html_escape(char *bstr, const void *raw, size_t len); FIO_IFUNC char *fio_bstr_write_html_unescape(char *bstr, const void *escaped, size_t len); FIO_IFUNC char *fio_bstr_readfd(char *bstr, int fd, intptr_t start_at, intptr_t limit); FIO_IFUNC char *fio_bstr_readfile(char *bstr, const char *filename, intptr_t start_at, intptr_t limit); FIO_IFUNC char *fio_bstr_getdelim_file(char *bstr, const char *filename, intptr_t start_at, char delim, size_t limit); FIO_IFUNC char *fio_bstr_getdelim_fd(char *bstr, int fd, intptr_t start_at, char delim, size_t limit); FIO_IFUNC char *fio_bstr_printf(char *bstr, const char *format, ...); ``` Every `fio_bstr_*` function returns the possibly-reallocated string pointer. Always assign the result back. #### `fio_bstr_is_greater` / `fio_bstr_is_eq` / `fio_bstr_is_eq2info` / `fio_bstr_is_eq2buf` ```c FIO_SFUNC int fio_bstr_is_greater(const char *a, const char *b); FIO_SFUNC int fio_bstr_is_eq(const char *a_, const char *b_); FIO_SFUNC int fio_bstr_is_eq2info(const char *a_, fio_str_info_s b); FIO_SFUNC int fio_bstr_is_eq2buf(const char *a_, fio_buf_info_s b); ``` Comparison helpers. #### `fio_bstr_reallocate` ```c SFUNC int fio_bstr_reallocate(fio_str_info_s *dest, size_t len); ``` Default reallocate callback for `fio_bstr`. --- ## Key String (`fio_keystr_s`) A compact key type used by hash maps. Small strings are embedded in the struct; larger ones store a pointer and length. Not NUL-terminated. #### `fio_keystr_tmp` ```c FIO_IFUNC fio_keystr_s fio_keystr_tmp(const char *buf, uint32_t len); ``` Returns a temporary key referencing `buf`/`len`. #### `fio_keystr_init` ```c FIO_SFUNC fio_keystr_s fio_keystr_init(fio_str_info_s str, void *(*alloc_func)(size_t len)); ``` Copies `str` into a persistent key. Strings short enough are embedded; otherwise `alloc_func` is used. Pass `capa == FIO_KEYSTR_CONST` to store a borrowed pointer. #### `fio_keystr_destroy` ```c FIO_SFUNC void fio_keystr_destroy(fio_keystr_s *key, void (*free_func)(void *, size_t)); ``` Releases a key allocated by `fio_keystr_init`. #### `fio_keystr_buf` / `fio_keystr_info` / `fio_keystr_is_eq` / `fio_keystr_is_eq2` / `fio_keystr_is_eq3` / `fio_keystr_hash` ```c FIO_IFUNC fio_buf_info_s fio_keystr_buf(fio_keystr_s *str); FIO_IFUNC fio_str_info_s fio_keystr_info(fio_keystr_s *str); FIO_IFUNC int fio_keystr_is_eq(fio_keystr_s a, fio_keystr_s b); FIO_IFUNC int fio_keystr_is_eq2(fio_keystr_s a_, fio_str_info_s b); FIO_IFUNC int fio_keystr_is_eq3(fio_keystr_s a_, fio_buf_info_s b); FIO_IFUNC uint64_t fio_keystr_hash(fio_keystr_s a); ``` Inspect, compare, and hash key strings. #### `FIO_KEYSTR_CONST` ```c #define FIO_KEYSTR_CONST ((size_t)-1LL) ``` Magic `capa` value for `fio_keystr_init` that keeps a borrowed pointer instead of allocating. --- ## Example ```c #define FIO_STR #include "fio-stl.h" int my_realloc(fio_str_info_s *dest, size_t len) { size_t capa = fio_string_capa4len(len); char *tmp = realloc(dest->buf, capa); if (!tmp) return -1; dest->buf = tmp; dest->capa = capa; return 0; } int main(void) { char buf[32]; fio_str_info_s str = FIO_STR_INFO3(buf, 0, 32); fio_string_write(&str, my_realloc, "answer: ", 8); fio_string_write_i(&str, my_realloc, 42); fprintf(stderr, "%s\n", str.buf); char *b = NULL; b = fio_bstr_write(b, "hello ", 6); b = fio_bstr_write(b, "world", 5); fprintf(stderr, "%s (len=%zu)\n", b, fio_bstr_len(b)); fio_bstr_free(b); return 0; } ``` ------------------------------------------------------------ # GFM Markdown Parser ```c #define FIO_GFM #include "fio-stl.h" ``` A flat-state, non-recursive parser for GitHub Flavored Markdown. It emits block and inline events through three callbacks — push, write, pop — without allocating heap memory itself. `fio_gfm_parse` is the only public entry point. The parser depends on `FIO_ENTITY` for HTML entity decoding. --- ## Configuration Macros #### `FIO_GFM_MAX_DEPTH` ```c #define FIO_GFM_MAX_DEPTH 255 ``` Maximum container nesting depth. Must fit in a byte (≤ 255). #### `FIO_GFM_MAX_TABLE_COLUMNS` ```c #define FIO_GFM_MAX_TABLE_COLUMNS 64 ``` Maximum table columns. Alignment is bit-packed, so four columns fit in one byte. #### `FIO_GFM_REF_CACHE_SIZE` ```c #define FIO_GFM_REF_CACHE_SIZE 128 ``` Maximum reference definitions held in the fast inline cache. --- ## Error Codes #### `FIO_GFM_ERR_GENERIC`, `FIO_GFM_ERR_DEPTH`, `FIO_GFM_ERR_INPUT` ```c #define FIO_GFM_ERR_GENERIC -1 #define FIO_GFM_ERR_DEPTH -2 #define FIO_GFM_ERR_INPUT -3 ``` Negative error codes used internally and returned through `fio_gfm_parse`. --- ## Flags #### `FIO_GFM_F_TIGHT` ```c #define FIO_GFM_F_TIGHT ((uint8_t)1U << 0) ``` The list is tight (no blank lines between items). #### `FIO_GFM_F_LOOSE_SEEN` ```c #define FIO_GFM_F_LOOSE_SEEN ((uint8_t)1U << 1) ``` A blank line was seen between list items. #### `FIO_GFM_F_TASK` ```c #define FIO_GFM_F_TASK ((uint8_t)1U << 2) ``` The list item is a task-list item. #### `FIO_GFM_F_TASK_CHECKED` ```c #define FIO_GFM_F_TASK_CHECKED ((uint8_t)1U << 3) ``` The task-list marker is checked (`[x]`). --- ## Types #### `fio_gfm_type_e` ```c typedef enum { FIO_GFM_PARAGRAPH = 1, FIO_GFM_HEADING, FIO_GFM_THEMATIC_BREAK, FIO_GFM_BLOCKQUOTE, FIO_GFM_LIST_UNORDERED, FIO_GFM_LIST_ORDERED, FIO_GFM_LIST_ITEM, FIO_GFM_CODE_BLOCK, FIO_GFM_HTML_BLOCK, FIO_GFM_TABLE, FIO_GFM_TABLE_ROW, FIO_GFM_TABLE_CELL, FIO_GFM_EMPHASIS, FIO_GFM_STRONG, FIO_GFM_STRIKETHROUGH, FIO_GFM_LINK, FIO_GFM_TEXT, FIO_GFM_SOFT_BREAK, FIO_GFM_HARD_BREAK, FIO_GFM_CODE_SPAN, FIO_GFM_IMAGE, FIO_GFM_AUTOLINK, FIO_GFM_INLINE_HTML, FIO_GFM_FOOTNOTE_REF, } fio_gfm_type_e; ``` Event type. Values `1`–`12` are block sections; `13`–`16` are inline sections; `17`–`23` are text and leaf content emitted with `write`. #### `fio_gfm_align_e` ```c typedef enum { FIO_GFM_ALIGN_NONE = 0, FIO_GFM_ALIGN_LEFT, FIO_GFM_ALIGN_RIGHT, FIO_GFM_ALIGN_CENTER, } fio_gfm_align_e; ``` Table cell alignment. #### `fio_gfm_event_s` ```c typedef struct { void *udata; fio_buf_info_s source; fio_buf_info_s text; fio_buf_info_s marker; fio_buf_info_s info; fio_buf_info_s destination; fio_buf_info_s title; fio_buf_info_s reference; uint32_t list_start; uint16_t columns; uint16_t column; uint8_t type; uint8_t heading_level; uint8_t flags; uint8_t align; uint8_t padding; } fio_gfm_event_s; ``` Event passed to every callback. **Fields:** - `udata` — live user pointer; the parser copies it back after each callback. - `source` — full source slice for this event. - `text` — literal text / label / code content. - `marker` — marker slice (`#`, fence, list bullet, etc.). - `info` — fenced code info string (language tag). - `destination` — link/image/autolink URL. - `title` — link/image title. - `reference` — reference label for ref-style links/images. - `list_start` — ordered list start number. - `columns` / `column` — table column count and cell index. - `type` — `fio_gfm_type_e` value. - `heading_level` — `1`–`6` for headings. - `flags` — `FIO_GFM_F_*`. - `align` — `fio_gfm_align_e` for table cells. - `padding` — virtual leading spaces from tab expansion. #### `fio_gfm_callbacks_s` ```c typedef struct { int (*push)(fio_gfm_event_s *e); int (*write)(fio_gfm_event_s *e); int (*pop)(fio_gfm_event_s *e); } fio_gfm_callbacks_s; ``` Parser callbacks. All are optional. Return `0` to continue, non-zero to abort. Positive values are user errors; negative values are reserved for parser errors. --- ## Parsing #### `fio_gfm_parse` ```c SFUNC size_t fio_gfm_parse(const fio_gfm_callbacks_s *callbacks, void *udata, fio_buf_info_s source); ``` Parses a complete Markdown document and emits events. The document must be fully available in `source`; streaming is not supported. **Parameters:** - `callbacks` — pointer to callback struct. - `udata` — user pointer copied into every event; callbacks may update it. - `source` — full document bytes. **Returns:** the number of bytes consumed. If this equals `source.len`, parsing completed. A smaller value means a callback aborted or a parser error occurred. --- ## Example ```c #define FIO_GFM #include "fio-stl.h" static int on_push(fio_gfm_event_s *e) { fprintf(stderr, "open type=%u\n", e->type); return 0; } static int on_write(fio_gfm_event_s *e) { fprintf(stderr, "text: %.*s\n", (int)e->text.len, e->text.buf); return 0; } static int on_pop(fio_gfm_event_s *e) { fprintf(stderr, "close type=%u\n", e->type); return 0; } int main(void) { fio_gfm_callbacks_s cb = { .push = on_push, .write = on_write, .pop = on_pop, }; fio_buf_info_s src = FIO_BUF_INFO2("# Hello\n\nworld.\n", 16); size_t n = fio_gfm_parse(&cb, NULL, src); fprintf(stderr, "consumed %zu of %zu bytes\n", n, src.len); return 0; } ``` ------------------------------------------------------------ # POSIX Portable Polling API ```c #define FIO_POLL #include "fio-stl.h" ``` A small abstraction over `poll`, `epoll`, and `kqueue`. Define `FIO_POLL_ENGINE_POLL`, `FIO_POLL_ENGINE_EPOLL`, or `FIO_POLL_ENGINE_KQUEUE` before inclusion to force a backend; otherwise the best available engine is selected automatically. The API is the same for every backend. Backend-specific details live in: - [`102 poll poll.md`](https://facil.io/0.8.x/posix-portable-polling-api/) - [`102 poll epoll.md`](https://facil.io/0.8.x/posix-polling-epoll-backend/) - [`102 poll kqueue.md`](https://facil.io/0.8.x/posix-polling-kqueue-backend/) --- ## Configuration Macros #### `FIO_POLL_POSSIBLE_FLAGS` ```c #define FIO_POLL_POSSIBLE_FLAGS (POLLIN | POLLOUT | POLLPRI) ``` Mask of event flags the polling layer recognizes. #### `FIO_POLL_MAX_EVENTS` ```c #define FIO_POLL_MAX_EVENTS 128 ``` Maximum events returned in one `fio_poll_review` call. Relevant for epoll and kqueue. Larger on 32-bit platforms. --- ## Types #### `fio_poll_s` ```c typedef struct fio_poll_s fio_poll_s; ``` Opaque polling object. The actual layout depends on the selected backend. #### `fio_poll_settings_s` ```c typedef struct { void (*on_data)(void *udata); void (*on_ready)(void *udata); void (*on_close)(void *udata); } fio_poll_settings_s; ``` Callback settings. Each callback receives the `udata` pointer supplied when the file descriptor was monitored. - `on_data` — data is available to read. - `on_ready` — the socket is ready to write. - `on_close` — connection closed or an error occurred. --- ## Functions #### `fio_poll_engine` ```c FIO_IFUNC const char *fio_poll_engine(void); ``` Returns the name of the active backend (`"epoll"`, `"kqueue"`, or `"poll"`) as a constant string. #### `fio_poll_init` ```c FIO_IFUNC void fio_poll_init(fio_poll_s *p, fio_poll_settings_s settings); #define fio_poll_init(p, ...) \ fio_poll_init((p), (fio_poll_settings_s){__VA_ARGS__}) ``` Initializes the polling object. The macro form accepts designated initializers. **Parameters:** - `p` — pointer to the polling object. - `settings` — callback configuration. ```c fio_poll_init(&poll, .on_data = on_data, .on_ready = on_ready, .on_close = on_close); ``` #### `fio_poll_destroy` ```c FIO_IFUNC void fio_poll_destroy(fio_poll_s *p); ``` Frees backend resources held by the polling object. #### `fio_poll_monitor` ```c SFUNC int fio_poll_monitor(fio_poll_s *p, fio_socket_i fd, void *udata, unsigned short flags); ``` Adds `fd` to the poll set, updates its `udata`, or adds events. Recognized flags are `POLLIN`, `POLLOUT`, and `POLLPRI`; other flags may be ignored. Monitoring is one-shot: once an event fires, it is removed until re-armed. **Returns:** `0` on success, `-1` on error. #### `fio_poll_review` ```c SFUNC int fio_poll_review(fio_poll_s *p, size_t timeout); ``` Waits up to `timeout` milliseconds for events and dispatches callbacks. Adding a file descriptor from another thread while `fio_poll_review` is running will not include that descriptor until the next call. **Returns:** the number of events dispatched, or `-1` on error. #### `fio_poll_forget` ```c SFUNC int fio_poll_forget(fio_poll_s *p, fio_socket_i fd); ``` Removes `fd` from the poll set. **Returns:** `0` on success, `-1` on error. --- ## Example ```c #define FIO_POLL #include "fio-stl.h" static void on_data(void *udata) { fprintf(stderr, "readable\n"); } static void on_ready(void *udata) { fprintf(stderr, "writable\n"); } static void on_close(void *udata) { fprintf(stderr, "closed\n"); } int main(void) { fio_poll_s p; fio_poll_init(&p, .on_data = on_data, .on_ready = on_ready, .on_close = on_close); fio_poll_monitor(&p, some_fd, NULL, POLLIN); for (int i = 0; i < 10; ++i) fio_poll_review(&p, 1000); fio_poll_destroy(&p); return 0; } ``` ------------------------------------------------------------ # POSIX Polling — epoll Backend ```c #define FIO_POLL_ENGINE_EPOLL #define FIO_POLL #include "fio-stl.h" ``` Implementation of the portable polling API on top of Linux `epoll`. Normally selected automatically on Linux; define `FIO_POLL_ENGINE_EPOLL` to force it. The public API is documented in [`102 poll api.md`](https://facil.io/0.8.x/posix-portable-polling-api/). This file describes backend-specific behavior. --- ## Backend Details #### `struct fio_poll_s` ```c struct fio_poll_s { fio_poll_settings_s settings; struct pollfd fds[2]; int fd[2]; }; ``` Two internal `epoll` file descriptors are used: one for `POLLOUT` readiness and one for `POLLIN`/error events. A `pollfd` pair wakes the internal `poll` used to wait on both epoll FDs at once. #### `fio_poll_engine` Returns `"epoll"`. #### Fork Safety A state callback re-creates the epoll FDs in the child process after `fork`. --- ## Notes - Events are monitored in one-shot mode (`EPOLLONESHOT`). After an event fires, the descriptor must be re-armed with `fio_poll_monitor`. - Error and hang-up events are dispatched through the read-side epoll FD and delivered to `on_close`. - `EPOLLRDHUP` and `EPOLLHUP` are always included in the monitored events. ------------------------------------------------------------ # POSIX Polling — kqueue Backend ```c #define FIO_POLL_ENGINE_KQUEUE #define FIO_POLL #include "fio-stl.h" ``` Implementation of the portable polling API on top of BSD/macOS `kqueue`. Normally selected automatically when `` is available; define `FIO_POLL_ENGINE_KQUEUE` to force it. The public API is documented in [`102 poll api.md`](https://facil.io/0.8.x/posix-portable-polling-api/). This file describes backend-specific behavior. --- ## Backend Details #### `struct fio_poll_s` ```c struct fio_poll_s { fio_poll_settings_s settings; int fd; }; ``` A single kqueue descriptor is used for both read and write filters. #### `fio_poll_engine` Returns `"kqueue"`. #### Fork Safety A state callback re-creates the kqueue descriptor in the child process after `fork`. --- ## Notes - Read and write filters are registered with `EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT`. - `EV_EOF` and `EV_ERROR` flags are routed to `on_close`. - `fio_poll_review` uses a `timespec` built from the millisecond timeout. ------------------------------------------------------------ # POSIX Polling — poll Backend ```c #define FIO_POLL_ENGINE_POLL #define FIO_POLL #include "fio-stl.h" ``` Implementation of the portable polling API on top of POSIX `poll()` / Windows `WSAPoll`. This is the fallback backend and is selected automatically when neither epoll nor kqueue is available. The public API is documented in [`102 poll api.md`](https://facil.io/0.8.x/posix-portable-polling-api/). This file describes backend-specific behavior. --- ## Backend Details #### `struct fio_poll_s` ```c struct fio_poll_s { fio_poll_settings_s settings; fio___poll_map_s map; FIO___LOCK_TYPE lock; }; ``` The poll backend keeps monitored descriptors in an internal imap (`fio___poll_map_s`) and snapshots its armed flags before each `poll()` call. Descriptors added or re-armed during `fio_poll_review` update the retained map directly; surviving one-shot flags are restored when the call returns. `fio_poll_forget` removes the descriptor from the retained map, which also prevents any surviving snapshot flags from being restored. It is not a cancellation guarantee: an event already returned by an in-flight `poll()` call may still be dispatched. This matches the epoll/kqueue backends, whose in-hand event lists always dispatch. As on every backend, `udata` lifetime during dispatch is the caller's responsibility (the facil.io IO layer only forgets from the reactor thread, never concurrently with a review). One-shot flag accounting strips whole event groups: `WSAPoll` reports sub-band bits (e.g. `POLLRDNORM`) while `POLLIN`/`POLLOUT` are supersets, so a raw bitwise strip would leave band bits (e.g. `POLLRDBAND`) armed on Windows. Fired `POLLIN`/`POLLOUT` groups are therefore stripped in full. #### `fio_poll_engine` Returns `"poll"`. #### `fio_poll_close_all` ```c SFUNC void fio_poll_close_all(fio_poll_s *p); ``` Additional helper available only in the poll backend. Closes every monitored socket and calls `on_close` for each one. Useful during shutdown. --- ## Notes - `POLLRDHUP` is used when available; otherwise the backend relies on `POLLHUP`, `POLLERR`, and `POLLNVAL` for close/error detection. - On Windows, `POLLPRI` is omitted because `WSAPoll` rejects it. - Fired events are stripped from the descriptor’s flags (one-shot semantics). Surviving flags are restored to retained entries after `poll()` returns; concurrent removals (which delete the retained entry) prevent that restoration. ------------------------------------------------------------ # Task and Timer Queues ```c #define FIO_QUEUE #include "fio-stl.h" ``` A thread-safe task queue built from linked ring buffers, plus a timer queue that moves due events into a task queue. Worker threads can be attached to drain the queue in the background. --- ## Configuration #### `FIO_QUEUE_TASKS_PER_ALLOC` ```c #define FIO_QUEUE_TASKS_PER_ALLOC 168 /* 338 on 32-bit */ ``` Tasks per ring-buffer chunk. Default is chosen so `fio_queue_s` fits in one page. Must not exceed 65535. --- ## Task Queue Types #### `fio_queue_task_s` ```c typedef struct { void (*fn)(void *, void *); void *udata1; void *udata2; } fio_queue_task_s; ``` A single task. `fn` receives both `udata` pointers. Tasks with `fn == NULL` are ignored. #### `fio_queue_s` ```c typedef struct { fio___task_ring_s *r; fio___task_ring_s *w; uint32_t count; FIO___LOCK_TYPE lock; FIO_LIST_NODE consumers; fio___task_ring_s mem; } fio_queue_s; ``` Queue object. Treat as opaque. Allocate on the stack or with `fio_queue_new`. --- ## Task Queue API #### `FIO_QUEUE_STATIC_INIT` ```c #define FIO_QUEUE_STATIC_INIT(queue) /* ... */ ``` Static initializer for a global queue. Prefer `fio_queue_init` at runtime when possible — it initializes only the bytes that matter. #### `fio_queue_init` ```c FIO_IFUNC void fio_queue_init(fio_queue_s *q); ``` Initializes `q` in place. #### `fio_queue_destroy` ```c SFUNC void fio_queue_destroy(fio_queue_s *q); ``` Frees ring buffers, stops and joins workers, and re-initializes `q`. Pending tasks are performed only if the queue has workers (see `fio_queue_workers_join`); otherwise they are discarded without being performed. After destruction the queue may be reused, but on some platforms the lock may need explicit re-initialization. #### `fio_queue_new` ```c SFUNC fio_queue_s *fio_queue_new(void); ``` Allocates and initializes a queue on the heap. #### `fio_queue_free` ```c SFUNC void fio_queue_free(fio_queue_s *q); ``` Destroys `q` and frees the queue object itself. #### `fio_queue_push` ```c SFUNC int fio_queue_push(fio_queue_s *q, fio_queue_task_s task); #define fio_queue_push(q, ...) \ fio_queue_push((q), (fio_queue_task_s){__VA_ARGS__}) ``` Pushes a task to the tail of the queue. The macro accepts named arguments. **Returns:** `0` on success, `-1` on memory error. ```c fio_queue_push(&q, .fn = my_task, .udata1 = arg); ``` #### `fio_queue_push_urgent` ```c SFUNC int fio_queue_push_urgent(fio_queue_s *q, fio_queue_task_s task); #define fio_queue_push_urgent(q, ...) \ fio_queue_push_urgent((q), (fio_queue_task_s){__VA_ARGS__}) ``` Pushes a task to the head of the queue (LIFO). **Returns:** `0` on success, `-1` on memory error. #### `fio_queue_pop` ```c SFUNC fio_queue_task_s fio_queue_pop(fio_queue_s *q); ``` Removes and returns the next task (FIFO). The returned task has `fn == NULL` if the queue was empty. #### `fio_queue_perform` ```c SFUNC int fio_queue_perform(fio_queue_s *q); ``` Pops and performs one task. Returns `-1` if the queue was empty. #### `fio_queue_perform_all` ```c SFUNC void fio_queue_perform_all(fio_queue_s *q); ``` Performs every task currently in the queue. #### `fio_queue_count` ```c FIO_IFUNC uint32_t fio_queue_count(fio_queue_s *q); ``` Returns the number of pending tasks. --- ## Worker Threads #### `fio_queue_workers_add` ```c SFUNC int fio_queue_workers_add(fio_queue_s *q, size_t count); ``` Spawns `count` consumer threads that automatically perform tasks as they arrive. Threads sleep on a condition variable when idle. **Returns:** `0` on success, `-1` on thread creation failure. #### `fio_queue_workers_stop` ```c SFUNC size_t fio_queue_workers_stop(fio_queue_s *q); ``` Signals all workers to stop. Returns immediately without waiting. Pending tasks may remain in the queue (they can be performed later using `fio_queue_perform_all` or discarded by `fio_queue_destroy`). **Returns:** the number of worker groups signaled (one per successful `fio_queue_workers_add` call still attached to the queue). #### `fio_queue_workers_join` ```c SFUNC void fio_queue_workers_join(fio_queue_s *q); ``` Signals workers to stop and blocks until they terminate. If the queue had workers, any pending tasks are performed before the function returns (so `fio_queue_destroy`, which calls `fio_queue_workers_join`, drains a queue that has workers). A queue without workers is not drained: `fio_queue_destroy` on such a queue discards pending tasks without performing them. #### `fio_queue_workers_wake` ```c SFUNC void fio_queue_workers_wake(fio_queue_s *q); ``` Wakes all workers to check for new tasks. Called automatically on push. --- ## Timer Queue Types #### `fio_timer_queue_s` ```c typedef struct { fio___timer_event_s *next; FIO___LOCK_TYPE lock; } fio_timer_queue_s; ``` Opaque timer queue. #### `FIO_TIMER_QUEUE_INIT` ```c #define FIO_TIMER_QUEUE_INIT /* ... */ ``` Static initializer for a timer queue. #### `fio_timer_schedule_args_s` ```c typedef struct { int (*fn)(void *, void *); void *udata1; void *udata2; void (*on_finish)(void *, void *); uint32_t every; int32_t repetitions; int64_t start_at; } fio_timer_schedule_args_s; ``` Timer schedule arguments. **Members:** - `fn` — callback. Return non-zero to stop the timer. - `udata1`, `udata2` — opaque data passed to `fn` and `on_finish`. - `on_finish` — called when the timer stops. - `every` — interval in milliseconds. - `repetitions` — repeat count; `-1` means forever. - `start_at` — base time in milliseconds; `0` uses `fio_time_milli()`. --- ## Timer Queue API #### `fio_timer_schedule` ```c SFUNC void fio_timer_schedule(fio_timer_queue_s *timer_queue, fio_timer_schedule_args_s args); #define fio_timer_schedule(timer_queue, ...) \ fio_timer_schedule((timer_queue), (fio_timer_schedule_args_s){__VA_ARGS__}) ``` Adds a timed event to the timer queue. The macro accepts named arguments. ```c fio_timer_schedule(&timers, .fn = tick, .udata1 = ctx, .every = 1000, .repetitions = -1); ``` #### `fio_timer_push2queue` ```c SFUNC size_t fio_timer_push2queue(fio_queue_s *queue, fio_timer_queue_s *timer_queue, int64_t now_in_milliseconds); ``` Moves all due timer events into `queue`. Pass `0` for `now_in_milliseconds` to use `fio_time_milli()`. **Returns:** number of timers pushed. #### `fio_timer_next_at` ```c FIO_IFUNC int64_t fio_timer_next_at(fio_timer_queue_s *timer_queue); ``` Returns the due time of the next event, or `INT64_MAX` if the queue is empty. #### `fio_timer_destroy` ```c SFUNC void fio_timer_destroy(fio_timer_queue_s *timer_queue); ``` Cancels all pending timers. Do not free the timer queue while timer tasks may still be queued in a `fio_queue_s`, because repeating timers reschedule themselves. --- ## Example ```c #define FIO_QUEUE #include "fio-stl.h" void work(void *a, void *b) { (void)a; (void)b; fprintf(stderr, "working\n"); } int tick(void *a, void *b) { (void)a; (void)b; fprintf(stderr, "tick\n"); return 0; } int main(void) { fio_queue_s q = {0}; fio_queue_init(&q); fio_queue_push(&q, .fn = work); fio_queue_perform(&q); fio_timer_queue_s t = FIO_TIMER_QUEUE_INIT; fio_timer_schedule(&t, .fn = tick, .every = 100, .repetitions = 3); fio_timer_push2queue(&q, &t, 0); fio_queue_perform_all(&q); fio_timer_destroy(&t); fio_queue_destroy(&q); return 0; } ``` ------------------------------------------------------------ # Packet Data Stream ```c #define FIO_STREAM #include "fio-stl.h" ``` A packet-based byte stream for buffering data from memory buffers and file descriptors. It is useful when writes are partial: add packets to the stream, read the next contiguous chunk into or from a scratch buffer, write what you can, then advance by the number of bytes written. For the low-level helpers commonly used with streams, see [`004 files.md`](https://facil.io/0.8.x/file-helpers/), [`004 sock.md`](https://facil.io/0.8.x/basic-socket-helpers/), and [`102 poll api.md`](https://facil.io/0.8.x/posix-portable-polling-api/). --- ## Configuration Macros #### `FIO_STREAM_COPY_PER_PACKET` ```c #define FIO_STREAM_COPY_PER_PACKET 98304 ``` Maximum copied payload per packet. Larger copied buffers are split into multiple packets so memory can be released progressively. Override before including the implementation. #### `FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN` ```c #define FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN 116 /* 8 in DEBUG */ ``` Small buffers are copied even when `copy_buffer == 0`, improving locality and avoiding tiny external references. --- ## Types #### `fio_stream_s` ```c typedef struct { fio_stream_packet_s *next; fio_stream_packet_s **pos; size_t consumed; size_t length; } fio_stream_s; ``` Stream object. Treat the fields as private. Allocate on the stack with `FIO_STREAM_INIT` or on the heap with `fio_stream_new`. #### `fio_stream_packet_s` ```c typedef struct fio_stream_packet_s fio_stream_packet_s; ``` Opaque packed data node. Packets are prepared separately, then transferred to a stream with `fio_stream_add`. --- ## Lifecycle #### `FIO_STREAM_INIT` ```c #define FIO_STREAM_INIT(s) { .next = NULL, .pos = &(s).next } ``` Initializer for an in-place stream. ```c fio_stream_s stream = FIO_STREAM_INIT(stream); ``` #### `fio_stream_new` ```c fio_stream_s *fio_stream_new(void); ``` Allocates and initializes a heap stream. **Returns:** stream pointer, or `NULL` on allocation failure. Not declared when `FIO_REF_CONSTRUCTOR_ONLY` is defined. #### `fio_stream_free` ```c int fio_stream_free(fio_stream_s *stream); ``` Destroys `stream`, frees the stream object itself, and returns `0`. Not declared when `FIO_REF_CONSTRUCTOR_ONLY` is defined. #### `fio_stream_destroy` ```c void fio_stream_destroy(fio_stream_s *stream); ``` Frees all queued packets and re-initializes the stream object. Safe to call with `NULL`. --- ## Packing Data #### `fio_stream_pack_data` ```c fio_stream_packet_s *fio_stream_pack_data(void *buf, size_t len, size_t offset, uint8_t copy_buffer, void (*dealloc_func)(void *)); ``` Packs `len` bytes starting at `((char *)buf + offset)`. - If `copy_buffer` is non-zero, or `len < FIO_STREAM_ALWAYS_COPY_IF_LESS_THAN`, bytes are copied into stream-owned packets. - Otherwise the packet references `buf` and calls `dealloc_func(buf)` when the packet is freed, if `dealloc_func` is not `NULL`. - Copied data larger than `FIO_STREAM_COPY_PER_PACKET` is split into multiple packets. **Returns:** packet pointer, or `NULL` if `buf == NULL`, `len == 0`, the length is too large for the packet format, or allocation fails. If `dealloc_func` is provided, it is called on both success-after-copy and failure. #### `fio_stream_pack_fd` ```c fio_stream_packet_s *fio_stream_pack_fd(int fd, size_t len, size_t offset, uint8_t keep_open); ``` Packs bytes from an open file descriptor. Reads are performed later by `fio_stream_read` using `fio_fd_read`. - `len == 0` auto-detects the remaining file size with `fio_fd_size(fd)`. - `offset` is the starting file offset. - If `keep_open == 0`, the descriptor is closed when the packet is freed, or on pack failure after ownership was accepted. - If `keep_open != 0`, the caller remains responsible for closing `fd`. **Returns:** packet pointer, or `NULL` on invalid input, size detection failure / oversized auto-detected size when `len == 0`, or allocation failure. #### `fio_stream_add` ```c void fio_stream_add(fio_stream_s *stream, fio_stream_packet_s *packet); ``` Appends `packet` to `stream` and transfers packet ownership to the stream. If `stream` or `packet` is `NULL`, the packet is freed. This is not thread-safe. #### `fio_stream_pack_free` ```c void fio_stream_pack_free(fio_stream_packet_s *packet); ``` Frees a packet that was not added to a stream. Do not call this after `fio_stream_add` succeeds. --- ## Reading and Consuming #### `fio_stream_read` ```c void fio_stream_read(fio_stream_s *stream, char **buf, size_t *len); ``` Reads the next available bytes without consuming them. Before the call, `*buf` must point to a scratch buffer with at least `*len` bytes. This buffer is required when data spans multiple packets or comes from a file descriptor. After the call: - On empty stream or error, `*buf == NULL` and `*len == 0`. - Otherwise `*buf` either still points to the supplied scratch buffer or points directly into stream-owned memory. - `*len` is updated to the number of readable bytes available at `*buf`. Reset both `*buf` and `*len` before each read if reusing the same scratch buffer. This is not thread-safe. #### `fio_stream_advance` ```c void fio_stream_advance(fio_stream_s *stream, size_t len); ``` Consumes `len` bytes from the stream, freeing fully consumed packets. Usually pass the number of bytes actually written to the destination. This is not thread-safe. #### `fio_stream_any` ```c uint8_t fio_stream_any(fio_stream_s *stream); ``` Returns non-zero when `stream` has pending packets. Returns `0` for `NULL` or empty streams. This is not truly thread-safe. #### `fio_stream_length` ```c size_t fio_stream_length(fio_stream_s *stream); ``` Returns the number of bytes waiting in the stream. Call with a valid stream object. This is not truly thread-safe. --- ## Ownership and Thread-Safety Packing creates a packet owned by the caller. `fio_stream_add` transfers that ownership to the stream. `fio_stream_destroy`, `fio_stream_free`, and `fio_stream_advance` free packets as needed. For referenced memory packets, the referenced buffer must remain valid until the packet is freed. Pass `copy_buffer != 0` when that lifetime is not guaranteed. For file descriptor packets, keep the descriptor readable until the packet is consumed or destroyed; `keep_open` controls who closes it. Packing packets can be done before taking the stream lock, but mutating or consuming a single `fio_stream_s` (`add`, `read`, `advance`, and length checks) should be externally synchronized when shared across threads. --- ## Example ```c #define FIO_STREAM #include "fio-stl.h" #include #include int main(void) { fio_stream_s stream = FIO_STREAM_INIT(stream); char scratch[4096]; char msg[] = "hello, stream\n"; fio_stream_packet_s *p = fio_stream_pack_data(msg, strlen(msg), 0, 1, NULL); fio_stream_add(&stream, p); while (fio_stream_any(&stream)) { char *buf = scratch; size_t len = sizeof(scratch); fio_stream_read(&stream, &buf, &len); if (!len) break; size_t written = fwrite(buf, 1, len, stdout); fio_stream_advance(&stream, written); if (written != len) break; } fio_stream_destroy(&stream); return 0; } ``` ### File Descriptor Packet ```c #define FIO_STREAM #define FIO_FILES #include "fio-stl.h" fio_stream_s stream = FIO_STREAM_INIT(stream); int fd = fio_filename_open("./asset.bin", O_RDONLY); fio_stream_packet_s *p = fio_stream_pack_fd(fd, 0, 0, 0); /* stream closes fd */ fio_stream_add(&stream, p); /* ... read / write packets ... */ fio_stream_destroy(&stream); ``` ------------------------------------------------------------ # Markdown to HTML bstr Renderer ```c #define FIO_MD2HTML #include "fio-stl.h" ``` A complete-document Markdown / GFM renderer that turns parser events into escaped HTML stored in an owned `fio_bstr`. Markdown goes in, tags come out. Implemented in [`./103 md2html.h`](https://facil.io/0.8.x/api/103-md2html/). Defining `FIO_MD2HTML` automatically includes: - `FIO_STR` for `fio_bstr` - `FIO_GFM` for Markdown / GFM parsing ### Error Codes #### `FIO_MD2HTML_ERR_ALLOC` ```c #ifndef FIO_MD2HTML_ERR_ALLOC #define FIO_MD2HTML_ERR_ALLOC 1 #endif ``` Internal renderer error used when output allocation fails. Override before inclusion only if you need a different non-zero value. ### Rendering #### `fio_md2html` ```c SFUNC char *fio_md2html(char *bstr_target, fio_buf_info_s source); ``` Renders a complete Markdown / GFM document into a `fio_bstr`. **Parameters:** - `bstr_target` - existing `fio_bstr` to append to, or `NULL` to allocate a new one - `source` - Markdown bytes to parse **Returns:** the output `fio_bstr`, or `NULL` on parser / allocation failure. **Ownership:** the returned buffer is owned by the caller and should be released with `fio_bstr_free`. If `bstr_target == NULL` and rendering fails, the newly allocated output is freed. If `bstr_target` was provided, ownership stays with the caller. ### Rendering Behavior From the implementation: - normal text, code, and attribute values are escaped - raw Markdown HTML blocks / inline HTML are preserved, with GFM tagfilter applied - blocked tag names include `iframe`, `noembed`, `noframes`, `plaintext`, `style`, `textarea`, `title`, and `xmp`; HTML blocks only filter `textarea` and `xmp` - fenced code info strings use the first word as `class="language-..."` - GFM tables render `...` and open `` for body rows - table cell alignment uses `align="left"`, `align="right"`, or `align="center"` - GFM task list items render disabled checkbox inputs - autolinks add `mailto:` for bare email addresses and `http://` for `www.` links - image alt text is escaped and strips nested link / emphasis markup ### Thread-Safety The renderer uses caller-provided input and a local renderer state. It is thread-safe as long as separate calls do not mutate the same `bstr_target` at the same time. ### Example ```c #define FIO_MD2HTML #include "fio-stl.h" #include int main(void) { char md[] = "# Demo\n\nHello **world**.\n"; char *html = fio_md2html(NULL, FIO_BUF_INFO1(md)); if (!html) return 1; fwrite(html, 1, fio_bstr_len(html), stdout); fio_bstr_free(html); return 0; } ``` --- # Mustache-ish Template Engine ```c #define FIO_MUSTACHE #include "fio-stl.h" ``` A Mustache-ish template parser and renderer with variables, sections, inverted sections, partials, delimiter changes, comments, and optional YAML front matter callbacks. Logic-less-ish, because C callbacks still get a vote. Implemented in [`./104 mustache.h`](https://facil.io/0.8.x/api/104-mustache/). Defining `FIO_MUSTACHE` pulls in string / `fio_bstr` helpers as needed. ### Configuration Macros #### `FIO_MUSTACHE_MAX_DEPTH` ```c #ifndef FIO_MUSTACHE_MAX_DEPTH #define FIO_MUSTACHE_MAX_DEPTH 128 #endif ``` Maximum parser / builder nesting depth for sections and partials. #### `FIO_MUSTACHE_PRESERVE_PADDING` ```c #ifndef FIO_MUSTACHE_PRESERVE_PADDING #define FIO_MUSTACHE_PRESERVE_PADDING 0 #endif ``` When enabled, preserves padding for stand-alone variables and partial templates. #### `FIO_MUSTACHE_LAMBDA_SUPPORT` ```c #ifndef FIO_MUSTACHE_LAMBDA_SUPPORT #define FIO_MUSTACHE_LAMBDA_SUPPORT 0 #endif ``` When enabled, stores raw section text for lambda-style section handling through `is_lambda`. #### `FIO_MUSTACHE_ISOLATE_PARTIALS` ```c #ifndef FIO_MUSTACHE_ISOLATE_PARTIALS #define FIO_MUSTACHE_ISOLATE_PARTIALS 1 #endif ``` When enabled, limits partial lookup scope to the context of the partial's section. #### `FIO_MUSTACHE_SECURE_PATH` ```c #ifndef FIO_MUSTACHE_SECURE_PATH #define FIO_MUSTACHE_SECURE_PATH 1 #endif ``` When enabled, skips partial names that attempt `../` path traversal. ### Syntax Quick Map | Syntax | Meaning | | --- | --- | | `{{name}}` | escaped variable | | `{{{name}}}` | raw variable | | `{{&name}}` | raw variable | | `{{#items}}...{{/items}}` | truthy section / array iteration | | `{{^items}}...{{/items}}` | inverted section | | `{{>partial}}` | partial template include | | `{{! comment}}` | comment | | `{{=<% %>=}}` | delimiter change | | `{{.}}` | current context | Dot lookup such as `person.name` is supported by repeatedly calling `get_var` for path segments. ### Types #### `fio_mustache_s` ```c typedef struct fio_mustache_s fio_mustache_s; ``` Opaque parsed template object. Internally it is stored as a `fio_bstr` instruction stream. **Ownership:** `fio_mustache_load` returns an owned template. Free it with `fio_mustache_free`. `fio_mustache_dup` creates a copy; it is not a shared reference counter in this header. #### `fio_mustache_bargs_s` ```c typedef struct fio_mustache_bargs_s fio_mustache_bargs_s; ``` Forward declaration for build arguments. The full struct is listed below. #### `fio_mustache_load_args_s` ```c typedef struct { fio_buf_info_s data; fio_buf_info_s filename; fio_buf_info_s (*load_file_data)(fio_buf_info_s filename, void *udata); void (*free_file_data)(fio_buf_info_s file_data, void *udata); void (*on_yaml_front_matter)(fio_buf_info_s yaml_front_matter, void *udata); void *udata; } fio_mustache_load_args_s; ``` Load / parse settings. **Members:** - `data` - preloaded template bytes - `filename` - file name, also used as base path for partials - `load_file_data` - callback that loads file contents - `free_file_data` - callback that frees loaded file contents - `on_yaml_front_matter` - called when front matter is found - `udata` - user data for load callbacks If both `load_file_data` and `free_file_data` are missing, defaults use `fio_bstr_readfile` and `fio_bstr_free`. If one is custom, provide the matching cleanup. Either `filename` or `data` is required. #### `fio_mustache_bargs_s` ```c struct fio_mustache_bargs_s { void *(*write_text)(void *udata, fio_buf_info_s txt); void *(*write_text_escaped)(void *udata, fio_buf_info_s raw); void *(*get_var)(void *ctx, fio_buf_info_s name); size_t (*array_length)(void *ctx); void *(*get_var_index)(void *ctx, size_t index); fio_buf_info_s (*var2str)(void *var); int (*var_is_truthful)(void *ctx); void (*release_var)(void *ctx); int (*is_lambda)(void **udata, void *ctx, fio_buf_info_s raw_template_section); void *ctx; void *udata; }; ``` Build / render callbacks. **Members:** - `write_text` - writes raw template text - `write_text_escaped` - writes escaped variable text - `get_var` - returns a value for `name` from `ctx` - `array_length` - returns array length, or `0` for non-array values - `get_var_index` - returns an item context by index - `var2str` - returns a string view for a value - `var_is_truthful` - returns non-zero for truthy values - `release_var` - releases values returned by callbacks - `is_lambda` - handles lambda sections when lambda support is enabled - `ctx` - root render context - `udata` - output / user pointer; the final value is returned by `fio_mustache_build` If both writer callbacks are missing, defaults append to a `fio_bstr` in `udata`. The returned pointer should be freed with `fio_bstr_free`. ### Loading #### `fio_mustache_load` ```c SFUNC fio_mustache_s *fio_mustache_load(fio_mustache_load_args_s settings); #define fio_mustache_load(...) \ fio_mustache_load((fio_mustache_load_args_s){__VA_ARGS__}) ``` Loads and parses a template from `settings.data` or `settings.filename`. If `filename` is provided and `data` is empty, the loader callback is used. If both are provided, `data` is parsed and `filename` is kept for partial path resolution. **Returns:** a parsed template object, or `NULL` on error. #### `fio_mustache_free` ```c SFUNC void fio_mustache_free(fio_mustache_s *m); ``` Frees a parsed template. Accepts `NULL`. #### `fio_mustache_dup` ```c SFUNC fio_mustache_s *fio_mustache_dup(fio_mustache_s *m); ``` Copies a parsed template using `fio_bstr_copy`. **Returns:** the copied template, or `NULL` if `m == NULL`. ### Rendering #### `fio_mustache_build` ```c SFUNC void *fio_mustache_build(fio_mustache_s *m, fio_mustache_bargs_s); #define fio_mustache_build(m, ...) \ fio_mustache_build((m), ((fio_mustache_bargs_s){__VA_ARGS__})) ``` Renders `m` with the supplied context and callbacks. **Returns:** the final `udata` value. If `m == NULL`, returns `args.udata` unchanged. During rendering: - escaped variables call `write_text_escaped` - raw variables call `write_text` - sections render once for truthy non-arrays, or once per array item - inverted sections render when `var_is_truthful` returns zero - values returned from lookup / array callbacks are released with `release_var` ### Partials and Files Partials are loaded by name through `load_file_data`. The implementation tries each name with these suffixes: 1. `.mustache` 2. `.html` 3. no suffix Relative partials are searched against the including template's path chain, then as provided. With `FIO_MUSTACHE_SECURE_PATH`, `../` partials are skipped. ### YAML Front Matter If a template begins with `---` followed by a line break, the parser scans until a closing `---` line and calls: ```c void (*on_yaml_front_matter)(fio_buf_info_s yaml_front_matter, void *udata); ``` The front matter bytes are not rendered. The engine does not parse YAML for you. It has enough hobbies. ### Thread-Safety Parsed templates are immutable during `fio_mustache_build`, so separate builds can use the same template as long as your callbacks and contexts are safe. Loading and freeing are caller-owned operations. ### Example ```c #define FIO_MUSTACHE #include "fio-stl.h" #include #include typedef struct { const char *name; } data_s; static void *get_var(void *ctx, fio_buf_info_s name) { data_s *d = (data_s *)ctx; if (name.len == 4 && !memcmp(name.buf, "name", 4)) return (void *)d->name; return NULL; } static fio_buf_info_s var2str(void *var) { char *s = (char *)var; return FIO_BUF_INFO2(s, strlen(s)); } int main(void) { char template_text[] = "Hello, {{name}}!\n"; data_s data = {.name = "World"}; fio_mustache_s *m = fio_mustache_load( .data = FIO_BUF_INFO1(template_text)); if (!m) return 1; char *out = fio_mustache_build(m, .ctx = &data, .get_var = get_var, .var2str = var2str); if (out) { fwrite(out, 1, fio_bstr_len(out), stdout); fio_bstr_free(out); } fio_mustache_free(m); return 0; } ``` --- # Crypto Core ```c #define FIO_CRYPTO_CORE #include "fio-stl.h" ``` The crypto slice is a small, zero-dependency toolbox for code that needs to hash, sign, encrypt, parse certificates, or derive keys without dragging in a larger library. Handy? yes. Magic security dust? no. `FIO_CRYPTO_CORE` itself is the shared glue. It defines the AEAD function pointer types used by the symmetric ciphers and by higher-level helpers that accept “any AEAD with this call shape”. Most users include a specific module, or define `FIO_CRYPTO` to pull in the crypto set. ## Map of the Crypto Modules | Class | Docs | What lives there | | --- | --- | --- | | Hash | [SHA-1](https://facil.io/0.8.x/sha-1/), [SHA-2](https://facil.io/0.8.x/sha-1/), [SHA-3 / SHAKE](https://facil.io/0.8.x/sha-1/), [BLAKE2](https://facil.io/0.8.x/blake2/) | Digest functions, streaming hash contexts, SHA/BLAKE HMAC helpers where provided. SHA-1 is legacy-only glue. | | Symmetric | [ChaCha20-Poly1305](https://facil.io/0.8.x/chacha20-poly1305/), [AES-GCM](https://facil.io/0.8.x/aes-gcm/) | AEAD encryption, stream cipher helpers, and the shared in-place authenticated encryption shape. | | Asymmetric | [Ed25519 & X25519](https://facil.io/0.8.x/ed25519-x25519/), [P-256](https://facil.io/0.8.x/p-256-secp256r1/), [P-384](https://facil.io/0.8.x/p-256-secp256r1/), [RSA](https://facil.io/0.8.x/rsa-signatures/) | Signatures, key exchange, ECIES-style X25519 encryption, and RSA signatures for TLS-style use cases. | | PKI | [ASN.1 DER](https://facil.io/0.8.x/asn-1-der/), [X.509](https://facil.io/0.8.x/http-1-x-parser/), [PEM](https://facil.io/0.8.x/p-256-secp256r1/) | DER/PEM parsing, certificate fields, hostname checks, signature checks, and certificate chain validation helpers. | | KDF | [HKDF](https://facil.io/0.8.x/hkdf/), [Argon2](https://facil.io/0.8.x/argon2/), [Lyra2](https://facil.io/0.8.x/lyra2/), [OTP](https://facil.io/0.8.x/otp-totp/), [Secrets](https://facil.io/0.8.x/secrets/) | Key derivation, password hashing, TOTP codes, and hashing a process secret into a stable internal value. | | Post-Quantum | [ML-KEM-768](https://facil.io/0.8.x/ml-entity-decoding/) | ML-KEM-768 key encapsulation and the X25519MLKEM768 hybrid key exchange shape used by TLS drafts. | ## The Shared AEAD Shape The core header defines two function pointer types: ```c typedef void(fio_crypto_enc_fn)(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); typedef int(fio_crypto_dec_fn)(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Current AEAD implementations that match this shape include: - `fio_chacha20_poly1305_enc` / `fio_chacha20_poly1305_dec` - `fio_xchacha20_poly1305_enc` / `fio_xchacha20_poly1305_dec` - `fio_aes128_gcm_enc` / `fio_aes128_gcm_dec` - `fio_aes256_gcm_enc` / `fio_aes256_gcm_dec` These functions encrypt or decrypt `data` in place. `ad` is authenticated but not encrypted. `mac` is the authentication tag buffer; the current AEAD modules use 16-byte tags. Key and nonce sizes belong to the selected cipher, not to the function pointer type. Decryption returns `0` when authentication succeeds and `-1` when it fails. Treat any failure as “message not trusted”; do not parse, log in detail, or partially use the plaintext. ## Safe Use, Plainly This module helps when you need portable crypto building blocks inside the STL: hashing buffers, deriving session keys, signing messages, checking certificates, or doing AEAD encryption with a known protocol design. It does **not** promise that your protocol is safe. It does not manage long-term keys for you, pick nonces, rotate secrets, maintain a root trust store, protect keys in hardware, provide FIPS validation, or substitute for an external security review. Several public-key and PKI headers explicitly note that they have not been independently audited. Use this toolbox when: - you need the zero-dependency facil.io STL path; - the protocol is already designed and reviewed; - inputs, key sizes, nonce rules, and failure paths are controlled; - a compact embedded copy is more important than delegating to a platform stack. Prefer audited platform/security libraries such as OpenSSL, BoringSSL, libsodium, CommonCrypto, platform TLS, or OS key stores when: - you are building TLS, PKI, payment, identity, or compliance-sensitive systems; - private keys live for a long time or leave the process boundary; - certificate trust decisions must track OS/browser policy; - side-channel hardening, hardware acceleration policy, or formal validation matters. In short: this is a sharp little knife. Useful. Keep fingers clear. ## Implementation Notes The crypto headers use optional CPU-specific paths where available, including SHA intrinsics, AES-NI/PCLMULQDQ, ARM crypto extensions, NEON, and AVX2. The public APIs stay the same when the implementation falls back to portable C. The SIMD notes in `150 crypto core.h` are documentation for maintainers: ChaCha20-Poly1305 can process blocks in parallel, Curve25519 uses vectorized field add/sub/cswap where useful, and ML-KEM vectorizes NTT/reduction paths. These are implementation details, not extra guarantees. # BLAKE2 ```c #define FIO_BLAKE2 #include "fio-stl.h" ``` BLAKE2b (64-bit, up to 64-byte digest) and BLAKE2s (32-bit, up to 32-byte digest). Both are fast, modern cryptographic hashes with built-in keyed hashing, standard HMAC wrappers, and a streaming init/consume/finalize API. ### Types #### `fio_blake2b_s` ```c typedef struct { uint64_t h[8]; /* state */ uint64_t t[2]; /* total bytes processed (128-bit counter) */ uint64_t f[2]; /* finalization flags */ uint8_t buf[128]; /* input buffer */ size_t buflen; /* bytes in buffer */ size_t outlen; /* digest length */ } fio_blake2b_s; ``` Streaming BLAKE2b context. Treat it as opaque and initialize with `fio_blake2b_init`. #### `fio_blake2s_s` ```c typedef struct { uint32_t h[8]; /* state */ uint32_t t[2]; /* total bytes processed (64-bit counter) */ uint32_t f[2]; /* finalization flags */ uint8_t buf[64]; /* input buffer */ size_t buflen; /* bytes in buffer */ size_t outlen; /* digest length */ } fio_blake2s_s; ``` Streaming BLAKE2s context. Treat it as opaque and initialize with `fio_blake2s_init`. ### BLAKE2b Functions #### `fio_blake2b` ```c FIO_IFUNC fio_u512 fio_blake2b(const void *data, uint64_t len); ``` One-shot BLAKE2b with the full 64-byte digest. **Parameters:** - `data` — bytes to hash. - `len` — number of bytes. **Returns:** the 64-byte digest as `fio_u512`. **Note:** signature matches `fio_sha512`, so the two are interchangeable as `fio_u512 (*)(const void *, uint64_t)`. #### `fio_blake2b_hash` ```c SFUNC void fio_blake2b_hash(void *restrict out, size_t outlen, const void *restrict data, size_t len, const void *restrict key, size_t keylen); ``` Flexible-output BLAKE2b with optional keyed hashing. **Parameters:** - `out` — destination buffer (at least `outlen` bytes). - `outlen` — digest length (1–64; defaults to 64 if 0). - `data` — bytes to hash. - `len` — data length. - `key` — optional secret key (NULL for unkeyed). - `keylen` — key length (0–64; clamped to 64 if larger). **Note:** keyed BLAKE2b is a built-in MAC mode; for RFC-2104 HMAC, use `fio_blake2b_hmac`. #### `fio_blake2b_hmac` ```c SFUNC fio_u512 fio_blake2b_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Standard HMAC-BLAKE2b with a 64-byte digest. **Parameters:** - `key` — secret key. - `key_len` — key length. - `msg` — message to authenticate. - `msg_len` — message length. **Returns:** 64-byte authentication code as `fio_u512`. #### `fio_blake2b_init` ```c SFUNC fio_blake2b_s fio_blake2b_init(size_t outlen, const void *key, size_t keylen); ``` Initializes a BLAKE2b streaming context. **Parameters:** - `outlen` — desired digest length (1–64; defaults to 64 if 0). - `key` — optional secret key (NULL for unkeyed). - `keylen` — key length (0–64; clamped to 64 if larger). #### `fio_blake2b_consume` ```c SFUNC void fio_blake2b_consume(fio_blake2b_s *restrict h, const void *restrict data, size_t len); ``` Feeds more data into a BLAKE2b streaming context. May be called repeatedly. #### `fio_blake2b_finalize` ```c SFUNC fio_u512 fio_blake2b_finalize(fio_blake2b_s *h); ``` Finalizes the stream and returns the digest. **Returns:** digest as `fio_u512`. Only the first `outlen` bytes are valid. ### BLAKE2s Functions #### `fio_blake2s` ```c FIO_IFUNC fio_u256 fio_blake2s(const void *data, uint64_t len); ``` One-shot BLAKE2s with the full 32-byte digest. #### `fio_blake2s_hash` ```c SFUNC void fio_blake2s_hash(void *restrict out, size_t outlen, const void *restrict data, size_t len, const void *restrict key, size_t keylen); ``` Flexible-output BLAKE2s with optional keyed hashing. `outlen` is 1–32 (defaults to 32 if 0). #### `fio_blake2s_hmac` ```c SFUNC fio_u256 fio_blake2s_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Standard HMAC-BLAKE2s with a 32-byte digest. #### `fio_blake2s_init` ```c SFUNC fio_blake2s_s fio_blake2s_init(size_t outlen, const void *key, size_t keylen); ``` Initializes a BLAKE2s streaming context. `outlen` is 1–32 (defaults to 32 if 0). #### `fio_blake2s_consume` ```c SFUNC void fio_blake2s_consume(fio_blake2s_s *restrict h, const void *restrict data, size_t len); ``` Feeds more data into a BLAKE2s streaming context. #### `fio_blake2s_finalize` ```c SFUNC fio_u256 fio_blake2s_finalize(fio_blake2s_s *h); ``` Finalizes the stream and returns the digest. ### Examples ```c #define FIO_BLAKE2 #include "fio-stl.h" void examples(void) { const char *msg = "Hello, BLAKE2!"; /* One-shot */ fio_u512 b2b = fio_blake2b(msg, strlen(msg)); fio_u256 b2s = fio_blake2s(msg, strlen(msg)); /* Flexible output with key */ uint8_t digest[16]; fio_blake2b_hash(digest, 16, msg, strlen(msg), "key", 3); /* Streaming */ fio_blake2b_s ctx = fio_blake2b_init(64, NULL, 0); fio_blake2b_consume(&ctx, "Hello, ", 7); fio_blake2b_consume(&ctx, "BLAKE2!", 7); fio_u512 result = fio_blake2b_finalize(&ctx); /* HMAC */ fio_u512 mac = fio_blake2b_hmac("key", 3, msg, strlen(msg)); } ``` ### BLAKE2b vs BLAKE2s | Property | BLAKE2b | BLAKE2s | |---|---|---| | Word size | 64-bit | 32-bit | | Block size | 128 bytes | 64 bytes | | Max digest | 64 bytes | 32 bytes | | Max key | 64 bytes | 32 bytes | | Rounds | 12 | 10 | | Result type | `fio_u512` | `fio_u256` | ------------------------------------------------------------ # ChaCha20 & Poly1305 ```c #define FIO_CHACHA #include "fio-stl.h" ``` ChaCha20 stream cipher, Poly1305 authenticator, and the ChaCha20-Poly1305 AEAD combination. Also includes XChaCha20-Poly1305 with a 192-bit nonce, which is safer for random nonces than the 96-bit variant. **Security note:** this implementation has not been independently audited. Use at your own risk, and prefer a tested cryptographic library when one is available. ### ChaCha20-Poly1305 API #### `fio_chacha20_poly1305_enc` ```c SFUNC void fio_chacha20_poly1305_enc(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` In-place encryption with a 16-byte Poly1305 tag. **Parameters:** - `mac` — output buffer; must have at least 16 writable bytes. - `data` — plaintext buffer; encrypted in place. - `len` — data length. - `ad` — additional authenticated data (not encrypted); may be `NULL`. - `adlen` — length of `ad`. - `key` — 32-byte key. - `nonce` — 12-byte nonce. #### `fio_chacha20_poly1305_dec` ```c SFUNC int fio_chacha20_poly1305_dec(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` In-place decryption with tag verification. **Returns:** `0` on success, `-1` if authentication fails. On failure `data` is zeroed. #### `fio_chacha20_poly1305_auth` ```c SFUNC void fio_chacha20_poly1305_auth(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Computes the Poly1305 tag for already-encrypted data without decrypting it. ### Standalone ChaCha20 / Poly1305 #### `fio_chacha20` ```c SFUNC void fio_chacha20(void *restrict data, size_t len, const void *key, const void *nonce, uint32_t counter); ``` In-place ChaCha20 encryption or decryption. **Parameters:** - `data` — buffer to transform in place. - `len` — buffer length. - `key` — 32-byte key. - `nonce` — 12-byte nonce. - `counter` — block counter; usually `1` unless resuming mid-ciphertext. #### `fio_poly1305_auth` ```c SFUNC void fio_poly1305_auth(void *restrict mac_dest, void *restrict message, size_t len, const void *ad, size_t ad_len, const void *key256bits); ``` Computes a Poly1305 MAC for `message` and `ad` using a 32-byte key. **Parameters:** - `mac_dest` — output buffer; must have at least 16 writable bytes. - `message` — message to authenticate; may be `NULL` if `len` is 0. - `len` — message length. - `ad` — additional data; may be `NULL`. - `ad_len` — additional data length. - `key256bits` — 32-byte Poly1305 key. ### XChaCha20-Poly1305 API XChaCha20 uses a 24-byte nonce, making random nonces safe from birthday-bound collisions. #### `fio_xchacha20_poly1305_enc` ```c SFUNC void fio_xchacha20_poly1305_enc(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Same shape as `fio_chacha20_poly1305_enc`, but `nonce` must be 24 bytes. #### `fio_xchacha20_poly1305_dec` ```c SFUNC int fio_xchacha20_poly1305_dec(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Same shape as `fio_chacha20_poly1305_dec`, but `nonce` must be 24 bytes. #### `fio_xchacha20` ```c SFUNC void fio_xchacha20(void *restrict data, size_t len, const void *key, const void *nonce, uint32_t counter); ``` In-place XChaCha20 encryption or decryption. ### Example ```c #define FIO_CHACHA #include "fio-stl.h" int main(void) { uint8_t key[32] = {0}; uint8_t nonce[12] = {0}; uint8_t msg[32] = "hello, chacha world!"; uint8_t tag[16]; fio_rand_bytes_secure(key, sizeof(key)); fio_rand_bytes_secure(nonce, sizeof(nonce)); fio_chacha20_poly1305_enc(tag, msg, sizeof(msg), NULL, 0, key, nonce); if (fio_chacha20_poly1305_dec(tag, msg, sizeof(msg), NULL, 0, key, nonce)) { printf("authentication failed\n"); return 1; } return 0; } ``` ------------------------------------------------------------ # HKDF ```c #define FIO_HKDF #include "fio-stl.h" ``` HMAC-based Key Derivation Function per [RFC 5869](https://www.rfc-editor.org/rfc/rfc5869). HKDF extracts entropy from input keying material into a pseudorandom key, then expands that key into one or more output keys. Supports SHA-256 and SHA-384 as the underlying HMAC. **Note:** HKDF needs `fio_sha256_hmac` and `fio_sha512_hmac`. Define `FIO_SHA2` before `FIO_HKDF`, or use `FIO_CRYPTO` to pull in everything. ### Constants #### `FIO_HKDF_SHA256_HASH_LEN` ```c #define FIO_HKDF_SHA256_HASH_LEN 32 ``` SHA-256 hash length in bytes. #### `FIO_HKDF_SHA384_HASH_LEN` ```c #define FIO_HKDF_SHA384_HASH_LEN 48 ``` SHA-384 hash length in bytes. ### Functions #### `fio_hkdf_extract` ```c SFUNC void fio_hkdf_extract(void *restrict prk, const void *restrict salt, size_t salt_len, const void *restrict ikm, size_t ikm_len, int use_sha384); ``` HKDF-Extract: `PRK = HMAC-Hash(salt, IKM)`. **Parameters:** - `prk` — output buffer (32 bytes for SHA-256, 48 for SHA-384). - `salt` — optional salt; if `NULL`, a zero string of hash length is used. - `salt_len` — salt length. - `ikm` — input keying material. - `ikm_len` — IKM length. - `use_sha384` — non-zero for SHA-384, zero for SHA-256. #### `fio_hkdf_expand` ```c SFUNC void fio_hkdf_expand(void *restrict okm, size_t okm_len, const void *restrict prk, size_t prk_len, const void *restrict info, size_t info_len, int use_sha384); ``` HKDF-Expand: `OKM = HKDF-Expand(PRK, info, L)`. **Parameters:** - `okm` — output buffer. - `okm_len` — desired output length (max `255 * hash_len`). - `prk` — pseudorandom key from Extract. - `prk_len` — 32 for SHA-256, 48 for SHA-384. - `info` — optional context/info string; may be `NULL`. - `info_len` — info length. - `use_sha384` — non-zero for SHA-384, zero for SHA-256. #### `fio_hkdf` ```c SFUNC void fio_hkdf(void *restrict okm, size_t okm_len, const void *restrict salt, size_t salt_len, const void *restrict ikm, size_t ikm_len, const void *restrict info, size_t info_len, int use_sha384); ``` Combined HKDF: Extract followed by Expand in one call. ### Example ```c #define FIO_SHA2 #define FIO_HKDF #include "fio-stl.h" int main(void) { const char *ikm = "input keying material"; const char *salt = "salt"; const char *info = "app context"; uint8_t key[32]; fio_hkdf(key, sizeof(key), salt, strlen(salt), ikm, strlen(ikm), info, strlen(info), 0); return 0; } ``` ------------------------------------------------------------ # SHA-1 ```c #define FIO_SHA1 #include "fio-stl.h" ``` SHA-1 hashing and HMAC. It is cryptographically broken, so do not use it for anything that needs security. Some protocols (hello, WebSockets) still ask for it, so it lives here for compatibility. ### Types #### `fio_sha1_s` ```c typedef union { #ifdef __SIZEOF_INT128__ __uint128_t align__; #else uint64_t align__; #endif uint32_t v[5]; uint8_t digest[20]; } fio_sha1_s; ``` Container for the 20-byte SHA-1 digest. **Members:** - `align__` - alignment padding. - `v` - the 5 × 32-bit state words. - `digest` - the 20-byte digest as raw bytes. ### API Functions #### `fio_sha1` ```c fio_sha1_s fio_sha1(const void *data, uint64_t len); ``` One-shot SHA-1 hash of `len` bytes at `data`. **Parameters:** - `data` - pointer to the data to hash. - `len` - length of the data in bytes. **Returns:** a `fio_sha1_s` containing the 20-byte digest. #### `fio_sha1_hmac` ```c fio_sha1_s fio_sha1_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Computes HMAC-SHA1, producing a 20-byte authentication code. **Parameters:** - `key` - pointer to the secret key. - `key_len` - length of the key in bytes. - `msg` - pointer to the message to authenticate. - `msg_len` - length of the message in bytes. **Returns:** a `fio_sha1_s` containing the 20-byte HMAC. **Note:** keys longer than 64 bytes are first hashed with SHA-1. #### `fio_sha1_len` ```c size_t fio_sha1_len(void); ``` **Returns:** the SHA-1 digest length in bytes (20). #### `fio_sha1_digest` ```c uint8_t *fio_sha1_digest(fio_sha1_s *s); ``` **Parameters:** - `s` - pointer to a `fio_sha1_s` result. **Returns:** a pointer to the 20-byte digest inside `s`. ### Examples #### One-shot hash ```c #define FIO_SHA1 #include "fio-stl.h" #include #include int main(void) { const char *msg = "hello"; fio_sha1_s r = fio_sha1(msg, strlen(msg)); for (size_t i = 0; i < fio_sha1_len(); ++i) printf("%02x", fio_sha1_digest(&r)[i]); printf("\n"); return 0; } ``` #### HMAC ```c fio_sha1_s mac = fio_sha1_hmac("secret", 6, "hello", 5); ``` **Note:** SHA-1 is broken. Prefer SHA-256, SHA-3, or another modern hash for security-sensitive work. ------------------------------------------------------------ # SHA-2 ```c #define FIO_SHA2 #include "fio-stl.h" ``` SHA-256 and SHA-512 hashing, with both one-shot and streaming APIs plus HMAC. These are the workhorse hashes for checksums, signatures, and anything that needs a collision-resistant digest. ### Types #### `fio_sha256_s` ```c typedef struct { fio_u256 hash; fio_u512 cache; uint64_t total_len; } fio_sha256_s; ``` Streaming SHA-256 state. Initialize with `fio_sha256_init`, feed with `fio_sha256_consume`, and finish with `fio_sha256_finalize`. #### `fio_sha512_s` ```c typedef struct { fio_u512 hash; fio_u1024 cache; uint64_t total_len; } fio_sha512_s; ``` Streaming SHA-512 state. Initialize with `fio_sha512_init`, feed with `fio_sha512_consume`, and finish with `fio_sha512_finalize`. ### SHA-256 Functions #### `fio_sha256` ```c fio_u256 fio_sha256(const void *data, uint64_t len); ``` One-shot SHA-256 of `len` bytes at `data`. **Parameters:** - `data` - pointer to the data to hash. - `len` - length of the data in bytes. **Returns:** a `fio_u256` containing the 32-byte digest. #### `fio_sha256_init` ```c fio_sha256_s fio_sha256_init(void); ``` **Returns:** a freshly initialized SHA-256 streaming state. #### `fio_sha256_consume` ```c void fio_sha256_consume(fio_sha256_s *h, const void *data, uint64_t len); ``` Feeds more data into a streaming SHA-256 hash. May be called repeatedly. **Parameters:** - `h` - pointer to the streaming state. - `data` - pointer to the data to add. - `len` - length of the data in bytes. #### `fio_sha256_finalize` ```c fio_u256 fio_sha256_finalize(fio_sha256_s *h); ``` Finalizes the hash and returns the 32-byte digest. **Parameters:** - `h` - pointer to the streaming state. **Returns:** a `fio_u256` containing the digest. **Note:** after finalization, `h` should not be reused without re-initialization. ### SHA-512 Functions #### `fio_sha512` ```c fio_u512 fio_sha512(const void *data, uint64_t len); ``` One-shot SHA-512 of `len` bytes at `data`. **Parameters:** - `data` - pointer to the data to hash. - `len` - length of the data in bytes. **Returns:** a `fio_u512` containing the 64-byte digest. #### `fio_sha512_init` ```c fio_sha512_s fio_sha512_init(void); ``` **Returns:** a freshly initialized SHA-512 streaming state. #### `fio_sha512_consume` ```c void fio_sha512_consume(fio_sha512_s *h, const void *data, uint64_t len); ``` Feeds more data into a streaming SHA-512 hash. May be called repeatedly. **Parameters:** - `h` - pointer to the streaming state. - `data` - pointer to the data to add. - `len` - length of the data in bytes. #### `fio_sha512_finalize` ```c fio_u512 fio_sha512_finalize(fio_sha512_s *h); ``` Finalizes the hash and returns the 64-byte digest. **Parameters:** - `h` - pointer to the streaming state. **Returns:** a `fio_u512` containing the digest. **Note:** after finalization, `h` should not be reused without re-initialization. ### SHA-384 Functions SHA-384 is SHA-512 with different initial values, truncated to 48 bytes (truncating a SHA-512 *result* is NOT the same as SHA-384). #### `fio_sha384` ```c fio_u512 fio_sha384(const void *data, uint64_t len); ``` Computes SHA-384 in a single call. **Returns:** a `fio_u512` whose first 48 bytes hold the digest. #### `fio_sha384_init` / `fio_sha384_consume` / `fio_sha384_finalize` ```c fio_sha512_s fio_sha384_init(void); void fio_sha384_consume(fio_sha512_s *h, const void *data, uint64_t len); fio_u512 fio_sha384_finalize(fio_sha512_s *h); ``` Streaming SHA-384: same state type as SHA-512; `fio_sha384_init` seeds the SHA-384 initial values, consume/finalize alias the SHA-512 operations. ### HMAC Functions #### `fio_sha256_hmac` ```c fio_u256 fio_sha256_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Computes HMAC-SHA256, producing a 32-byte authentication code. **Parameters:** - `key` - pointer to the secret key. - `key_len` - length of the key in bytes. - `msg` - pointer to the message to authenticate. - `msg_len` - length of the message in bytes. **Returns:** a `fio_u256` containing the 32-byte HMAC. **Note:** keys longer than 64 bytes are hashed first. #### `fio_sha512_hmac` ```c fio_u512 fio_sha512_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Computes HMAC-SHA512, producing a 64-byte authentication code. **Parameters:** - `key` - pointer to the secret key. - `key_len` - length of the key in bytes. - `msg` - pointer to the message to authenticate. - `msg_len` - length of the message in bytes. **Returns:** a `fio_u512` containing the 64-byte HMAC. **Note:** keys longer than 128 bytes are hashed first. ### Examples #### One-shot SHA-256 ```c #define FIO_SHA2 #include "fio-stl.h" #include #include int main(void) { const char *msg = "hello"; fio_u256 r = fio_sha256(msg, strlen(msg)); for (size_t i = 0; i < sizeof(r.u8); ++i) printf("%02x", r.u8[i]); printf("\n"); return 0; } ``` #### Streaming SHA-256 ```c fio_sha256_s h = fio_sha256_init(); fio_sha256_consume(&h, "hello, ", 7); fio_sha256_consume(&h, "world", 5); fio_u256 r = fio_sha256_finalize(&h); ``` #### HMAC-SHA256 ```c fio_u256 mac = fio_sha256_hmac("secret", 6, "hello", 5); ``` **Note:** all state lives in caller-owned variables. The functions do not allocate heap memory and are thread-safe as long as each thread uses its own state. ------------------------------------------------------------ # SHA-3 ```c #define FIO_SHA3 #include "fio-stl.h" ``` The SHA-3 family (SHA3-224/256/384/512) and the SHAKE128/SHAKE256 extendable-output functions, built on Keccak-f[1600]. All variants share one streaming context type. ### Types #### `fio_sha3_s` ```c typedef struct { uint64_t state[25]; uint8_t buf[200]; size_t buflen; size_t rate; size_t outlen; uint8_t delim; } fio_sha3_s; ``` Streaming context for every SHA-3 and SHAKE variant. **Members:** - `state` - the 25-word Keccak-f[1600] state. - `buf` - partial-block input buffer. - `buflen` - bytes currently buffered. - `rate` - absorption/squeezing rate in bytes, set by the init function. - `outlen` - fixed output length in bytes for SHA-3; 0 for SHAKE. - `delim` - domain separator (`0x06` for SHA-3, `0x1F` for SHAKE). **Note:** treat this as opaque. Always initialize it with one of the `fio_sha3_*_init` or `fio_shake*_init` functions. ### SHA-3 Fixed-Output Functions #### `fio_sha3_224_init` ```c fio_sha3_s fio_sha3_224_init(void); ``` **Returns:** a SHA3-224 context (28-byte output, rate 144). #### `fio_sha3_256_init` ```c fio_sha3_s fio_sha3_256_init(void); ``` **Returns:** a SHA3-256 context (32-byte output, rate 136). #### `fio_sha3_384_init` ```c fio_sha3_s fio_sha3_384_init(void); ``` **Returns:** a SHA3-384 context (48-byte output, rate 104). #### `fio_sha3_512_init` ```c fio_sha3_s fio_sha3_512_init(void); ``` **Returns:** a SHA3-512 context (64-byte output, rate 72). #### `fio_sha3_consume` ```c void fio_sha3_consume(fio_sha3_s *restrict h, const void *restrict data, size_t len); ``` Feeds `len` bytes into a SHA-3 context. May be called repeatedly. **Parameters:** - `h` - pointer to the streaming context. - `data` - pointer to the data to absorb. - `len` - length of the data in bytes. #### `fio_sha3_finalize` ```c void fio_sha3_finalize(fio_sha3_s *restrict h, void *restrict out); ``` Pads, permutes, and writes `h->outlen` digest bytes to `out`. **Parameters:** - `h` - pointer to the streaming context. - `out` - output buffer, must hold at least `h->outlen` bytes. **Note:** do not call this on a SHAKE context. Use `fio_shake_squeeze` instead. #### `fio_sha3_224` ```c void fio_sha3_224(void *restrict out, const void *restrict data, size_t len); ``` One-shot SHA3-224. **Parameters:** - `out` - output buffer (28 bytes). - `data` - pointer to the data to hash. - `len` - length of the data in bytes. #### `fio_sha3_256` ```c void fio_sha3_256(void *restrict out, const void *restrict data, size_t len); ``` One-shot SHA3-256. **Parameters:** - `out` - output buffer (32 bytes). - `data` - pointer to the data to hash. - `len` - length of the data in bytes. #### `fio_sha3_384` ```c void fio_sha3_384(void *restrict out, const void *restrict data, size_t len); ``` One-shot SHA3-384. **Parameters:** - `out` - output buffer (48 bytes). - `data` - pointer to the data to hash. - `len` - length of the data in bytes. #### `fio_sha3_512` ```c void fio_sha3_512(void *restrict out, const void *restrict data, size_t len); ``` One-shot SHA3-512. **Parameters:** - `out` - output buffer (64 bytes). - `data` - pointer to the data to hash. - `len` - length of the data in bytes. ### SHAKE Extendable-Output Functions #### `fio_shake128_init` ```c fio_sha3_s fio_shake128_init(void); ``` **Returns:** a SHAKE128 context (variable output, rate 168). #### `fio_shake256_init` ```c fio_sha3_s fio_shake256_init(void); ``` **Returns:** a SHAKE256 context (variable output, rate 136). #### `fio_shake_consume` ```c #define fio_shake_consume fio_sha3_consume ``` Alias for `fio_sha3_consume`. Feeds data into a SHAKE context. #### `fio_shake_squeeze` ```c void fio_shake_squeeze(fio_sha3_s *restrict h, void *restrict out, size_t outlen); ``` Squeezes `outlen` bytes from a SHAKE context. On the first call it applies padding and runs the first permutation; later calls continue squeezing and re-permute as needed. **Parameters:** - `h` - pointer to the SHAKE context. - `out` - output buffer, must hold at least `outlen` bytes. - `outlen` - number of bytes to squeeze. #### `fio_shake128` ```c void fio_shake128(void *restrict out, size_t outlen, const void *restrict data, size_t len); ``` One-shot SHAKE128. **Parameters:** - `out` - output buffer. - `outlen` - number of bytes to produce. - `data` - pointer to the data to hash. - `len` - length of the data in bytes. #### `fio_shake256` ```c void fio_shake256(void *restrict out, size_t outlen, const void *restrict data, size_t len); ``` One-shot SHAKE256. **Parameters:** - `out` - output buffer. - `outlen` - number of bytes to produce. - `data` - pointer to the data to hash. - `len` - length of the data in bytes. ### Examples #### One-shot SHA3-256 ```c #define FIO_SHA3 #include "fio-stl.h" #include #include int main(void) { const char *msg = "hello"; uint8_t digest[32]; fio_sha3_256(digest, msg, strlen(msg)); for (size_t i = 0; i < sizeof(digest); ++i) printf("%02x", digest[i]); printf("\n"); return 0; } ``` #### Streaming SHA3-512 ```c fio_sha3_s h = fio_sha3_512_init(); fio_sha3_consume(&h, "hello, ", 7); fio_sha3_consume(&h, "world", 5); uint8_t digest[64]; fio_sha3_finalize(&h, digest); ``` #### SHAKE256 extendable output ```c const char *seed = "hello"; uint8_t out[128]; fio_sha3_s h = fio_shake256_init(); fio_shake_consume(&h, seed, strlen(seed)); fio_shake_squeeze(&h, out, sizeof(out)); ``` **Note:** state is caller-owned; no heap allocation occurs. Keep SHA-3 and SHAKE contexts separate at finalization time (`fio_sha3_finalize` vs. `fio_shake_squeeze`). ------------------------------------------------------------ # AES-GCM ```c #define FIO_AES #include "fio-stl.h" ``` Authenticated encryption with associated data (AEAD) using AES-128-GCM and AES-256-GCM. The API is in-place and matches [`fio_chacha20_poly1305`](https://facil.io/0.8.x/chacha20-poly1305/), so the two cipher suites can be swapped with the same call shape. Hardware acceleration is used when available (x86 AES-NI + PCLMULQDQ, ARM crypto extensions), with a portable fallback otherwise. **Security note:** GCM nonces must never be reused with the same key. A 96-bit nonce runs out of uniqueness long before the key does; generate a fresh nonce for every message. ### API Functions #### `fio_aes128_gcm_enc` ```c SFUNC void fio_aes128_gcm_enc(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Encrypts `data` in place with AES-128-GCM and writes a 16-byte authentication tag to `mac`. **Parameters:** - `mac` — output buffer; must have at least 16 writable bytes. - `data` — plaintext buffer; encrypted in place. May be `NULL` if `len` is 0. - `len` — plaintext length in bytes. - `ad` — additional authenticated data; not encrypted but covered by the tag. May be `NULL`. - `adlen` — length of `ad`. - `key` — 16-byte AES-128 key. - `nonce` — 12-byte nonce. #### `fio_aes128_gcm_dec` ```c SFUNC int fio_aes128_gcm_dec(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Decrypts `data` in place and verifies the 16-byte tag in `mac`. **Parameters:** same as `fio_aes128_gcm_enc`. **Returns:** `0` on success, `-1` if authentication fails. On failure `data` is left unchanged. #### `fio_aes256_gcm_enc` ```c SFUNC void fio_aes256_gcm_enc(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Same as `fio_aes128_gcm_enc` but uses a 32-byte AES-256 key. #### `fio_aes256_gcm_dec` ```c SFUNC int fio_aes256_gcm_dec(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Same as `fio_aes128_gcm_dec` but uses a 32-byte AES-256 key. ### Example ```c #define FIO_AES #include "fio-stl.h" int main(void) { uint8_t key[32] = {0}; uint8_t nonce[12] = {0}; uint8_t msg[32] = "hello, gcm world!"; uint8_t tag[16]; fio_rand_bytes_secure(key, sizeof(key)); fio_rand_bytes_secure(nonce, sizeof(nonce)); fio_aes256_gcm_enc(tag, msg, sizeof(msg), NULL, 0, key, nonce); /* msg is now ciphertext; tag authenticates it. */ if (fio_aes256_gcm_dec(tag, msg, sizeof(msg), NULL, 0, key, nonce)) { printf("authentication failed\n"); return 1; } /* msg is restored plaintext. */ return 0; } ``` ------------------------------------------------------------ # Ed25519 & X25519 ```c #define FIO_ED25519 #include "fio-stl.h" ``` Elliptic-curve cryptography over Curve25519: Ed25519 for signatures and X25519 for key exchange. Both provide 128-bit security with compact 32-byte keys. **Security note:** this implementation has not been independently audited. Use at your own risk, and prefer a tested cryptographic library when available. ### Ed25519 Digital Signatures - Secret key: 32 bytes - Public key: 32 bytes - Signature: 64 bytes #### `fio_ed25519_keypair` ```c SFUNC void fio_ed25519_keypair(uint8_t secret_key[32], uint8_t public_key[32]); ``` Generates a new random Ed25519 key pair. #### `fio_ed25519_public_key` ```c SFUNC void fio_ed25519_public_key(uint8_t public_key[32], const uint8_t secret_key[32]); ``` Derives the public key from a secret key. #### `fio_ed25519_sign` ```c SFUNC int fio_ed25519_sign(uint8_t signature[64], const void *message, size_t len, const uint8_t secret_key[32], const uint8_t public_key[32]); ``` Signs a message. Returns `0` on success, `-1` on failure. The signature is deterministic. #### `fio_ed25519_verify` ```c SFUNC int fio_ed25519_verify(const uint8_t signature[64], const void *message, size_t len, const uint8_t public_key[32]); ``` Verifies a signature. Returns `0` on success, `-1` on failure. ### X25519 Key Exchange - Secret key: 32 bytes - Public key: 32 bytes - Shared secret: 32 bytes #### `fio_x25519_keypair` ```c SFUNC void fio_x25519_keypair(uint8_t secret_key[32], uint8_t public_key[32]); ``` Generates a new random X25519 key pair. #### `fio_x25519_public_key` ```c SFUNC void fio_x25519_public_key(uint8_t public_key[32], const uint8_t secret_key[32]); ``` Derives the public key from a secret key. #### `fio_x25519_shared_secret` ```c SFUNC int fio_x25519_shared_secret(uint8_t shared_secret[32], const uint8_t secret_key[32], const uint8_t their_public_key[32]); ``` Computes a shared secret. Returns `0` on success, `-1` if the result is the all-zero point. **Note:** pass the shared secret through a KDF such as HKDF before using it as an encryption key. ### Key Conversion #### `fio_ed25519_sk_to_x25519` ```c SFUNC void fio_ed25519_sk_to_x25519(uint8_t x_secret_key[32], const uint8_t ed_secret_key[32]); ``` Converts an Ed25519 secret key to an X25519 secret key. #### `fio_ed25519_pk_to_x25519` ```c SFUNC void fio_ed25519_pk_to_x25519(uint8_t x_public_key[32], const uint8_t ed_public_key[32]); ``` Converts an Ed25519 public key to an X25519 public key. ### ECIES Public-Key Encryption #### `FIO_X25519_CIPHERTEXT_LEN` ```c #define FIO_X25519_CIPHERTEXT_LEN(message_len) ((message_len) + 48) ``` Plaintext plus 32-byte ephemeral public key and 16-byte MAC. #### `FIO_X25519_PLAINTEXT_LEN` ```c #define FIO_X25519_PLAINTEXT_LEN(ciphertext_len) \ ((ciphertext_len) > 48 ? ((ciphertext_len)-48) : 0) ``` Reverses `FIO_X25519_CIPHERTEXT_LEN`. Returns `0` for invalid ciphertext. #### `fio_x25519_encrypt` ```c SFUNC int fio_x25519_encrypt(uint8_t *ciphertext, const void *message, size_t message_len, fio_crypto_enc_fn encryption_function, const uint8_t recipient_pk[32]); ``` Encrypts a message to an X25519 public key. Ciphertext format: `ephemeral_pk || mac || encrypted_data`. **Parameters:** - `ciphertext` — output buffer (`message_len + 48` bytes). - `message` — plaintext. - `message_len` — plaintext length. - `encryption_function` — e.g., `fio_chacha20_poly1305_enc`. - `recipient_pk` — recipient's 32-byte X25519 public key. **Returns:** `0` on success, `-1` on failure. #### `fio_x25519_decrypt` ```c SFUNC int fio_x25519_decrypt(uint8_t *plaintext, const uint8_t *ciphertext, size_t ciphertext_len, fio_crypto_dec_fn decryption_function, const uint8_t recipient_sk[32]); ``` Decrypts a message using the recipient's X25519 secret key. **Parameters:** - `plaintext` — output buffer (`ciphertext_len - 48` bytes). - `ciphertext` — ciphertext from `fio_x25519_encrypt`. - `ciphertext_len` — total ciphertext length (must be at least 48). - `decryption_function` — e.g., `fio_chacha20_poly1305_dec`. - `recipient_sk` — recipient's 32-byte X25519 secret key. **Returns:** `0` on success, `-1` on failure. ### Example ```c #define FIO_ED25519 #include "fio-stl.h" int main(void) { uint8_t sk[32], pk[32], sig[64]; fio_ed25519_keypair(sk, pk); const char *msg = "hello, ed25519"; fio_ed25519_sign(sig, msg, strlen(msg), sk, pk); if (fio_ed25519_verify(sig, msg, strlen(msg), pk)) { printf("bad signature\n"); return 1; } return 0; } ``` ------------------------------------------------------------ # P-256 (secp256r1) ```c #define FIO_P256 #include "fio-stl.h" ``` P-256 is the NIST `secp256r1` curve used by TLS and certificate tooling. This module covers two jobs: ECDSA signatures over SHA-256 message hashes, and ECDH shared-secret creation. **Security note:** this implementation has not been independently audited. Use it when the zero-dependency STL path matters; prefer a tested crypto library for long-lived keys, compliance, or high-value trust decisions. ## What It Provides | Use | Function | Notes | | --- | --- | --- | | Sign | `fio_ecdsa_p256_sign` | Creates a DER-encoded ECDSA signature from a 32-byte SHA-256 hash. | | Verify DER signature | `fio_ecdsa_p256_verify` | Accepts `SEQUENCE { r INTEGER, s INTEGER }` and a 65-byte uncompressed public key. | | Verify raw signature | `fio_ecdsa_p256_verify_raw` | Accepts raw 32-byte `r`, `s`, `x`, and `y` values. | | Key pair | `fio_p256_keypair` | Produces a 32-byte secret key and a 65-byte uncompressed public key. | | Shared secret | `fio_p256_shared_secret` | Accepts compressed or uncompressed peer public keys and returns the x-coordinate. | The curve parameters are the standard P-256 parameters from NIST FIPS 186-4: - `p = 2^256 - 2^224 + 2^192 + 2^96 - 1` - `n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551` - curve equation: `y² = x³ - 3x + b (mod p)` ## ECDSA Signatures ### `fio_ecdsa_p256_sign` ```c SFUNC int fio_ecdsa_p256_sign(uint8_t *sig, size_t *sig_len, size_t sig_capacity, const uint8_t msg_hash[32], const uint8_t secret_key[32]); ``` Signs a 32-byte SHA-256 message hash. The function writes a DER-encoded signature to `sig` and stores the actual length in `sig_len`. - `sig_capacity` must be at least `72` bytes. - `secret_key` is a 32-byte big-endian scalar and must be in the valid range `1..n-1`. - The function generates a random ECDSA nonce internally and retries invalid nonces. - Returns `0` on success, `-1` on invalid input or signing failure. It signs the hash you give it; it does not hash the original message. No sneaky kitchen work here. ### `fio_ecdsa_p256_verify` ```c SFUNC int fio_ecdsa_p256_verify(const uint8_t *sig, size_t sig_len, const uint8_t *msg_hash, const uint8_t *pubkey, size_t pubkey_len); ``` Verifies a DER-encoded ECDSA signature against a 32-byte SHA-256 hash. - `sig` is a DER `SEQUENCE { r INTEGER, s INTEGER }`. - `msg_hash` is exactly 32 bytes. - `pubkey` must be an uncompressed P-256 public key: `0x04 || x || y`. - `pubkey_len` must be `65`. - Returns `0` for a valid signature, `-1` for invalid input or a bad signature. ### `fio_ecdsa_p256_verify_raw` ```c SFUNC int fio_ecdsa_p256_verify_raw(const uint8_t r[32], const uint8_t s[32], const uint8_t msg_hash[32], const uint8_t pubkey_x[32], const uint8_t pubkey_y[32]); ``` Verifies a signature when `r` and `s` are already decoded. Public-key coordinates are 32-byte big-endian values. The verifier checks that `r` and `s` are in range, checks the public point is on the P-256 curve, performs the ECDSA verification equation, and returns `0` only when the signature matches. ## ECDH Key Exchange ### `fio_p256_keypair` ```c SFUNC int fio_p256_keypair(uint8_t secret_key[32], uint8_t public_key[65]); ``` Generates a P-256 key pair. - `secret_key` receives a 32-byte scalar. - `public_key` receives `0x04 || x || y` (65 bytes). - Returns `0` on success, `-1` on bad arguments or random generation failure. ### `fio_p256_shared_secret` ```c SFUNC int fio_p256_shared_secret(uint8_t shared_secret[32], const uint8_t secret_key[32], const uint8_t *their_public_key, size_t their_public_key_len); ``` Computes a P-256 ECDH shared secret using your secret key and their public key. - `secret_key` is a 32-byte scalar in the valid range `1..n-1`. - `their_public_key` may be uncompressed (`65` bytes, `0x04 || x || y`) or compressed (`33` bytes, `0x02/0x03 || x`). - The peer point is decompressed when needed and checked against the curve. - `shared_secret` receives the 32-byte x-coordinate of the result. - Returns `0` on success, `-1` on invalid input, invalid point, point at infinity, or all-zero result. Run the shared secret through a KDF such as HKDF before using it as an encryption key. Raw ECDH output is an ingredient, not dinner. ## Example ```c #define FIO_P256 #define FIO_SHA2 #include "fio-stl.h" int main(void) { uint8_t secret_key[32]; uint8_t public_key[65]; if (fio_p256_keypair(secret_key, public_key)) return 1; const char *message = "hello, p-256"; fio_u256 hash = fio_sha256(message, strlen(message)); uint8_t sig[72]; size_t sig_len = 0; if (fio_ecdsa_p256_sign(sig, &sig_len, sizeof(sig), hash.u8, secret_key)) return 1; if (fio_ecdsa_p256_verify(sig, sig_len, hash.u8, public_key, 65)) return 1; return 0; } ``` ------------------------------------------------------------ # P-384 (secp384r1) ```c #define FIO_P384 #include "fio-stl.h" ``` P-384 is the NIST `secp384r1` curve. In this STL, it is a verifier: it checks ECDSA P-384 signatures, mainly for TLS 1.3 certificate chains and CA roots that use P-384. There is no signing or key-generation API here. Just verification. Nice and focused. **Security note:** this implementation has not been independently audited. It is meant for verification of public data; prefer a tested crypto library when certificate trust is central to your application. ## What It Provides | Use | Function | Notes | | --- | --- | --- | | Verify DER signature | `fio_ecdsa_p384_verify` | Accepts `SEQUENCE { r INTEGER, s INTEGER }` and a 97-byte uncompressed public key. | | Verify raw signature | `fio_ecdsa_p384_verify_raw` | Accepts raw 48-byte `r`, `s`, `x`, and `y` values. | Curve parameters from NIST FIPS 186-4: - `p = 2^384 - 2^128 - 2^96 + 2^32 - 1` - `n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973` - curve equation: `y² = x³ - 3x + b (mod p)` - coordinate size: 48 bytes ## Verification API ### `fio_ecdsa_p384_verify` ```c SFUNC int fio_ecdsa_p384_verify(const uint8_t *sig, size_t sig_len, const uint8_t *msg_hash, const uint8_t *pubkey, size_t pubkey_len); ``` Verifies a DER-encoded ECDSA P-384 signature over a 48-byte SHA-384 hash. - `sig` is a DER `SEQUENCE { r INTEGER, s INTEGER }`. - `sig_len` is the signature length in bytes. - `msg_hash` is the SHA-384 hash of the message, exactly 48 bytes. - `pubkey` must be an uncompressed public key: `0x04 || x || y`. - `pubkey_len` must be `97`. - Returns `0` for a valid signature, `-1` for invalid input or a bad signature. The function validates the public-key wrapper, decodes `r` and `s` from DER, then calls `fio_ecdsa_p384_verify_raw`. Compressed public keys are not accepted by this API. ### `fio_ecdsa_p384_verify_raw` ```c SFUNC int fio_ecdsa_p384_verify_raw(const uint8_t r[48], const uint8_t s[48], const uint8_t msg_hash[48], const uint8_t pubkey_x[48], const uint8_t pubkey_y[48]); ``` Verifies an ECDSA P-384 signature when the signature and public key are already decoded. All inputs are fixed-width big-endian values: - `r` and `s`: 48-byte signature scalars. - `msg_hash`: 48-byte SHA-384 hash. - `pubkey_x` and `pubkey_y`: 48-byte public-key coordinates. The verifier checks that `r` and `s` are in range, checks the public point is on the P-384 curve, computes the ECDSA verification equation, and returns `0` only when `r == R.x mod n`. ## Example ```c #define FIO_P384 #include "fio-stl.h" int verify_cert_sig(const uint8_t *sig, size_t sig_len, const uint8_t hash[48], const uint8_t pubkey[97]) { if (fio_ecdsa_p384_verify(sig, sig_len, hash, pubkey, 97)) return -1; return 0; } ``` ## Implementation Notes The implementation uses 6 × 64-bit limbs for field elements, Jacobian coordinates for point work, P-384-specific modular reduction, and a double-scalar multiplication path for ECDSA verification. The scalar multiplication is for public verification data. Do not treat this header as a P-384 signing toolkit; there is no public signing API, and private-key use should go to an audited library. ------------------------------------------------------------ # ASN.1 DER ```c #define FIO_DER #include "fio-stl.h" ``` ASN.1 DER is the small binary grammar hiding inside X.509 certificates, keys, signatures, and TLS messages. This module parses and writes the pieces facil.io needs for that world. The parser is non-allocating. Parsed elements point into the original DER buffer, so keep that buffer alive while you use the results. No buffer, no treasure map. ## Core Types ### `fio_der_element_s` ```c typedef struct { const uint8_t *data; size_t len; uint8_t tag; uint8_t is_constructed; uint8_t tag_class; uint8_t tag_number; } fio_der_element_s; ``` A parsed DER element. `data` points to the content bytes, after the tag and length fields. `len` is the content length. The remaining fields describe the tag byte. ### `fio_der_iterator_s` ```c typedef struct { const uint8_t *pos; const uint8_t *end; } fio_der_iterator_s; ``` Iterator state for walking a `SEQUENCE` or `SET` element. ### Tags and Classes `fio_der_tag_e` defines the common universal tags, including `FIO_DER_INTEGER`, `FIO_DER_BIT_STRING`, `FIO_DER_OID`, `FIO_DER_SEQUENCE`, `FIO_DER_SET`, string types, `FIO_DER_UTC_TIME`, `FIO_DER_GENERALIZED_TIME`, and context wrappers `FIO_DER_CONTEXT_0` through `FIO_DER_CONTEXT_3`. `fio_der_class_e` names the tag classes: universal, application, context-specific, and private. ## OID Values OID constants live in the X.509 module (`FIO_X509_OID_*`, see the X.509 documentation). An OID value is a plain `fio_u128` where bytes 0-14 hold the DER content bytes (zero-padded) and byte 15 holds the content length. The length is folded in because `0x00` is a legal OID content byte (arc 0), so padding alone could not distinguish `{..0B}` from `{..0B 00}` - different OIDs. Oversized OIDs (content > 15 bytes) simply never match, which is safe: AlgorithmIdentifier OIDs are attacker-controlled and RFC 5280 requires exact match. Build a value once per parsed element with `fio___der_oid_value` (internal), then compare it against any number of constants with `fio___der_oid_eq` (internal) - two u64-lane `==` comparisons, no memcmp, no call: ```c fio_u128 oid = fio___der_oid_value(&elem); /* build ONCE per element */ if (fio___der_oid_eq(oid, FIO_X509_OID_COMMON_NAME)) { /* ... */ } ``` ## Parsing ### `fio_der_parse` ```c SFUNC const uint8_t *fio_der_parse(fio_der_element_s *elem, const uint8_t *data, size_t data_len); ``` Parses one DER element. On success, fills `elem` and returns a pointer to the next element. On error, returns `NULL`. It rejects invalid DER lengths, truncated buffers, unsupported high-tag-number encodings, and indefinite lengths. ### `fio_der_element_total_len` ```c FIO_IFUNC size_t fio_der_element_total_len(const fio_der_element_s *elem, const uint8_t *data); ``` Returns the full encoded length of an element: tag + length + content. `data` must be the original pointer used to parse the element. ## Type Parsers ### `fio_der_parse_integer` ```c SFUNC int fio_der_parse_integer(const fio_der_element_s *elem, uint64_t *value); ``` Parses an ASN.1 `INTEGER`. For small integers, pass `value` and receive the 64-bit value. For large integers such as RSA moduli, pass `NULL` and use `elem->data` / `elem->len` directly. Leading zero bytes for positive integers are handled. Returns `0` on success, `-1` on error. ### `fio_der_parse_bit_string` ```c SFUNC int fio_der_parse_bit_string(const fio_der_element_s *elem, const uint8_t **bits, size_t *bit_len, uint8_t *unused_bits); ``` Parses an ASN.1 `BIT STRING`. `bits` points into the element data after the unused-bit count byte. `bit_len` is the byte length of the bit payload. `unused_bits` is the number of unused bits in the last byte. ### `fio_der_parse_oid` ```c SFUNC int fio_der_parse_oid(const fio_der_element_s *elem, char *buf, size_t buf_len); ``` Converts an ASN.1 `OBJECT IDENTIFIER` into dotted text such as `1.2.840.113549.1.1.11`. Returns the number of characters written, excluding the NUL byte, or `-1` on error. ### `fio_der_parse_time` ```c SFUNC int fio_der_parse_time(const fio_der_element_s *elem, int64_t *unix_time); ``` Parses `UTCTime` or `GeneralizedTime` into a Unix timestamp. Times must be UTC (`Z`). Fractional seconds are skipped when present. ### `fio_der_parse_string` ```c FIO_IFUNC const char *fio_der_parse_string(const fio_der_element_s *elem, size_t *len); ``` Returns a pointer to the string bytes for supported ASN.1 string tags, with the byte length in `len`. This does not validate or transcode the text; it simply gives you the payload. ### `fio_der_parse_boolean` ```c FIO_IFUNC int fio_der_parse_boolean(const fio_der_element_s *elem, int *value); ``` Parses a DER boolean and writes `0` or non-zero to `value`. ## Walking Sequences and Sets ### `fio_der_iterator_init` ```c FIO_IFUNC void fio_der_iterator_init(fio_der_iterator_s *it, const fio_der_element_s *sequence); ``` Initializes an iterator over the content bytes of a parsed `SEQUENCE` or `SET`. ### `fio_der_iterator_next` ```c SFUNC int fio_der_iterator_next(fio_der_iterator_s *it, fio_der_element_s *elem); ``` Parses the next child element and advances the iterator. Returns `0` when an element was read, `-1` at end or on parse error. ### `fio_der_iterator_has_next` ```c FIO_IFUNC int fio_der_iterator_has_next(const fio_der_iterator_s *it); ``` Returns `1` when the iterator still has bytes to parse, otherwise `0`. ## Tag Helpers ```c FIO_IFUNC int fio_der_is_tag(const fio_der_element_s *elem, uint8_t tag); FIO_IFUNC int fio_der_is_context_tag(const fio_der_element_s *elem, uint8_t tag_num); FIO_IFUNC uint8_t fio_der_tag_number(const fio_der_element_s *elem); ``` Use these to keep parser code readable: - `fio_der_is_tag` checks a universal tag such as `FIO_DER_INTEGER`. - `fio_der_is_context_tag` checks `[0]`, `[1]`, and friends. - `fio_der_tag_number` returns the decoded tag number. ## Encoding All encoder functions return the number of bytes written or needed. Pass `NULL` as `buf` to calculate the encoded length before writing. ### Basic Encoders ```c SFUNC size_t fio_der_encode_length(uint8_t *buf, size_t len); SFUNC size_t fio_der_encode_integer(uint8_t *buf, const uint8_t *data, size_t data_len); SFUNC size_t fio_der_encode_integer_small(uint8_t *buf, uint64_t value); SFUNC size_t fio_der_encode_null(uint8_t *buf); SFUNC size_t fio_der_encode_boolean(uint8_t *buf, int value); ``` These write DER length fields, positive integers, `NULL`, and booleans. `fio_der_encode_integer` expects big-endian integer bytes and adds the leading zero byte when DER needs one to keep the integer positive. OIDs are encoded with the internal `fio___der_encode_oid(buf, fio_u128 oid)`, which TLV-wraps an OID value (see OID Values above) without any dot-string parsing. ### String and Byte Encoders ```c SFUNC size_t fio_der_encode_utf8_string(uint8_t *buf, const char *str, size_t str_len); SFUNC size_t fio_der_encode_printable_string(uint8_t *buf, const char *str, size_t str_len); SFUNC size_t fio_der_encode_bit_string(uint8_t *buf, const uint8_t *bits, size_t bit_len, uint8_t unused_bits); SFUNC size_t fio_der_encode_octet_string(uint8_t *buf, const uint8_t *data, size_t data_len); ``` These write the tag, DER length, and payload for common primitive values. ### Header Encoders ```c SFUNC size_t fio_der_encode_sequence_header(uint8_t *buf, size_t content_len); SFUNC size_t fio_der_encode_set_header(uint8_t *buf, size_t content_len); SFUNC size_t fio_der_encode_context_header(uint8_t *buf, uint8_t tag_num, size_t content_len, int constructed); ``` These write only the wrapper tag and length. Write the content bytes immediately after the returned header length. ### Time Encoders ```c SFUNC size_t fio_der_encode_utc_time(uint8_t *buf, int64_t unix_time); SFUNC size_t fio_der_encode_generalized_time(uint8_t *buf, int64_t unix_time); ``` These encode Unix timestamps as DER `UTCTime` or `GeneralizedTime`. ## Example ```c #define FIO_DER #include "fio-stl.h" void scan_sequence(const uint8_t *der, size_t der_len) { fio_der_element_s seq; if (!fio_der_parse(&seq, der, der_len)) return; if (!fio_der_is_tag(&seq, FIO_DER_SEQUENCE)) return; fio_der_iterator_s it; fio_der_iterator_init(&it, &seq); fio_der_element_s elem; while (fio_der_iterator_next(&it, &elem) == 0) { if (fio_der_is_tag(&elem, FIO_DER_OID)) { fio_u128 oid = fio___der_oid_value(&elem); /* compare with FIO_X509_OID_* constants via fio___der_oid_eq, or */ char dot[128]; if (fio_der_parse_oid(&elem, dot, sizeof(dot)) > 0) { /* dotted string available for diagnostics */ } } } } ``` ------------------------------------------------------------ # RSA Signatures ```c #define FIO_RSA #include "fio-stl.h" ``` RSA here means signature work for TLS and X.509: verify PKCS#1 v1.5 signatures, verify RSA-PSS signatures, and create RSA-PSS signatures for TLS 1.3 `CertificateVerify`. Supported modulus sizes are 2048, 3072, and 4096 bits. Smaller keys do not get a party hat. **Security note:** this implementation has not been independently audited. Prefer a tested crypto library for high-value private keys, compliance, HSM integration, or anything that keeps auditors awake. ## Constants and Hash IDs ```c #define FIO_RSA_MAX_BITS 4096 #define FIO_RSA_MAX_BYTES (FIO_RSA_MAX_BITS / 8) #define FIO_RSA_MAX_WORDS (FIO_RSA_MAX_BYTES / 8) ``` ```c typedef enum { FIO_RSA_HASH_SHA256 = 0, FIO_RSA_HASH_SHA384 = 1, FIO_RSA_HASH_SHA512 = 2, } fio_rsa_hash_e; ``` `hash_len` must match the selected hash: 32 bytes for SHA-256, 48 for SHA-384, and 64 for SHA-512. ## Key Types ### `fio_rsa_pubkey_s` ```c typedef struct { const uint8_t *n; size_t n_len; const uint8_t *e; size_t e_len; } fio_rsa_pubkey_s; ``` Public key for verification. `n` is the modulus and `e` is the public exponent, both as big-endian byte arrays. This matches the DER layout used in X.509 certificates. ### `fio_rsa_privkey_s` ```c typedef struct { const uint8_t *n; size_t n_len; const uint8_t *d; size_t d_len; const uint8_t *e; size_t e_len; const uint8_t *p; size_t p_len; const uint8_t *q; size_t q_len; const uint8_t *dP; size_t dP_len; const uint8_t *dQ; size_t dQ_len; const uint8_t *qInv; size_t qInv_len; } fio_rsa_privkey_s; ``` Private key for RSA-PSS signing. `n` and `d` are required. The public exponent `e` and CRT fields (`p`, `q`, `dP`, `dQ`, `qInv`) are optional. When CRT parameters are available, signing uses CRT with message blinding. If only `n` and `d` are available, signing falls back to the non-CRT path. ## Verification ### `fio_rsa_verify_pkcs1` ```c SFUNC int fio_rsa_verify_pkcs1(const uint8_t *sig, size_t sig_len, const uint8_t *msg_hash, size_t hash_len, fio_rsa_hash_e hash_alg, const fio_rsa_pubkey_s *key); ``` Verifies an RSA PKCS#1 v1.5 signature with `DigestInfo` encoding. This covers the X.509 signature algorithms commonly named: - `sha256WithRSAEncryption` - `sha384WithRSAEncryption` - `sha512WithRSAEncryption` Parameters: - `sig`: signature bytes; length must match the modulus length. - `sig_len`: signature length in bytes. - `msg_hash`: pre-computed message hash. - `hash_len`: 32, 48, or 64 bytes. - `hash_alg`: matching `FIO_RSA_HASH_*` value. - `key`: RSA public key. Returns `0` for a valid signature, `-1` on invalid input or a bad signature. ### `fio_rsa_verify_pss` ```c SFUNC int fio_rsa_verify_pss(const uint8_t *sig, size_t sig_len, const uint8_t *msg_hash, size_t hash_len, fio_rsa_hash_e hash_alg, const fio_rsa_pubkey_s *key); ``` Verifies an RSA-PSS signature. This is the RSA signature style TLS 1.3 uses for `CertificateVerify`. The implementation uses: - MGF1 with the same hash function; - salt length equal to the hash length; - trailer byte `0xBC`. Returns `0` for a valid signature, `-1` on invalid input or a bad signature. ## Signing ### `fio_rsa_sign_pss` ```c SFUNC int fio_rsa_sign_pss(uint8_t *signature, size_t *sig_len, const uint8_t *msg_hash, size_t hash_len, fio_rsa_hash_e hash_alg, const fio_rsa_privkey_s *key); ``` Creates an RSA-PSS signature using `RSASSA-PSS-SIGN` from RFC 8017, with the TLS 1.3 settings listed above. - `signature` must have room for `key->n_len` bytes. - `sig_len` receives the signature length, which equals `key->n_len` on success. - `msg_hash` is already hashed; this function does not hash the message. - `key` must include `n` and `d`; CRT fields are optional. Returns `0` on success, `-1` on error. PKCS#1 v1.5 signing is not exposed. TLS 1.3 does not want it for `CertificateVerify`, and this header agrees. ## Example: Verify an RSA-PSS Signature ```c #define FIO_RSA #define FIO_SHA2 #include "fio-stl.h" int verify_pss(const uint8_t *sig, size_t sig_len, const uint8_t *modulus, size_t modulus_len, const uint8_t *exponent, size_t exponent_len, const void *message, size_t message_len) { fio_rsa_pubkey_s key = { .n = modulus, .n_len = modulus_len, .e = exponent, .e_len = exponent_len, }; fio_u256 hash = fio_sha256(message, message_len); return fio_rsa_verify_pss(sig, sig_len, hash.u8, 32, FIO_RSA_HASH_SHA256, &key); } ``` ## Practical Notes - The signature length must match the modulus length. - The public exponent is usually `65537` (`0x010001`), but the API accepts DER-style big-endian bytes. - The module works on pre-computed hashes. Pick the hash to match the certificate or protocol signature algorithm. - For X.509 parsing, this module is normally used through [X.509](https://facil.io/0.8.x/http-1-x-parser/), which extracts the RSA key and dispatches verification. ------------------------------------------------------------ # ML-KEM-768 ```c #define FIO_MLKEM #include "fio-stl.h" ``` ML-KEM-768 is the FIPS 203 module-lattice key encapsulation mechanism: a post-quantum way to agree on a shared secret. This header also exposes the TLS hybrid `X25519MLKEM768` shape, which combines ML-KEM-768 with X25519. **Security note:** this implementation has not been independently audited. Use it carefully, and prefer audited crypto stacks for high-value deployments. ## ML-KEM-768 Sizes ```c #define FIO_MLKEM768_PUBLICKEYBYTES 1184 #define FIO_MLKEM768_SECRETKEYBYTES 2400 #define FIO_MLKEM768_CIPHERTEXTBYTES 1088 #define FIO_MLKEM768_SSBYTES 32 #define FIO_MLKEM768_SYMBYTES 32 ``` Parameters: `n = 256`, `k = 3`, `q = 3329`, `eta1 = 2`, `eta2 = 2`, `d_u = 10`, `d_v = 4`. ## ML-KEM-768 API ### `fio_mlkem768_keypair` ```c SFUNC int fio_mlkem768_keypair(uint8_t pk[1184], uint8_t sk[2400]); ``` Generates a random ML-KEM-768 key pair using the system CSPRNG. Returns `0` on success, `-1` on failure. ### `fio_mlkem768_keypair_derand` ```c SFUNC int fio_mlkem768_keypair_derand(uint8_t pk[1184], uint8_t sk[2400], const uint8_t coins[64]); ``` Generates a deterministic key pair. `coins` is exactly 64 bytes: `d || z`. This is mostly for test vectors and reproducible checks; production code should normally use `fio_mlkem768_keypair`. ### `fio_mlkem768_encaps` ```c SFUNC int fio_mlkem768_encaps(uint8_t ct[1088], uint8_t ss[32], const uint8_t pk[1184]); ``` Creates a ciphertext and shared secret for the holder of `pk`. Randomness comes from the system CSPRNG. Send `ct`; keep `ss` as the shared secret input to your KDF or protocol. ### `fio_mlkem768_encaps_derand` ```c SFUNC int fio_mlkem768_encaps_derand(uint8_t ct[1088], uint8_t ss[32], const uint8_t pk[1184], const uint8_t coins[32]); ``` Deterministic encapsulation using 32 bytes of caller-provided randomness. Useful for known-answer tests. Boring in production, which is exactly the point. ### `fio_mlkem768_decaps` ```c SFUNC int fio_mlkem768_decaps(uint8_t ss[32], const uint8_t ct[1088], const uint8_t sk[2400]); ``` Recovers the shared secret from a ciphertext and secret key. ML-KEM uses implicit rejection: invalid ciphertexts produce a pseudorandom shared secret derived from the secret key and ciphertext instead of a loud failure. That prevents chosen-ciphertext games from turning your error path into an oracle. Returns `0` for well-formed inputs. ## X25519MLKEM768 Hybrid `X25519MLKEM768` is the TLS 1.3 hybrid key exchange shape from the ECDHE/ML-KEM draft. It needs `FIO_ED25519` too, because this STL implements X25519 in the Ed25519/Curve25519 header. The name starts with X25519, but the bytes start with ML-KEM. Yes, naming is hard. ```c #define FIO_X25519MLKEM768_PUBLICKEYBYTES (32 + 1184) /* 1216 */ #define FIO_X25519MLKEM768_SECRETKEYBYTES (32 + 2400) /* 2432 */ #define FIO_X25519MLKEM768_CIPHERTEXTBYTES (32 + 1088) /* 1120 */ #define FIO_X25519MLKEM768_SSBYTES 64 ``` | Value | Layout | | --- | --- | | Public key | `ML-KEM-768_ek` (1184) `||` `X25519_pk` (32) | | Secret key | `ML-KEM-768_dk` (2400) `||` `X25519_sk` (32) | | Ciphertext | `ML-KEM-768_ct` (1088) `||` `X25519_ephemeral_pk` (32) | | Shared secret | `ML-KEM-768_ss` (32) `||` `X25519_ss` (32) | ### `fio_x25519mlkem768_keypair` ```c SFUNC int fio_x25519mlkem768_keypair(uint8_t pk[1216], uint8_t sk[2432]); ``` Generates both the ML-KEM-768 and X25519 key pairs with system randomness. Returns `0` on success. ### `fio_x25519mlkem768_encaps` ```c SFUNC int fio_x25519mlkem768_encaps(uint8_t ct[1120], uint8_t ss[64], const uint8_t pk[1216]); ``` Performs ML-KEM encapsulation and X25519 ephemeral key exchange. The returned shared secret is the concatenation `ML-KEM-768_ss || X25519_ss`. ### `fio_x25519mlkem768_decaps` ```c SFUNC int fio_x25519mlkem768_decaps(uint8_t ss[64], const uint8_t ct[1120], const uint8_t sk[2432]); ``` Decapsulates the ML-KEM part and computes the X25519 shared secret. Returns `0` on success, or `-1` if X25519 rejects the peer point. ML-KEM invalid ciphertexts still use implicit rejection. ## Example ```c #define FIO_MLKEM #include "fio-stl.h" int roundtrip(void) { uint8_t pk[FIO_MLKEM768_PUBLICKEYBYTES]; uint8_t sk[FIO_MLKEM768_SECRETKEYBYTES]; uint8_t ct[FIO_MLKEM768_CIPHERTEXTBYTES]; uint8_t sender_ss[FIO_MLKEM768_SSBYTES]; uint8_t receiver_ss[FIO_MLKEM768_SSBYTES]; if (fio_mlkem768_keypair(pk, sk)) return -1; if (fio_mlkem768_encaps(ct, sender_ss, pk)) return -1; if (fio_mlkem768_decaps(receiver_ss, ct, sk)) return -1; return fio_memcmp(sender_ss, receiver_ss, sizeof(sender_ss)); } ``` ## Implementation Notes The implementation uses ML-KEM-768 parameters, NTT arithmetic, Montgomery and Barrett reduction, and optional NEON / AVX2 paths for vectorized polynomial work. Those SIMD paths are speed knobs, not different APIs. ------------------------------------------------------------ # X.509 Certificates ```c #define FIO_X509 #include "fio-stl.h" ``` This module parses DER-encoded X.509v3 certificates for TLS 1.3 style certificate checks. It is a compact parser, verifier, trust-store checker, TLS Certificate-message splitter, and small self-signed certificate generator. It is non-allocating while parsing: `fio_x509_cert_s` stores pointers into the original DER bytes. Keep those bytes alive. Certificates dislike disappearing floors. **Scope note:** this is a minimal TLS-oriented X.509 implementation, not a full PKI policy engine. Not all X.509 features are supported. ## Supported Pieces - RSA, ECDSA P-256, ECDSA P-384, and Ed25519 public keys. - RSA PKCS#1 v1.5, RSA-PSS, ECDSA, and Ed25519 signature verification. - Validity period checks. - Hostname matching using SAN DNS names and CN fallback, with one-label wildcards. - Basic Constraints and Key Usage. - Certificate chain validation against an optional trust store. - TLS 1.3 Certificate message parsing. - Self-signed certificate generation for Ed25519 and P-256 keys. ## Main Types ### Key and Signature Enums ```c typedef enum { FIO_X509_KEY_UNKNOWN = 0, FIO_X509_KEY_RSA = 1, FIO_X509_KEY_ECDSA_P256 = 2, FIO_X509_KEY_ECDSA_P384 = 3, FIO_X509_KEY_ED25519 = 4, } fio_x509_key_algo_e; ``` ```c typedef enum { FIO_X509_SIGNATURE_UNKNOWN = 0, FIO_X509_SIGNATURE_RSA_PKCS1_SHA256 = 1, FIO_X509_SIGNATURE_RSA_PKCS1_SHA384 = 2, FIO_X509_SIGNATURE_RSA_PKCS1_SHA512 = 3, FIO_X509_SIGNATURE_RSA_PSS_SHA256 = 4, FIO_X509_SIGNATURE_RSA_PSS_SHA384 = 5, FIO_X509_SIGNATURE_RSA_PSS_SHA512 = 6, FIO_X509_SIGNATURE_ECDSA_SHA256 = 7, FIO_X509_SIGNATURE_ECDSA_SHA384 = 8, FIO_X509_SIGNATURE_ED25519 = 9, } fio_x509_signature_algo_e; ``` ### Key Usage and Errors `fio_x509_key_usage_e` defines RFC 5280 key-usage bits such as `FIO_X509_KU_DIGITAL_SIGNATURE`, `FIO_X509_KU_KEY_ENCIPHERMENT`, and `FIO_X509_KU_KEY_CERT_SIGN`. ASN.1 bit strings use MSB-first ordering, so the constants look a little backwards until they save you from off-by-one sadness. `fio_x509_error_e` gives chain validation results: `FIO_X509_OK`, parse failure, expired/not-yet-valid, signature failure, issuer mismatch, not-a-CA, no trust anchor, hostname mismatch, empty chain, and chain-too-long. ### `fio_x509_trust_store_s` ```c typedef struct { const uint8_t **roots; const size_t *root_lens; size_t root_count; } fio_x509_trust_store_s; ``` A simple root store: arrays of DER certificate pointers and lengths. ### `fio_tls_cert_entry_s` ```c typedef struct { const uint8_t *cert; size_t cert_len; } fio_tls_cert_entry_s; ``` One certificate entry extracted from a TLS 1.3 `Certificate` handshake message. ### `fio_x509_cert_s` ```c typedef struct fio_x509_cert_s fio_x509_cert_s; ``` The parsed certificate structure uses non-owning buffer views (`fio_buf_info_s` / `fio_ubuf_info_s`) and packs to 256 bytes with no interior padding: - `verified`: non-zero when a TLS backend verified this certificate's chain (peer inspection only; always zero after `fio_x509_parse`); - `der`: a reference to the original DER bytes (not a copy); - `fingerprint`: 32-byte SHA-256 of the DER bytes (see `fio_x509_fingerprint`); - `version`, `serial`, and validity timestamps; - raw subject and issuer DNs; - subject CN; - public key type and key data; - signature algorithm and signature bytes; - TBS certificate bytes used for verification; - Basic Constraints (`is_ca`), Key Usage (`has_key_usage`, `key_usage`), and SAN data (first DNS name, first IP address, and the raw SAN extension for iterating the rest). All view pointers reference the original DER buffer. ## Parsing and Single-Certificate Checks ### `fio_x509_parse` ```c SFUNC int fio_x509_parse(fio_x509_cert_s *cert, const uint8_t *der_data, size_t der_len); ``` Parses one DER certificate. `cert` is zeroed first. Returns `0` on success, `-1` on parse error. ### `fio_x509_fingerprint` ```c SFUNC void fio_x509_fingerprint(fio_x509_cert_s *cert); ``` Computes the SHA-256 of the certificate's DER bytes into `cert->fingerprint` (32 raw bytes). Call after `fio_x509_parse`; hashing is lazy so parsing never pays for it. Requires the SHA2 module. ### `fio_x509_verify_signature` ```c SFUNC int fio_x509_verify_signature(const fio_x509_cert_s *cert, const fio_x509_cert_s *issuer); ``` Verifies that `issuer` signed `cert`, using the issuer public key and the certificate signature algorithm. Returns `0` when the signature is valid, `-1` otherwise. ### `fio_x509_check_validity` ```c FIO_IFUNC int fio_x509_check_validity(const fio_x509_cert_s *cert, int64_t current_time); ``` Checks `not_before <= current_time <= not_after`. Returns `0` when the certificate is in its validity window. ### `fio_x509_match_hostname` ```c SFUNC int fio_x509_match_hostname(const fio_x509_cert_s *cert, const char *hostname, size_t hostname_len); ``` Checks whether a certificate matches a hostname. SAN DNS entries are supported, with CN fallback. Wildcards are one-label wildcards such as `*.example.com`; they do not swallow `a.b.example.com`. Returns `0` for a match, `-1` for no match. Distinguished Names are compared as raw DER with the core `FIO_BUF_INFO_IS_EQ` macro (e.g., `FIO_BUF_INFO_IS_EQ(cert.issuer, root.subject)`). ## Chain Validation ### `fio_x509_verify_chain` ```c SFUNC int fio_x509_verify_chain(const uint8_t **certs, const size_t *cert_lens, size_t cert_count, const char *hostname, int64_t current_time, fio_x509_trust_store_s *trust_store); ``` Validates a certificate chain for TLS-style use. Expected order: 1. `certs[0]`: end-entity / server certificate. 2. `certs[1]`: intermediate that signed `certs[0]`. 3. `certs[n-1]`: closest-to-root certificate. Validation parses all certificates, checks validity periods, optionally matches the hostname, verifies each signature with the next certificate, checks issuer/subject DN links, requires CA certificates where needed, and optionally checks the final certificate against `trust_store`. Trust anchors are matched by (subject DN, public key): the anchor's key must verify the final certificate's signature. This also covers self-signed certificates — a same-named impostor with a different key is rejected. Returns `FIO_X509_OK` (`0`) on success, or a `FIO_X509_ERR_*` code. ### `fio_x509_is_trusted` ```c SFUNC int fio_x509_is_trusted(const fio_x509_cert_s *cert, fio_x509_trust_store_s *trust_store); ``` Checks whether `cert` appears in the trust store by subject DN match. Returns `0` if trusted, `-1` if not found. ### `fio_x509_error_str` ```c FIO_IFUNC const char *fio_x509_error_str(int error); ``` Returns a static string for a chain validation error code. ## TLS Certificate Messages ```c #define FIO_TLS_CERT_PARSE_ERROR ((size_t)-1) ``` ### `fio_tls_parse_certificate_message` ```c SFUNC size_t fio_tls_parse_certificate_message(fio_tls_cert_entry_s *entries, size_t max_entries, const uint8_t *data, size_t data_len); ``` Parses a TLS 1.3 `Certificate` message body, after the handshake header. It extracts certificate entries and skips per-certificate extensions. Returns the number of certificates parsed, or `FIO_TLS_CERT_PARSE_ERROR` on malformed input. ## Self-Signed Certificate Generation Generation supports Ed25519 and P-256 key pairs. ```c typedef enum { FIO_X509_KEYPAIR_ED25519 = 1, FIO_X509_KEYPAIR_P256 = 2, } fio_x509_keypair_type_e; ``` `fio_x509_keypair_s` stores the selected key type, secret key bytes, public key bytes, and their lengths. Use `fio_x509_keypair_clear` when done. `fio_x509_cert_options_s` controls the subject CN, organization, organizational unit, country, validity window, SAN DNS names, CA flag, and key-usage bits. ### `fio_x509_keypair_ed25519` ```c SFUNC int fio_x509_keypair_ed25519(fio_x509_keypair_s *keypair); ``` Generates an Ed25519 key pair for certificate signing. Requires the Ed25519 module to be included. Returns `0` on success. ### `fio_x509_keypair_p256` ```c SFUNC int fio_x509_keypair_p256(fio_x509_keypair_s *keypair); ``` Generates a P-256 key pair for certificate signing. Requires the P-256 module to be included. Returns `0` on success. ### `fio_x509_self_signed_cert` ```c SFUNC size_t fio_x509_self_signed_cert(uint8_t *buf, size_t buf_len, const fio_x509_keypair_s *keypair, const fio_x509_cert_options_s *options); ``` Writes a DER-encoded self-signed X.509v3 certificate. Call with `buf == NULL` to get a worst-case buffer size, then call again with a real buffer. Returns bytes written, or `0` on error. ### `fio_x509_keypair_clear` ```c FIO_IFUNC void fio_x509_keypair_clear(fio_x509_keypair_s *keypair); ``` Securely clears key material and zeros the structure. ## Example: Parse and Check Hostname ```c #define FIO_X509 #include "fio-stl.h" int check_cert(const uint8_t *der, size_t der_len, int64_t now) { fio_x509_cert_s cert; if (fio_x509_parse(&cert, der, der_len)) return -1; if (fio_x509_check_validity(&cert, now)) return -1; if (fio_x509_match_hostname(&cert, "example.com", 11)) return -1; return 0; } ``` ## Practical Notes - Define the crypto modules you need before including the STL: RSA, P-256, P-384, Ed25519, SHA-2, and ASN.1 may all matter depending on certificate algorithms. - `trust_store == NULL` skips root trust checking in `fio_x509_verify_chain`; useful for structural tests, not for real trust decisions. - Hostname checks are DNS-name focused. IP address SAN handling is not a full replacement for platform certificate validation. - Parsed strings and DNs are raw certificate data. Normalize policy elsewhere if your application needs browser-grade PKI behavior. ------------------------------------------------------------ # PEM Parser ```c #define FIO_PEM #include "fio-stl.h" ``` PEM wraps DER bytes in Base64 armor: ```text -----BEGIN
...` and open `` for body rows - table cell alignment uses `align="left"`, `align="right"`, or `align="center"` - GFM task list items render disabled checkbox inputs - autolinks add `mailto:` for bare email addresses and `http://` for `www.` links - image alt text is escaped and strips nested link / emphasis markup ### Thread-Safety The renderer uses caller-provided input and a local renderer state. It is thread-safe as long as separate calls do not mutate the same `bstr_target` at the same time. ### Example ```c #define FIO_MD2HTML #include "fio-stl.h" #include int main(void) { char md[] = "# Demo\n\nHello **world**.\n"; char *html = fio_md2html(NULL, FIO_BUF_INFO1(md)); if (!html) return 1; fwrite(html, 1, fio_bstr_len(html), stdout); fio_bstr_free(html); return 0; } ``` --- # Mustache-ish Template Engine ```c #define FIO_MUSTACHE #include "fio-stl.h" ``` A Mustache-ish template parser and renderer with variables, sections, inverted sections, partials, delimiter changes, comments, and optional YAML front matter callbacks. Logic-less-ish, because C callbacks still get a vote. Implemented in [`./104 mustache.h`](https://facil.io/edge/api/104-mustache/). Defining `FIO_MUSTACHE` pulls in string / `fio_bstr` helpers as needed. ### Configuration Macros #### `FIO_MUSTACHE_MAX_DEPTH` ```c #ifndef FIO_MUSTACHE_MAX_DEPTH #define FIO_MUSTACHE_MAX_DEPTH 128 #endif ``` Maximum parser / builder nesting depth for sections and partials. #### `FIO_MUSTACHE_PRESERVE_PADDING` ```c #ifndef FIO_MUSTACHE_PRESERVE_PADDING #define FIO_MUSTACHE_PRESERVE_PADDING 0 #endif ``` When enabled, preserves padding for stand-alone variables and partial templates. #### `FIO_MUSTACHE_LAMBDA_SUPPORT` ```c #ifndef FIO_MUSTACHE_LAMBDA_SUPPORT #define FIO_MUSTACHE_LAMBDA_SUPPORT 0 #endif ``` When enabled, stores raw section text for lambda-style section handling through `is_lambda`. #### `FIO_MUSTACHE_ISOLATE_PARTIALS` ```c #ifndef FIO_MUSTACHE_ISOLATE_PARTIALS #define FIO_MUSTACHE_ISOLATE_PARTIALS 1 #endif ``` When enabled, limits partial lookup scope to the context of the partial's section. #### `FIO_MUSTACHE_SECURE_PATH` ```c #ifndef FIO_MUSTACHE_SECURE_PATH #define FIO_MUSTACHE_SECURE_PATH 1 #endif ``` When enabled, skips partial names that attempt `../` path traversal. ### Syntax Quick Map | Syntax | Meaning | | --- | --- | | `{{name}}` | escaped variable | | `{{{name}}}` | raw variable | | `{{&name}}` | raw variable | | `{{#items}}...{{/items}}` | truthy section / array iteration | | `{{^items}}...{{/items}}` | inverted section | | `{{>partial}}` | partial template include | | `{{! comment}}` | comment | | `{{=<% %>=}}` | delimiter change | | `{{.}}` | current context | Dot lookup such as `person.name` is supported by repeatedly calling `get_var` for path segments. ### Types #### `fio_mustache_s` ```c typedef struct fio_mustache_s fio_mustache_s; ``` Opaque parsed template object. Internally it is stored as a `fio_bstr` instruction stream. **Ownership:** `fio_mustache_load` returns an owned template. Free it with `fio_mustache_free`. `fio_mustache_dup` creates a copy; it is not a shared reference counter in this header. #### `fio_mustache_bargs_s` ```c typedef struct fio_mustache_bargs_s fio_mustache_bargs_s; ``` Forward declaration for build arguments. The full struct is listed below. #### `fio_mustache_load_args_s` ```c typedef struct { fio_buf_info_s data; fio_buf_info_s filename; fio_buf_info_s (*load_file_data)(fio_buf_info_s filename, void *udata); void (*free_file_data)(fio_buf_info_s file_data, void *udata); void (*on_yaml_front_matter)(fio_buf_info_s yaml_front_matter, void *udata); void *udata; } fio_mustache_load_args_s; ``` Load / parse settings. **Members:** - `data` - preloaded template bytes - `filename` - file name, also used as base path for partials - `load_file_data` - callback that loads file contents - `free_file_data` - callback that frees loaded file contents - `on_yaml_front_matter` - called when front matter is found - `udata` - user data for load callbacks If both `load_file_data` and `free_file_data` are missing, defaults use `fio_bstr_readfile` and `fio_bstr_free`. If one is custom, provide the matching cleanup. Either `filename` or `data` is required. #### `fio_mustache_bargs_s` ```c struct fio_mustache_bargs_s { void *(*write_text)(void *udata, fio_buf_info_s txt); void *(*write_text_escaped)(void *udata, fio_buf_info_s raw); void *(*get_var)(void *ctx, fio_buf_info_s name); size_t (*array_length)(void *ctx); void *(*get_var_index)(void *ctx, size_t index); fio_buf_info_s (*var2str)(void *var); int (*var_is_truthful)(void *ctx); void (*release_var)(void *ctx); int (*is_lambda)(void **udata, void *ctx, fio_buf_info_s raw_template_section); void *ctx; void *udata; }; ``` Build / render callbacks. **Members:** - `write_text` - writes raw template text - `write_text_escaped` - writes escaped variable text - `get_var` - returns a value for `name` from `ctx` - `array_length` - returns array length, or `0` for non-array values - `get_var_index` - returns an item context by index - `var2str` - returns a string view for a value - `var_is_truthful` - returns non-zero for truthy values - `release_var` - releases values returned by callbacks - `is_lambda` - handles lambda sections when lambda support is enabled - `ctx` - root render context - `udata` - output / user pointer; the final value is returned by `fio_mustache_build` If both writer callbacks are missing, defaults append to a `fio_bstr` in `udata`. The returned pointer should be freed with `fio_bstr_free`. ### Loading #### `fio_mustache_load` ```c SFUNC fio_mustache_s *fio_mustache_load(fio_mustache_load_args_s settings); #define fio_mustache_load(...) \ fio_mustache_load((fio_mustache_load_args_s){__VA_ARGS__}) ``` Loads and parses a template from `settings.data` or `settings.filename`. If `filename` is provided and `data` is empty, the loader callback is used. If both are provided, `data` is parsed and `filename` is kept for partial path resolution. **Returns:** a parsed template object, or `NULL` on error. #### `fio_mustache_free` ```c SFUNC void fio_mustache_free(fio_mustache_s *m); ``` Frees a parsed template. Accepts `NULL`. #### `fio_mustache_dup` ```c SFUNC fio_mustache_s *fio_mustache_dup(fio_mustache_s *m); ``` Copies a parsed template using `fio_bstr_copy`. **Returns:** the copied template, or `NULL` if `m == NULL`. ### Rendering #### `fio_mustache_build` ```c SFUNC void *fio_mustache_build(fio_mustache_s *m, fio_mustache_bargs_s); #define fio_mustache_build(m, ...) \ fio_mustache_build((m), ((fio_mustache_bargs_s){__VA_ARGS__})) ``` Renders `m` with the supplied context and callbacks. **Returns:** the final `udata` value. If `m == NULL`, returns `args.udata` unchanged. During rendering: - escaped variables call `write_text_escaped` - raw variables call `write_text` - sections render once for truthy non-arrays, or once per array item - inverted sections render when `var_is_truthful` returns zero - values returned from lookup / array callbacks are released with `release_var` ### Partials and Files Partials are loaded by name through `load_file_data`. The implementation tries each name with these suffixes: 1. `.mustache` 2. `.html` 3. no suffix Relative partials are searched against the including template's path chain, then as provided. With `FIO_MUSTACHE_SECURE_PATH`, `../` partials are skipped. ### YAML Front Matter If a template begins with `---` followed by a line break, the parser scans until a closing `---` line and calls: ```c void (*on_yaml_front_matter)(fio_buf_info_s yaml_front_matter, void *udata); ``` The front matter bytes are not rendered. The engine does not parse YAML for you. It has enough hobbies. ### Thread-Safety Parsed templates are immutable during `fio_mustache_build`, so separate builds can use the same template as long as your callbacks and contexts are safe. Loading and freeing are caller-owned operations. ### Example ```c #define FIO_MUSTACHE #include "fio-stl.h" #include #include typedef struct { const char *name; } data_s; static void *get_var(void *ctx, fio_buf_info_s name) { data_s *d = (data_s *)ctx; if (name.len == 4 && !memcmp(name.buf, "name", 4)) return (void *)d->name; return NULL; } static fio_buf_info_s var2str(void *var) { char *s = (char *)var; return FIO_BUF_INFO2(s, strlen(s)); } int main(void) { char template_text[] = "Hello, {{name}}!\n"; data_s data = {.name = "World"}; fio_mustache_s *m = fio_mustache_load( .data = FIO_BUF_INFO1(template_text)); if (!m) return 1; char *out = fio_mustache_build(m, .ctx = &data, .get_var = get_var, .var2str = var2str); if (out) { fwrite(out, 1, fio_bstr_len(out), stdout); fio_bstr_free(out); } fio_mustache_free(m); return 0; } ``` --- # Crypto Core ```c #define FIO_CRYPTO_CORE #include "fio-stl.h" ``` The crypto slice is a small, zero-dependency toolbox for code that needs to hash, sign, encrypt, parse certificates, or derive keys without dragging in a larger library. Handy? yes. Magic security dust? no. `FIO_CRYPTO_CORE` itself is the shared glue. It defines the AEAD function pointer types used by the symmetric ciphers and by higher-level helpers that accept “any AEAD with this call shape”. Most users include a specific module, or define `FIO_CRYPTO` to pull in the crypto set. ## Map of the Crypto Modules | Class | Docs | What lives there | | --- | --- | --- | | Hash | [SHA-1](https://facil.io/edge/sha-1/), [SHA-2](https://facil.io/edge/sha-1/), [SHA-3 / SHAKE](https://facil.io/edge/sha-1/), [BLAKE2](https://facil.io/edge/blake2/) | Digest functions, streaming hash contexts, SHA/BLAKE HMAC helpers where provided. SHA-1 is legacy-only glue. | | Symmetric | [ChaCha20-Poly1305](https://facil.io/edge/chacha20-poly1305/), [AES-GCM](https://facil.io/edge/aes-gcm/) | AEAD encryption, stream cipher helpers, and the shared in-place authenticated encryption shape. | | Asymmetric | [Ed25519 & X25519](https://facil.io/edge/ed25519-x25519/), [P-256](https://facil.io/edge/p-256-secp256r1/), [P-384](https://facil.io/edge/p-256-secp256r1/), [RSA](https://facil.io/edge/rsa-signatures/) | Signatures, key exchange, ECIES-style X25519 encryption, and RSA signatures for TLS-style use cases. | | PKI | [ASN.1 DER](https://facil.io/edge/asn-1-der/), [X.509](https://facil.io/edge/http-1-x-parser/), [PEM](https://facil.io/edge/p-256-secp256r1/) | DER/PEM parsing, certificate fields, hostname checks, signature checks, and certificate chain validation helpers. | | KDF | [HKDF](https://facil.io/edge/hkdf/), [Argon2](https://facil.io/edge/argon2/), [Lyra2](https://facil.io/edge/lyra2/), [OTP](https://facil.io/edge/otp-totp/), [Secrets](https://facil.io/edge/secrets/) | Key derivation, password hashing, TOTP codes, and hashing a process secret into a stable internal value. | | Post-Quantum | [ML-KEM-768](https://facil.io/edge/ml-entity-decoding/) | ML-KEM-768 key encapsulation and the X25519MLKEM768 hybrid key exchange shape used by TLS drafts. | ## The Shared AEAD Shape The core header defines two function pointer types: ```c typedef void(fio_crypto_enc_fn)(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); typedef int(fio_crypto_dec_fn)(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Current AEAD implementations that match this shape include: - `fio_chacha20_poly1305_enc` / `fio_chacha20_poly1305_dec` - `fio_xchacha20_poly1305_enc` / `fio_xchacha20_poly1305_dec` - `fio_aes128_gcm_enc` / `fio_aes128_gcm_dec` - `fio_aes256_gcm_enc` / `fio_aes256_gcm_dec` These functions encrypt or decrypt `data` in place. `ad` is authenticated but not encrypted. `mac` is the authentication tag buffer; the current AEAD modules use 16-byte tags. Key and nonce sizes belong to the selected cipher, not to the function pointer type. Decryption returns `0` when authentication succeeds and `-1` when it fails. Treat any failure as “message not trusted”; do not parse, log in detail, or partially use the plaintext. ## Safe Use, Plainly This module helps when you need portable crypto building blocks inside the STL: hashing buffers, deriving session keys, signing messages, checking certificates, or doing AEAD encryption with a known protocol design. It does **not** promise that your protocol is safe. It does not manage long-term keys for you, pick nonces, rotate secrets, maintain a root trust store, protect keys in hardware, provide FIPS validation, or substitute for an external security review. Several public-key and PKI headers explicitly note that they have not been independently audited. Use this toolbox when: - you need the zero-dependency facil.io STL path; - the protocol is already designed and reviewed; - inputs, key sizes, nonce rules, and failure paths are controlled; - a compact embedded copy is more important than delegating to a platform stack. Prefer audited platform/security libraries such as OpenSSL, BoringSSL, libsodium, CommonCrypto, platform TLS, or OS key stores when: - you are building TLS, PKI, payment, identity, or compliance-sensitive systems; - private keys live for a long time or leave the process boundary; - certificate trust decisions must track OS/browser policy; - side-channel hardening, hardware acceleration policy, or formal validation matters. In short: this is a sharp little knife. Useful. Keep fingers clear. ## Implementation Notes The crypto headers use optional CPU-specific paths where available, including SHA intrinsics, AES-NI/PCLMULQDQ, ARM crypto extensions, NEON, and AVX2. The public APIs stay the same when the implementation falls back to portable C. The SIMD notes in `150 crypto core.h` are documentation for maintainers: ChaCha20-Poly1305 can process blocks in parallel, Curve25519 uses vectorized field add/sub/cswap where useful, and ML-KEM vectorizes NTT/reduction paths. These are implementation details, not extra guarantees. # BLAKE2 ```c #define FIO_BLAKE2 #include "fio-stl.h" ``` BLAKE2b (64-bit, up to 64-byte digest) and BLAKE2s (32-bit, up to 32-byte digest). Both are fast, modern cryptographic hashes with built-in keyed hashing, standard HMAC wrappers, and a streaming init/consume/finalize API. ### Types #### `fio_blake2b_s` ```c typedef struct { uint64_t h[8]; /* state */ uint64_t t[2]; /* total bytes processed (128-bit counter) */ uint64_t f[2]; /* finalization flags */ uint8_t buf[128]; /* input buffer */ size_t buflen; /* bytes in buffer */ size_t outlen; /* digest length */ } fio_blake2b_s; ``` Streaming BLAKE2b context. Treat it as opaque and initialize with `fio_blake2b_init`. #### `fio_blake2s_s` ```c typedef struct { uint32_t h[8]; /* state */ uint32_t t[2]; /* total bytes processed (64-bit counter) */ uint32_t f[2]; /* finalization flags */ uint8_t buf[64]; /* input buffer */ size_t buflen; /* bytes in buffer */ size_t outlen; /* digest length */ } fio_blake2s_s; ``` Streaming BLAKE2s context. Treat it as opaque and initialize with `fio_blake2s_init`. ### BLAKE2b Functions #### `fio_blake2b` ```c FIO_IFUNC fio_u512 fio_blake2b(const void *data, uint64_t len); ``` One-shot BLAKE2b with the full 64-byte digest. **Parameters:** - `data` — bytes to hash. - `len` — number of bytes. **Returns:** the 64-byte digest as `fio_u512`. **Note:** signature matches `fio_sha512`, so the two are interchangeable as `fio_u512 (*)(const void *, uint64_t)`. #### `fio_blake2b_hash` ```c SFUNC void fio_blake2b_hash(void *restrict out, size_t outlen, const void *restrict data, size_t len, const void *restrict key, size_t keylen); ``` Flexible-output BLAKE2b with optional keyed hashing. **Parameters:** - `out` — destination buffer (at least `outlen` bytes). - `outlen` — digest length (1–64; defaults to 64 if 0). - `data` — bytes to hash. - `len` — data length. - `key` — optional secret key (NULL for unkeyed). - `keylen` — key length (0–64; clamped to 64 if larger). **Note:** keyed BLAKE2b is a built-in MAC mode; for RFC-2104 HMAC, use `fio_blake2b_hmac`. #### `fio_blake2b_hmac` ```c SFUNC fio_u512 fio_blake2b_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Standard HMAC-BLAKE2b with a 64-byte digest. **Parameters:** - `key` — secret key. - `key_len` — key length. - `msg` — message to authenticate. - `msg_len` — message length. **Returns:** 64-byte authentication code as `fio_u512`. #### `fio_blake2b_init` ```c SFUNC fio_blake2b_s fio_blake2b_init(size_t outlen, const void *key, size_t keylen); ``` Initializes a BLAKE2b streaming context. **Parameters:** - `outlen` — desired digest length (1–64; defaults to 64 if 0). - `key` — optional secret key (NULL for unkeyed). - `keylen` — key length (0–64; clamped to 64 if larger). #### `fio_blake2b_consume` ```c SFUNC void fio_blake2b_consume(fio_blake2b_s *restrict h, const void *restrict data, size_t len); ``` Feeds more data into a BLAKE2b streaming context. May be called repeatedly. #### `fio_blake2b_finalize` ```c SFUNC fio_u512 fio_blake2b_finalize(fio_blake2b_s *h); ``` Finalizes the stream and returns the digest. **Returns:** digest as `fio_u512`. Only the first `outlen` bytes are valid. ### BLAKE2s Functions #### `fio_blake2s` ```c FIO_IFUNC fio_u256 fio_blake2s(const void *data, uint64_t len); ``` One-shot BLAKE2s with the full 32-byte digest. #### `fio_blake2s_hash` ```c SFUNC void fio_blake2s_hash(void *restrict out, size_t outlen, const void *restrict data, size_t len, const void *restrict key, size_t keylen); ``` Flexible-output BLAKE2s with optional keyed hashing. `outlen` is 1–32 (defaults to 32 if 0). #### `fio_blake2s_hmac` ```c SFUNC fio_u256 fio_blake2s_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Standard HMAC-BLAKE2s with a 32-byte digest. #### `fio_blake2s_init` ```c SFUNC fio_blake2s_s fio_blake2s_init(size_t outlen, const void *key, size_t keylen); ``` Initializes a BLAKE2s streaming context. `outlen` is 1–32 (defaults to 32 if 0). #### `fio_blake2s_consume` ```c SFUNC void fio_blake2s_consume(fio_blake2s_s *restrict h, const void *restrict data, size_t len); ``` Feeds more data into a BLAKE2s streaming context. #### `fio_blake2s_finalize` ```c SFUNC fio_u256 fio_blake2s_finalize(fio_blake2s_s *h); ``` Finalizes the stream and returns the digest. ### Examples ```c #define FIO_BLAKE2 #include "fio-stl.h" void examples(void) { const char *msg = "Hello, BLAKE2!"; /* One-shot */ fio_u512 b2b = fio_blake2b(msg, strlen(msg)); fio_u256 b2s = fio_blake2s(msg, strlen(msg)); /* Flexible output with key */ uint8_t digest[16]; fio_blake2b_hash(digest, 16, msg, strlen(msg), "key", 3); /* Streaming */ fio_blake2b_s ctx = fio_blake2b_init(64, NULL, 0); fio_blake2b_consume(&ctx, "Hello, ", 7); fio_blake2b_consume(&ctx, "BLAKE2!", 7); fio_u512 result = fio_blake2b_finalize(&ctx); /* HMAC */ fio_u512 mac = fio_blake2b_hmac("key", 3, msg, strlen(msg)); } ``` ### BLAKE2b vs BLAKE2s | Property | BLAKE2b | BLAKE2s | |---|---|---| | Word size | 64-bit | 32-bit | | Block size | 128 bytes | 64 bytes | | Max digest | 64 bytes | 32 bytes | | Max key | 64 bytes | 32 bytes | | Rounds | 12 | 10 | | Result type | `fio_u512` | `fio_u256` | ------------------------------------------------------------ # ChaCha20 & Poly1305 ```c #define FIO_CHACHA #include "fio-stl.h" ``` ChaCha20 stream cipher, Poly1305 authenticator, and the ChaCha20-Poly1305 AEAD combination. Also includes XChaCha20-Poly1305 with a 192-bit nonce, which is safer for random nonces than the 96-bit variant. **Security note:** this implementation has not been independently audited. Use at your own risk, and prefer a tested cryptographic library when one is available. ### ChaCha20-Poly1305 API #### `fio_chacha20_poly1305_enc` ```c SFUNC void fio_chacha20_poly1305_enc(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` In-place encryption with a 16-byte Poly1305 tag. **Parameters:** - `mac` — output buffer; must have at least 16 writable bytes. - `data` — plaintext buffer; encrypted in place. - `len` — data length. - `ad` — additional authenticated data (not encrypted); may be `NULL`. - `adlen` — length of `ad`. - `key` — 32-byte key. - `nonce` — 12-byte nonce. #### `fio_chacha20_poly1305_dec` ```c SFUNC int fio_chacha20_poly1305_dec(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` In-place decryption with tag verification. **Returns:** `0` on success, `-1` if authentication fails. On failure `data` is zeroed. #### `fio_chacha20_poly1305_auth` ```c SFUNC void fio_chacha20_poly1305_auth(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Computes the Poly1305 tag for already-encrypted data without decrypting it. ### Standalone ChaCha20 / Poly1305 #### `fio_chacha20` ```c SFUNC void fio_chacha20(void *restrict data, size_t len, const void *key, const void *nonce, uint32_t counter); ``` In-place ChaCha20 encryption or decryption. **Parameters:** - `data` — buffer to transform in place. - `len` — buffer length. - `key` — 32-byte key. - `nonce` — 12-byte nonce. - `counter` — block counter; usually `1` unless resuming mid-ciphertext. #### `fio_poly1305_auth` ```c SFUNC void fio_poly1305_auth(void *restrict mac_dest, void *restrict message, size_t len, const void *ad, size_t ad_len, const void *key256bits); ``` Computes a Poly1305 MAC for `message` and `ad` using a 32-byte key. **Parameters:** - `mac_dest` — output buffer; must have at least 16 writable bytes. - `message` — message to authenticate; may be `NULL` if `len` is 0. - `len` — message length. - `ad` — additional data; may be `NULL`. - `ad_len` — additional data length. - `key256bits` — 32-byte Poly1305 key. ### XChaCha20-Poly1305 API XChaCha20 uses a 24-byte nonce, making random nonces safe from birthday-bound collisions. #### `fio_xchacha20_poly1305_enc` ```c SFUNC void fio_xchacha20_poly1305_enc(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Same shape as `fio_chacha20_poly1305_enc`, but `nonce` must be 24 bytes. #### `fio_xchacha20_poly1305_dec` ```c SFUNC int fio_xchacha20_poly1305_dec(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Same shape as `fio_chacha20_poly1305_dec`, but `nonce` must be 24 bytes. #### `fio_xchacha20` ```c SFUNC void fio_xchacha20(void *restrict data, size_t len, const void *key, const void *nonce, uint32_t counter); ``` In-place XChaCha20 encryption or decryption. ### Example ```c #define FIO_CHACHA #include "fio-stl.h" int main(void) { uint8_t key[32] = {0}; uint8_t nonce[12] = {0}; uint8_t msg[32] = "hello, chacha world!"; uint8_t tag[16]; fio_rand_bytes_secure(key, sizeof(key)); fio_rand_bytes_secure(nonce, sizeof(nonce)); fio_chacha20_poly1305_enc(tag, msg, sizeof(msg), NULL, 0, key, nonce); if (fio_chacha20_poly1305_dec(tag, msg, sizeof(msg), NULL, 0, key, nonce)) { printf("authentication failed\n"); return 1; } return 0; } ``` ------------------------------------------------------------ # HKDF ```c #define FIO_HKDF #include "fio-stl.h" ``` HMAC-based Key Derivation Function per [RFC 5869](https://www.rfc-editor.org/rfc/rfc5869). HKDF extracts entropy from input keying material into a pseudorandom key, then expands that key into one or more output keys. Supports SHA-256 and SHA-384 as the underlying HMAC. **Note:** HKDF needs `fio_sha256_hmac` and `fio_sha512_hmac`. Define `FIO_SHA2` before `FIO_HKDF`, or use `FIO_CRYPTO` to pull in everything. ### Constants #### `FIO_HKDF_SHA256_HASH_LEN` ```c #define FIO_HKDF_SHA256_HASH_LEN 32 ``` SHA-256 hash length in bytes. #### `FIO_HKDF_SHA384_HASH_LEN` ```c #define FIO_HKDF_SHA384_HASH_LEN 48 ``` SHA-384 hash length in bytes. ### Functions #### `fio_hkdf_extract` ```c SFUNC void fio_hkdf_extract(void *restrict prk, const void *restrict salt, size_t salt_len, const void *restrict ikm, size_t ikm_len, int use_sha384); ``` HKDF-Extract: `PRK = HMAC-Hash(salt, IKM)`. **Parameters:** - `prk` — output buffer (32 bytes for SHA-256, 48 for SHA-384). - `salt` — optional salt; if `NULL`, a zero string of hash length is used. - `salt_len` — salt length. - `ikm` — input keying material. - `ikm_len` — IKM length. - `use_sha384` — non-zero for SHA-384, zero for SHA-256. #### `fio_hkdf_expand` ```c SFUNC void fio_hkdf_expand(void *restrict okm, size_t okm_len, const void *restrict prk, size_t prk_len, const void *restrict info, size_t info_len, int use_sha384); ``` HKDF-Expand: `OKM = HKDF-Expand(PRK, info, L)`. **Parameters:** - `okm` — output buffer. - `okm_len` — desired output length (max `255 * hash_len`). - `prk` — pseudorandom key from Extract. - `prk_len` — 32 for SHA-256, 48 for SHA-384. - `info` — optional context/info string; may be `NULL`. - `info_len` — info length. - `use_sha384` — non-zero for SHA-384, zero for SHA-256. #### `fio_hkdf` ```c SFUNC void fio_hkdf(void *restrict okm, size_t okm_len, const void *restrict salt, size_t salt_len, const void *restrict ikm, size_t ikm_len, const void *restrict info, size_t info_len, int use_sha384); ``` Combined HKDF: Extract followed by Expand in one call. ### Example ```c #define FIO_SHA2 #define FIO_HKDF #include "fio-stl.h" int main(void) { const char *ikm = "input keying material"; const char *salt = "salt"; const char *info = "app context"; uint8_t key[32]; fio_hkdf(key, sizeof(key), salt, strlen(salt), ikm, strlen(ikm), info, strlen(info), 0); return 0; } ``` ------------------------------------------------------------ # SHA-1 ```c #define FIO_SHA1 #include "fio-stl.h" ``` SHA-1 hashing and HMAC. It is cryptographically broken, so do not use it for anything that needs security. Some protocols (hello, WebSockets) still ask for it, so it lives here for compatibility. ### Types #### `fio_sha1_s` ```c typedef union { #ifdef __SIZEOF_INT128__ __uint128_t align__; #else uint64_t align__; #endif uint32_t v[5]; uint8_t digest[20]; } fio_sha1_s; ``` Container for the 20-byte SHA-1 digest. **Members:** - `align__` - alignment padding. - `v` - the 5 × 32-bit state words. - `digest` - the 20-byte digest as raw bytes. ### API Functions #### `fio_sha1` ```c fio_sha1_s fio_sha1(const void *data, uint64_t len); ``` One-shot SHA-1 hash of `len` bytes at `data`. **Parameters:** - `data` - pointer to the data to hash. - `len` - length of the data in bytes. **Returns:** a `fio_sha1_s` containing the 20-byte digest. #### `fio_sha1_hmac` ```c fio_sha1_s fio_sha1_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Computes HMAC-SHA1, producing a 20-byte authentication code. **Parameters:** - `key` - pointer to the secret key. - `key_len` - length of the key in bytes. - `msg` - pointer to the message to authenticate. - `msg_len` - length of the message in bytes. **Returns:** a `fio_sha1_s` containing the 20-byte HMAC. **Note:** keys longer than 64 bytes are first hashed with SHA-1. #### `fio_sha1_len` ```c size_t fio_sha1_len(void); ``` **Returns:** the SHA-1 digest length in bytes (20). #### `fio_sha1_digest` ```c uint8_t *fio_sha1_digest(fio_sha1_s *s); ``` **Parameters:** - `s` - pointer to a `fio_sha1_s` result. **Returns:** a pointer to the 20-byte digest inside `s`. ### Examples #### One-shot hash ```c #define FIO_SHA1 #include "fio-stl.h" #include #include int main(void) { const char *msg = "hello"; fio_sha1_s r = fio_sha1(msg, strlen(msg)); for (size_t i = 0; i < fio_sha1_len(); ++i) printf("%02x", fio_sha1_digest(&r)[i]); printf("\n"); return 0; } ``` #### HMAC ```c fio_sha1_s mac = fio_sha1_hmac("secret", 6, "hello", 5); ``` **Note:** SHA-1 is broken. Prefer SHA-256, SHA-3, or another modern hash for security-sensitive work. ------------------------------------------------------------ # SHA-2 ```c #define FIO_SHA2 #include "fio-stl.h" ``` SHA-256 and SHA-512 hashing, with both one-shot and streaming APIs plus HMAC. These are the workhorse hashes for checksums, signatures, and anything that needs a collision-resistant digest. ### Types #### `fio_sha256_s` ```c typedef struct { fio_u256 hash; fio_u512 cache; uint64_t total_len; } fio_sha256_s; ``` Streaming SHA-256 state. Initialize with `fio_sha256_init`, feed with `fio_sha256_consume`, and finish with `fio_sha256_finalize`. #### `fio_sha512_s` ```c typedef struct { fio_u512 hash; fio_u1024 cache; uint64_t total_len; } fio_sha512_s; ``` Streaming SHA-512 state. Initialize with `fio_sha512_init`, feed with `fio_sha512_consume`, and finish with `fio_sha512_finalize`. ### SHA-256 Functions #### `fio_sha256` ```c fio_u256 fio_sha256(const void *data, uint64_t len); ``` One-shot SHA-256 of `len` bytes at `data`. **Parameters:** - `data` - pointer to the data to hash. - `len` - length of the data in bytes. **Returns:** a `fio_u256` containing the 32-byte digest. #### `fio_sha256_init` ```c fio_sha256_s fio_sha256_init(void); ``` **Returns:** a freshly initialized SHA-256 streaming state. #### `fio_sha256_consume` ```c void fio_sha256_consume(fio_sha256_s *h, const void *data, uint64_t len); ``` Feeds more data into a streaming SHA-256 hash. May be called repeatedly. **Parameters:** - `h` - pointer to the streaming state. - `data` - pointer to the data to add. - `len` - length of the data in bytes. #### `fio_sha256_finalize` ```c fio_u256 fio_sha256_finalize(fio_sha256_s *h); ``` Finalizes the hash and returns the 32-byte digest. **Parameters:** - `h` - pointer to the streaming state. **Returns:** a `fio_u256` containing the digest. **Note:** after finalization, `h` should not be reused without re-initialization. ### SHA-512 Functions #### `fio_sha512` ```c fio_u512 fio_sha512(const void *data, uint64_t len); ``` One-shot SHA-512 of `len` bytes at `data`. **Parameters:** - `data` - pointer to the data to hash. - `len` - length of the data in bytes. **Returns:** a `fio_u512` containing the 64-byte digest. #### `fio_sha512_init` ```c fio_sha512_s fio_sha512_init(void); ``` **Returns:** a freshly initialized SHA-512 streaming state. #### `fio_sha512_consume` ```c void fio_sha512_consume(fio_sha512_s *h, const void *data, uint64_t len); ``` Feeds more data into a streaming SHA-512 hash. May be called repeatedly. **Parameters:** - `h` - pointer to the streaming state. - `data` - pointer to the data to add. - `len` - length of the data in bytes. #### `fio_sha512_finalize` ```c fio_u512 fio_sha512_finalize(fio_sha512_s *h); ``` Finalizes the hash and returns the 64-byte digest. **Parameters:** - `h` - pointer to the streaming state. **Returns:** a `fio_u512` containing the digest. **Note:** after finalization, `h` should not be reused without re-initialization. ### SHA-384 Functions SHA-384 is SHA-512 with different initial values, truncated to 48 bytes (truncating a SHA-512 *result* is NOT the same as SHA-384). #### `fio_sha384` ```c fio_u512 fio_sha384(const void *data, uint64_t len); ``` Computes SHA-384 in a single call. **Returns:** a `fio_u512` whose first 48 bytes hold the digest. #### `fio_sha384_init` / `fio_sha384_consume` / `fio_sha384_finalize` ```c fio_sha512_s fio_sha384_init(void); void fio_sha384_consume(fio_sha512_s *h, const void *data, uint64_t len); fio_u512 fio_sha384_finalize(fio_sha512_s *h); ``` Streaming SHA-384: same state type as SHA-512; `fio_sha384_init` seeds the SHA-384 initial values, consume/finalize alias the SHA-512 operations. ### HMAC Functions #### `fio_sha256_hmac` ```c fio_u256 fio_sha256_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Computes HMAC-SHA256, producing a 32-byte authentication code. **Parameters:** - `key` - pointer to the secret key. - `key_len` - length of the key in bytes. - `msg` - pointer to the message to authenticate. - `msg_len` - length of the message in bytes. **Returns:** a `fio_u256` containing the 32-byte HMAC. **Note:** keys longer than 64 bytes are hashed first. #### `fio_sha512_hmac` ```c fio_u512 fio_sha512_hmac(const void *key, uint64_t key_len, const void *msg, uint64_t msg_len); ``` Computes HMAC-SHA512, producing a 64-byte authentication code. **Parameters:** - `key` - pointer to the secret key. - `key_len` - length of the key in bytes. - `msg` - pointer to the message to authenticate. - `msg_len` - length of the message in bytes. **Returns:** a `fio_u512` containing the 64-byte HMAC. **Note:** keys longer than 128 bytes are hashed first. ### Examples #### One-shot SHA-256 ```c #define FIO_SHA2 #include "fio-stl.h" #include #include int main(void) { const char *msg = "hello"; fio_u256 r = fio_sha256(msg, strlen(msg)); for (size_t i = 0; i < sizeof(r.u8); ++i) printf("%02x", r.u8[i]); printf("\n"); return 0; } ``` #### Streaming SHA-256 ```c fio_sha256_s h = fio_sha256_init(); fio_sha256_consume(&h, "hello, ", 7); fio_sha256_consume(&h, "world", 5); fio_u256 r = fio_sha256_finalize(&h); ``` #### HMAC-SHA256 ```c fio_u256 mac = fio_sha256_hmac("secret", 6, "hello", 5); ``` **Note:** all state lives in caller-owned variables. The functions do not allocate heap memory and are thread-safe as long as each thread uses its own state. ------------------------------------------------------------ # SHA-3 ```c #define FIO_SHA3 #include "fio-stl.h" ``` The SHA-3 family (SHA3-224/256/384/512) and the SHAKE128/SHAKE256 extendable-output functions, built on Keccak-f[1600]. All variants share one streaming context type. ### Types #### `fio_sha3_s` ```c typedef struct { uint64_t state[25]; uint8_t buf[200]; size_t buflen; size_t rate; size_t outlen; uint8_t delim; } fio_sha3_s; ``` Streaming context for every SHA-3 and SHAKE variant. **Members:** - `state` - the 25-word Keccak-f[1600] state. - `buf` - partial-block input buffer. - `buflen` - bytes currently buffered. - `rate` - absorption/squeezing rate in bytes, set by the init function. - `outlen` - fixed output length in bytes for SHA-3; 0 for SHAKE. - `delim` - domain separator (`0x06` for SHA-3, `0x1F` for SHAKE). **Note:** treat this as opaque. Always initialize it with one of the `fio_sha3_*_init` or `fio_shake*_init` functions. ### SHA-3 Fixed-Output Functions #### `fio_sha3_224_init` ```c fio_sha3_s fio_sha3_224_init(void); ``` **Returns:** a SHA3-224 context (28-byte output, rate 144). #### `fio_sha3_256_init` ```c fio_sha3_s fio_sha3_256_init(void); ``` **Returns:** a SHA3-256 context (32-byte output, rate 136). #### `fio_sha3_384_init` ```c fio_sha3_s fio_sha3_384_init(void); ``` **Returns:** a SHA3-384 context (48-byte output, rate 104). #### `fio_sha3_512_init` ```c fio_sha3_s fio_sha3_512_init(void); ``` **Returns:** a SHA3-512 context (64-byte output, rate 72). #### `fio_sha3_consume` ```c void fio_sha3_consume(fio_sha3_s *restrict h, const void *restrict data, size_t len); ``` Feeds `len` bytes into a SHA-3 context. May be called repeatedly. **Parameters:** - `h` - pointer to the streaming context. - `data` - pointer to the data to absorb. - `len` - length of the data in bytes. #### `fio_sha3_finalize` ```c void fio_sha3_finalize(fio_sha3_s *restrict h, void *restrict out); ``` Pads, permutes, and writes `h->outlen` digest bytes to `out`. **Parameters:** - `h` - pointer to the streaming context. - `out` - output buffer, must hold at least `h->outlen` bytes. **Note:** do not call this on a SHAKE context. Use `fio_shake_squeeze` instead. #### `fio_sha3_224` ```c void fio_sha3_224(void *restrict out, const void *restrict data, size_t len); ``` One-shot SHA3-224. **Parameters:** - `out` - output buffer (28 bytes). - `data` - pointer to the data to hash. - `len` - length of the data in bytes. #### `fio_sha3_256` ```c void fio_sha3_256(void *restrict out, const void *restrict data, size_t len); ``` One-shot SHA3-256. **Parameters:** - `out` - output buffer (32 bytes). - `data` - pointer to the data to hash. - `len` - length of the data in bytes. #### `fio_sha3_384` ```c void fio_sha3_384(void *restrict out, const void *restrict data, size_t len); ``` One-shot SHA3-384. **Parameters:** - `out` - output buffer (48 bytes). - `data` - pointer to the data to hash. - `len` - length of the data in bytes. #### `fio_sha3_512` ```c void fio_sha3_512(void *restrict out, const void *restrict data, size_t len); ``` One-shot SHA3-512. **Parameters:** - `out` - output buffer (64 bytes). - `data` - pointer to the data to hash. - `len` - length of the data in bytes. ### SHAKE Extendable-Output Functions #### `fio_shake128_init` ```c fio_sha3_s fio_shake128_init(void); ``` **Returns:** a SHAKE128 context (variable output, rate 168). #### `fio_shake256_init` ```c fio_sha3_s fio_shake256_init(void); ``` **Returns:** a SHAKE256 context (variable output, rate 136). #### `fio_shake_consume` ```c #define fio_shake_consume fio_sha3_consume ``` Alias for `fio_sha3_consume`. Feeds data into a SHAKE context. #### `fio_shake_squeeze` ```c void fio_shake_squeeze(fio_sha3_s *restrict h, void *restrict out, size_t outlen); ``` Squeezes `outlen` bytes from a SHAKE context. On the first call it applies padding and runs the first permutation; later calls continue squeezing and re-permute as needed. **Parameters:** - `h` - pointer to the SHAKE context. - `out` - output buffer, must hold at least `outlen` bytes. - `outlen` - number of bytes to squeeze. #### `fio_shake128` ```c void fio_shake128(void *restrict out, size_t outlen, const void *restrict data, size_t len); ``` One-shot SHAKE128. **Parameters:** - `out` - output buffer. - `outlen` - number of bytes to produce. - `data` - pointer to the data to hash. - `len` - length of the data in bytes. #### `fio_shake256` ```c void fio_shake256(void *restrict out, size_t outlen, const void *restrict data, size_t len); ``` One-shot SHAKE256. **Parameters:** - `out` - output buffer. - `outlen` - number of bytes to produce. - `data` - pointer to the data to hash. - `len` - length of the data in bytes. ### Examples #### One-shot SHA3-256 ```c #define FIO_SHA3 #include "fio-stl.h" #include #include int main(void) { const char *msg = "hello"; uint8_t digest[32]; fio_sha3_256(digest, msg, strlen(msg)); for (size_t i = 0; i < sizeof(digest); ++i) printf("%02x", digest[i]); printf("\n"); return 0; } ``` #### Streaming SHA3-512 ```c fio_sha3_s h = fio_sha3_512_init(); fio_sha3_consume(&h, "hello, ", 7); fio_sha3_consume(&h, "world", 5); uint8_t digest[64]; fio_sha3_finalize(&h, digest); ``` #### SHAKE256 extendable output ```c const char *seed = "hello"; uint8_t out[128]; fio_sha3_s h = fio_shake256_init(); fio_shake_consume(&h, seed, strlen(seed)); fio_shake_squeeze(&h, out, sizeof(out)); ``` **Note:** state is caller-owned; no heap allocation occurs. Keep SHA-3 and SHAKE contexts separate at finalization time (`fio_sha3_finalize` vs. `fio_shake_squeeze`). ------------------------------------------------------------ # AES-GCM ```c #define FIO_AES #include "fio-stl.h" ``` Authenticated encryption with associated data (AEAD) using AES-128-GCM and AES-256-GCM. The API is in-place and matches [`fio_chacha20_poly1305`](https://facil.io/edge/chacha20-poly1305/), so the two cipher suites can be swapped with the same call shape. Hardware acceleration is used when available (x86 AES-NI + PCLMULQDQ, ARM crypto extensions), with a portable fallback otherwise. **Security note:** GCM nonces must never be reused with the same key. A 96-bit nonce runs out of uniqueness long before the key does; generate a fresh nonce for every message. ### API Functions #### `fio_aes128_gcm_enc` ```c SFUNC void fio_aes128_gcm_enc(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Encrypts `data` in place with AES-128-GCM and writes a 16-byte authentication tag to `mac`. **Parameters:** - `mac` — output buffer; must have at least 16 writable bytes. - `data` — plaintext buffer; encrypted in place. May be `NULL` if `len` is 0. - `len` — plaintext length in bytes. - `ad` — additional authenticated data; not encrypted but covered by the tag. May be `NULL`. - `adlen` — length of `ad`. - `key` — 16-byte AES-128 key. - `nonce` — 12-byte nonce. #### `fio_aes128_gcm_dec` ```c SFUNC int fio_aes128_gcm_dec(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Decrypts `data` in place and verifies the 16-byte tag in `mac`. **Parameters:** same as `fio_aes128_gcm_enc`. **Returns:** `0` on success, `-1` if authentication fails. On failure `data` is left unchanged. #### `fio_aes256_gcm_enc` ```c SFUNC void fio_aes256_gcm_enc(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Same as `fio_aes128_gcm_enc` but uses a 32-byte AES-256 key. #### `fio_aes256_gcm_dec` ```c SFUNC int fio_aes256_gcm_dec(void *restrict mac, void *restrict data, size_t len, const void *ad, size_t adlen, const void *key, const void *nonce); ``` Same as `fio_aes128_gcm_dec` but uses a 32-byte AES-256 key. ### Example ```c #define FIO_AES #include "fio-stl.h" int main(void) { uint8_t key[32] = {0}; uint8_t nonce[12] = {0}; uint8_t msg[32] = "hello, gcm world!"; uint8_t tag[16]; fio_rand_bytes_secure(key, sizeof(key)); fio_rand_bytes_secure(nonce, sizeof(nonce)); fio_aes256_gcm_enc(tag, msg, sizeof(msg), NULL, 0, key, nonce); /* msg is now ciphertext; tag authenticates it. */ if (fio_aes256_gcm_dec(tag, msg, sizeof(msg), NULL, 0, key, nonce)) { printf("authentication failed\n"); return 1; } /* msg is restored plaintext. */ return 0; } ``` ------------------------------------------------------------ # Ed25519 & X25519 ```c #define FIO_ED25519 #include "fio-stl.h" ``` Elliptic-curve cryptography over Curve25519: Ed25519 for signatures and X25519 for key exchange. Both provide 128-bit security with compact 32-byte keys. **Security note:** this implementation has not been independently audited. Use at your own risk, and prefer a tested cryptographic library when available. ### Ed25519 Digital Signatures - Secret key: 32 bytes - Public key: 32 bytes - Signature: 64 bytes #### `fio_ed25519_keypair` ```c SFUNC void fio_ed25519_keypair(uint8_t secret_key[32], uint8_t public_key[32]); ``` Generates a new random Ed25519 key pair. #### `fio_ed25519_public_key` ```c SFUNC void fio_ed25519_public_key(uint8_t public_key[32], const uint8_t secret_key[32]); ``` Derives the public key from a secret key. #### `fio_ed25519_sign` ```c SFUNC int fio_ed25519_sign(uint8_t signature[64], const void *message, size_t len, const uint8_t secret_key[32], const uint8_t public_key[32]); ``` Signs a message. Returns `0` on success, `-1` on failure. The signature is deterministic. #### `fio_ed25519_verify` ```c SFUNC int fio_ed25519_verify(const uint8_t signature[64], const void *message, size_t len, const uint8_t public_key[32]); ``` Verifies a signature. Returns `0` on success, `-1` on failure. ### X25519 Key Exchange - Secret key: 32 bytes - Public key: 32 bytes - Shared secret: 32 bytes #### `fio_x25519_keypair` ```c SFUNC void fio_x25519_keypair(uint8_t secret_key[32], uint8_t public_key[32]); ``` Generates a new random X25519 key pair. #### `fio_x25519_public_key` ```c SFUNC void fio_x25519_public_key(uint8_t public_key[32], const uint8_t secret_key[32]); ``` Derives the public key from a secret key. #### `fio_x25519_shared_secret` ```c SFUNC int fio_x25519_shared_secret(uint8_t shared_secret[32], const uint8_t secret_key[32], const uint8_t their_public_key[32]); ``` Computes a shared secret. Returns `0` on success, `-1` if the result is the all-zero point. **Note:** pass the shared secret through a KDF such as HKDF before using it as an encryption key. ### Key Conversion #### `fio_ed25519_sk_to_x25519` ```c SFUNC void fio_ed25519_sk_to_x25519(uint8_t x_secret_key[32], const uint8_t ed_secret_key[32]); ``` Converts an Ed25519 secret key to an X25519 secret key. #### `fio_ed25519_pk_to_x25519` ```c SFUNC void fio_ed25519_pk_to_x25519(uint8_t x_public_key[32], const uint8_t ed_public_key[32]); ``` Converts an Ed25519 public key to an X25519 public key. ### ECIES Public-Key Encryption #### `FIO_X25519_CIPHERTEXT_LEN` ```c #define FIO_X25519_CIPHERTEXT_LEN(message_len) ((message_len) + 48) ``` Plaintext plus 32-byte ephemeral public key and 16-byte MAC. #### `FIO_X25519_PLAINTEXT_LEN` ```c #define FIO_X25519_PLAINTEXT_LEN(ciphertext_len) \ ((ciphertext_len) > 48 ? ((ciphertext_len)-48) : 0) ``` Reverses `FIO_X25519_CIPHERTEXT_LEN`. Returns `0` for invalid ciphertext. #### `fio_x25519_encrypt` ```c SFUNC int fio_x25519_encrypt(uint8_t *ciphertext, const void *message, size_t message_len, fio_crypto_enc_fn encryption_function, const uint8_t recipient_pk[32]); ``` Encrypts a message to an X25519 public key. Ciphertext format: `ephemeral_pk || mac || encrypted_data`. **Parameters:** - `ciphertext` — output buffer (`message_len + 48` bytes). - `message` — plaintext. - `message_len` — plaintext length. - `encryption_function` — e.g., `fio_chacha20_poly1305_enc`. - `recipient_pk` — recipient's 32-byte X25519 public key. **Returns:** `0` on success, `-1` on failure. #### `fio_x25519_decrypt` ```c SFUNC int fio_x25519_decrypt(uint8_t *plaintext, const uint8_t *ciphertext, size_t ciphertext_len, fio_crypto_dec_fn decryption_function, const uint8_t recipient_sk[32]); ``` Decrypts a message using the recipient's X25519 secret key. **Parameters:** - `plaintext` — output buffer (`ciphertext_len - 48` bytes). - `ciphertext` — ciphertext from `fio_x25519_encrypt`. - `ciphertext_len` — total ciphertext length (must be at least 48). - `decryption_function` — e.g., `fio_chacha20_poly1305_dec`. - `recipient_sk` — recipient's 32-byte X25519 secret key. **Returns:** `0` on success, `-1` on failure. ### Example ```c #define FIO_ED25519 #include "fio-stl.h" int main(void) { uint8_t sk[32], pk[32], sig[64]; fio_ed25519_keypair(sk, pk); const char *msg = "hello, ed25519"; fio_ed25519_sign(sig, msg, strlen(msg), sk, pk); if (fio_ed25519_verify(sig, msg, strlen(msg), pk)) { printf("bad signature\n"); return 1; } return 0; } ``` ------------------------------------------------------------ # P-256 (secp256r1) ```c #define FIO_P256 #include "fio-stl.h" ``` P-256 is the NIST `secp256r1` curve used by TLS and certificate tooling. This module covers two jobs: ECDSA signatures over SHA-256 message hashes, and ECDH shared-secret creation. **Security note:** this implementation has not been independently audited. Use it when the zero-dependency STL path matters; prefer a tested crypto library for long-lived keys, compliance, or high-value trust decisions. ## What It Provides | Use | Function | Notes | | --- | --- | --- | | Sign | `fio_ecdsa_p256_sign` | Creates a DER-encoded ECDSA signature from a 32-byte SHA-256 hash. | | Verify DER signature | `fio_ecdsa_p256_verify` | Accepts `SEQUENCE { r INTEGER, s INTEGER }` and a 65-byte uncompressed public key. | | Verify raw signature | `fio_ecdsa_p256_verify_raw` | Accepts raw 32-byte `r`, `s`, `x`, and `y` values. | | Key pair | `fio_p256_keypair` | Produces a 32-byte secret key and a 65-byte uncompressed public key. | | Shared secret | `fio_p256_shared_secret` | Accepts compressed or uncompressed peer public keys and returns the x-coordinate. | The curve parameters are the standard P-256 parameters from NIST FIPS 186-4: - `p = 2^256 - 2^224 + 2^192 + 2^96 - 1` - `n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551` - curve equation: `y² = x³ - 3x + b (mod p)` ## ECDSA Signatures ### `fio_ecdsa_p256_sign` ```c SFUNC int fio_ecdsa_p256_sign(uint8_t *sig, size_t *sig_len, size_t sig_capacity, const uint8_t msg_hash[32], const uint8_t secret_key[32]); ``` Signs a 32-byte SHA-256 message hash. The function writes a DER-encoded signature to `sig` and stores the actual length in `sig_len`. - `sig_capacity` must be at least `72` bytes. - `secret_key` is a 32-byte big-endian scalar and must be in the valid range `1..n-1`. - The function generates a random ECDSA nonce internally and retries invalid nonces. - Returns `0` on success, `-1` on invalid input or signing failure. It signs the hash you give it; it does not hash the original message. No sneaky kitchen work here. ### `fio_ecdsa_p256_verify` ```c SFUNC int fio_ecdsa_p256_verify(const uint8_t *sig, size_t sig_len, const uint8_t *msg_hash, const uint8_t *pubkey, size_t pubkey_len); ``` Verifies a DER-encoded ECDSA signature against a 32-byte SHA-256 hash. - `sig` is a DER `SEQUENCE { r INTEGER, s INTEGER }`. - `msg_hash` is exactly 32 bytes. - `pubkey` must be an uncompressed P-256 public key: `0x04 || x || y`. - `pubkey_len` must be `65`. - Returns `0` for a valid signature, `-1` for invalid input or a bad signature. ### `fio_ecdsa_p256_verify_raw` ```c SFUNC int fio_ecdsa_p256_verify_raw(const uint8_t r[32], const uint8_t s[32], const uint8_t msg_hash[32], const uint8_t pubkey_x[32], const uint8_t pubkey_y[32]); ``` Verifies a signature when `r` and `s` are already decoded. Public-key coordinates are 32-byte big-endian values. The verifier checks that `r` and `s` are in range, checks the public point is on the P-256 curve, performs the ECDSA verification equation, and returns `0` only when the signature matches. ## ECDH Key Exchange ### `fio_p256_keypair` ```c SFUNC int fio_p256_keypair(uint8_t secret_key[32], uint8_t public_key[65]); ``` Generates a P-256 key pair. - `secret_key` receives a 32-byte scalar. - `public_key` receives `0x04 || x || y` (65 bytes). - Returns `0` on success, `-1` on bad arguments or random generation failure. ### `fio_p256_shared_secret` ```c SFUNC int fio_p256_shared_secret(uint8_t shared_secret[32], const uint8_t secret_key[32], const uint8_t *their_public_key, size_t their_public_key_len); ``` Computes a P-256 ECDH shared secret using your secret key and their public key. - `secret_key` is a 32-byte scalar in the valid range `1..n-1`. - `their_public_key` may be uncompressed (`65` bytes, `0x04 || x || y`) or compressed (`33` bytes, `0x02/0x03 || x`). - The peer point is decompressed when needed and checked against the curve. - `shared_secret` receives the 32-byte x-coordinate of the result. - Returns `0` on success, `-1` on invalid input, invalid point, point at infinity, or all-zero result. Run the shared secret through a KDF such as HKDF before using it as an encryption key. Raw ECDH output is an ingredient, not dinner. ## Example ```c #define FIO_P256 #define FIO_SHA2 #include "fio-stl.h" int main(void) { uint8_t secret_key[32]; uint8_t public_key[65]; if (fio_p256_keypair(secret_key, public_key)) return 1; const char *message = "hello, p-256"; fio_u256 hash = fio_sha256(message, strlen(message)); uint8_t sig[72]; size_t sig_len = 0; if (fio_ecdsa_p256_sign(sig, &sig_len, sizeof(sig), hash.u8, secret_key)) return 1; if (fio_ecdsa_p256_verify(sig, sig_len, hash.u8, public_key, 65)) return 1; return 0; } ``` ------------------------------------------------------------ # P-384 (secp384r1) ```c #define FIO_P384 #include "fio-stl.h" ``` P-384 is the NIST `secp384r1` curve. In this STL, it is a verifier: it checks ECDSA P-384 signatures, mainly for TLS 1.3 certificate chains and CA roots that use P-384. There is no signing or key-generation API here. Just verification. Nice and focused. **Security note:** this implementation has not been independently audited. It is meant for verification of public data; prefer a tested crypto library when certificate trust is central to your application. ## What It Provides | Use | Function | Notes | | --- | --- | --- | | Verify DER signature | `fio_ecdsa_p384_verify` | Accepts `SEQUENCE { r INTEGER, s INTEGER }` and a 97-byte uncompressed public key. | | Verify raw signature | `fio_ecdsa_p384_verify_raw` | Accepts raw 48-byte `r`, `s`, `x`, and `y` values. | Curve parameters from NIST FIPS 186-4: - `p = 2^384 - 2^128 - 2^96 + 2^32 - 1` - `n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973` - curve equation: `y² = x³ - 3x + b (mod p)` - coordinate size: 48 bytes ## Verification API ### `fio_ecdsa_p384_verify` ```c SFUNC int fio_ecdsa_p384_verify(const uint8_t *sig, size_t sig_len, const uint8_t *msg_hash, const uint8_t *pubkey, size_t pubkey_len); ``` Verifies a DER-encoded ECDSA P-384 signature over a 48-byte SHA-384 hash. - `sig` is a DER `SEQUENCE { r INTEGER, s INTEGER }`. - `sig_len` is the signature length in bytes. - `msg_hash` is the SHA-384 hash of the message, exactly 48 bytes. - `pubkey` must be an uncompressed public key: `0x04 || x || y`. - `pubkey_len` must be `97`. - Returns `0` for a valid signature, `-1` for invalid input or a bad signature. The function validates the public-key wrapper, decodes `r` and `s` from DER, then calls `fio_ecdsa_p384_verify_raw`. Compressed public keys are not accepted by this API. ### `fio_ecdsa_p384_verify_raw` ```c SFUNC int fio_ecdsa_p384_verify_raw(const uint8_t r[48], const uint8_t s[48], const uint8_t msg_hash[48], const uint8_t pubkey_x[48], const uint8_t pubkey_y[48]); ``` Verifies an ECDSA P-384 signature when the signature and public key are already decoded. All inputs are fixed-width big-endian values: - `r` and `s`: 48-byte signature scalars. - `msg_hash`: 48-byte SHA-384 hash. - `pubkey_x` and `pubkey_y`: 48-byte public-key coordinates. The verifier checks that `r` and `s` are in range, checks the public point is on the P-384 curve, computes the ECDSA verification equation, and returns `0` only when `r == R.x mod n`. ## Example ```c #define FIO_P384 #include "fio-stl.h" int verify_cert_sig(const uint8_t *sig, size_t sig_len, const uint8_t hash[48], const uint8_t pubkey[97]) { if (fio_ecdsa_p384_verify(sig, sig_len, hash, pubkey, 97)) return -1; return 0; } ``` ## Implementation Notes The implementation uses 6 × 64-bit limbs for field elements, Jacobian coordinates for point work, P-384-specific modular reduction, and a double-scalar multiplication path for ECDSA verification. The scalar multiplication is for public verification data. Do not treat this header as a P-384 signing toolkit; there is no public signing API, and private-key use should go to an audited library. ------------------------------------------------------------ # ASN.1 DER ```c #define FIO_DER #include "fio-stl.h" ``` ASN.1 DER is the small binary grammar hiding inside X.509 certificates, keys, signatures, and TLS messages. This module parses and writes the pieces facil.io needs for that world. The parser is non-allocating. Parsed elements point into the original DER buffer, so keep that buffer alive while you use the results. No buffer, no treasure map. ## Core Types ### `fio_der_element_s` ```c typedef struct { const uint8_t *data; size_t len; uint8_t tag; uint8_t is_constructed; uint8_t tag_class; uint8_t tag_number; } fio_der_element_s; ``` A parsed DER element. `data` points to the content bytes, after the tag and length fields. `len` is the content length. The remaining fields describe the tag byte. ### `fio_der_iterator_s` ```c typedef struct { const uint8_t *pos; const uint8_t *end; } fio_der_iterator_s; ``` Iterator state for walking a `SEQUENCE` or `SET` element. ### Tags and Classes `fio_der_tag_e` defines the common universal tags, including `FIO_DER_INTEGER`, `FIO_DER_BIT_STRING`, `FIO_DER_OID`, `FIO_DER_SEQUENCE`, `FIO_DER_SET`, string types, `FIO_DER_UTC_TIME`, `FIO_DER_GENERALIZED_TIME`, and context wrappers `FIO_DER_CONTEXT_0` through `FIO_DER_CONTEXT_3`. `fio_der_class_e` names the tag classes: universal, application, context-specific, and private. ## OID Values OID constants live in the X.509 module (`FIO_X509_OID_*`, see the X.509 documentation). An OID value is a plain `fio_u128` where bytes 0-14 hold the DER content bytes (zero-padded) and byte 15 holds the content length. The length is folded in because `0x00` is a legal OID content byte (arc 0), so padding alone could not distinguish `{..0B}` from `{..0B 00}` - different OIDs. Oversized OIDs (content > 15 bytes) simply never match, which is safe: AlgorithmIdentifier OIDs are attacker-controlled and RFC 5280 requires exact match. Build a value once per parsed element with `fio___der_oid_value` (internal), then compare it against any number of constants with `fio___der_oid_eq` (internal) - two u64-lane `==` comparisons, no memcmp, no call: ```c fio_u128 oid = fio___der_oid_value(&elem); /* build ONCE per element */ if (fio___der_oid_eq(oid, FIO_X509_OID_COMMON_NAME)) { /* ... */ } ``` ## Parsing ### `fio_der_parse` ```c SFUNC const uint8_t *fio_der_parse(fio_der_element_s *elem, const uint8_t *data, size_t data_len); ``` Parses one DER element. On success, fills `elem` and returns a pointer to the next element. On error, returns `NULL`. It rejects invalid DER lengths, truncated buffers, unsupported high-tag-number encodings, and indefinite lengths. ### `fio_der_element_total_len` ```c FIO_IFUNC size_t fio_der_element_total_len(const fio_der_element_s *elem, const uint8_t *data); ``` Returns the full encoded length of an element: tag + length + content. `data` must be the original pointer used to parse the element. ## Type Parsers ### `fio_der_parse_integer` ```c SFUNC int fio_der_parse_integer(const fio_der_element_s *elem, uint64_t *value); ``` Parses an ASN.1 `INTEGER`. For small integers, pass `value` and receive the 64-bit value. For large integers such as RSA moduli, pass `NULL` and use `elem->data` / `elem->len` directly. Leading zero bytes for positive integers are handled. Returns `0` on success, `-1` on error. ### `fio_der_parse_bit_string` ```c SFUNC int fio_der_parse_bit_string(const fio_der_element_s *elem, const uint8_t **bits, size_t *bit_len, uint8_t *unused_bits); ``` Parses an ASN.1 `BIT STRING`. `bits` points into the element data after the unused-bit count byte. `bit_len` is the byte length of the bit payload. `unused_bits` is the number of unused bits in the last byte. ### `fio_der_parse_oid` ```c SFUNC int fio_der_parse_oid(const fio_der_element_s *elem, char *buf, size_t buf_len); ``` Converts an ASN.1 `OBJECT IDENTIFIER` into dotted text such as `1.2.840.113549.1.1.11`. Returns the number of characters written, excluding the NUL byte, or `-1` on error. ### `fio_der_parse_time` ```c SFUNC int fio_der_parse_time(const fio_der_element_s *elem, int64_t *unix_time); ``` Parses `UTCTime` or `GeneralizedTime` into a Unix timestamp. Times must be UTC (`Z`). Fractional seconds are skipped when present. ### `fio_der_parse_string` ```c FIO_IFUNC const char *fio_der_parse_string(const fio_der_element_s *elem, size_t *len); ``` Returns a pointer to the string bytes for supported ASN.1 string tags, with the byte length in `len`. This does not validate or transcode the text; it simply gives you the payload. ### `fio_der_parse_boolean` ```c FIO_IFUNC int fio_der_parse_boolean(const fio_der_element_s *elem, int *value); ``` Parses a DER boolean and writes `0` or non-zero to `value`. ## Walking Sequences and Sets ### `fio_der_iterator_init` ```c FIO_IFUNC void fio_der_iterator_init(fio_der_iterator_s *it, const fio_der_element_s *sequence); ``` Initializes an iterator over the content bytes of a parsed `SEQUENCE` or `SET`. ### `fio_der_iterator_next` ```c SFUNC int fio_der_iterator_next(fio_der_iterator_s *it, fio_der_element_s *elem); ``` Parses the next child element and advances the iterator. Returns `0` when an element was read, `-1` at end or on parse error. ### `fio_der_iterator_has_next` ```c FIO_IFUNC int fio_der_iterator_has_next(const fio_der_iterator_s *it); ``` Returns `1` when the iterator still has bytes to parse, otherwise `0`. ## Tag Helpers ```c FIO_IFUNC int fio_der_is_tag(const fio_der_element_s *elem, uint8_t tag); FIO_IFUNC int fio_der_is_context_tag(const fio_der_element_s *elem, uint8_t tag_num); FIO_IFUNC uint8_t fio_der_tag_number(const fio_der_element_s *elem); ``` Use these to keep parser code readable: - `fio_der_is_tag` checks a universal tag such as `FIO_DER_INTEGER`. - `fio_der_is_context_tag` checks `[0]`, `[1]`, and friends. - `fio_der_tag_number` returns the decoded tag number. ## Encoding All encoder functions return the number of bytes written or needed. Pass `NULL` as `buf` to calculate the encoded length before writing. ### Basic Encoders ```c SFUNC size_t fio_der_encode_length(uint8_t *buf, size_t len); SFUNC size_t fio_der_encode_integer(uint8_t *buf, const uint8_t *data, size_t data_len); SFUNC size_t fio_der_encode_integer_small(uint8_t *buf, uint64_t value); SFUNC size_t fio_der_encode_null(uint8_t *buf); SFUNC size_t fio_der_encode_boolean(uint8_t *buf, int value); ``` These write DER length fields, positive integers, `NULL`, and booleans. `fio_der_encode_integer` expects big-endian integer bytes and adds the leading zero byte when DER needs one to keep the integer positive. OIDs are encoded with the internal `fio___der_encode_oid(buf, fio_u128 oid)`, which TLV-wraps an OID value (see OID Values above) without any dot-string parsing. ### String and Byte Encoders ```c SFUNC size_t fio_der_encode_utf8_string(uint8_t *buf, const char *str, size_t str_len); SFUNC size_t fio_der_encode_printable_string(uint8_t *buf, const char *str, size_t str_len); SFUNC size_t fio_der_encode_bit_string(uint8_t *buf, const uint8_t *bits, size_t bit_len, uint8_t unused_bits); SFUNC size_t fio_der_encode_octet_string(uint8_t *buf, const uint8_t *data, size_t data_len); ``` These write the tag, DER length, and payload for common primitive values. ### Header Encoders ```c SFUNC size_t fio_der_encode_sequence_header(uint8_t *buf, size_t content_len); SFUNC size_t fio_der_encode_set_header(uint8_t *buf, size_t content_len); SFUNC size_t fio_der_encode_context_header(uint8_t *buf, uint8_t tag_num, size_t content_len, int constructed); ``` These write only the wrapper tag and length. Write the content bytes immediately after the returned header length. ### Time Encoders ```c SFUNC size_t fio_der_encode_utc_time(uint8_t *buf, int64_t unix_time); SFUNC size_t fio_der_encode_generalized_time(uint8_t *buf, int64_t unix_time); ``` These encode Unix timestamps as DER `UTCTime` or `GeneralizedTime`. ## Example ```c #define FIO_DER #include "fio-stl.h" void scan_sequence(const uint8_t *der, size_t der_len) { fio_der_element_s seq; if (!fio_der_parse(&seq, der, der_len)) return; if (!fio_der_is_tag(&seq, FIO_DER_SEQUENCE)) return; fio_der_iterator_s it; fio_der_iterator_init(&it, &seq); fio_der_element_s elem; while (fio_der_iterator_next(&it, &elem) == 0) { if (fio_der_is_tag(&elem, FIO_DER_OID)) { fio_u128 oid = fio___der_oid_value(&elem); /* compare with FIO_X509_OID_* constants via fio___der_oid_eq, or */ char dot[128]; if (fio_der_parse_oid(&elem, dot, sizeof(dot)) > 0) { /* dotted string available for diagnostics */ } } } } ``` ------------------------------------------------------------ # RSA Signatures ```c #define FIO_RSA #include "fio-stl.h" ``` RSA here means signature work for TLS and X.509: verify PKCS#1 v1.5 signatures, verify RSA-PSS signatures, and create RSA-PSS signatures for TLS 1.3 `CertificateVerify`. Supported modulus sizes are 2048, 3072, and 4096 bits. Smaller keys do not get a party hat. **Security note:** this implementation has not been independently audited. Prefer a tested crypto library for high-value private keys, compliance, HSM integration, or anything that keeps auditors awake. ## Constants and Hash IDs ```c #define FIO_RSA_MAX_BITS 4096 #define FIO_RSA_MAX_BYTES (FIO_RSA_MAX_BITS / 8) #define FIO_RSA_MAX_WORDS (FIO_RSA_MAX_BYTES / 8) ``` ```c typedef enum { FIO_RSA_HASH_SHA256 = 0, FIO_RSA_HASH_SHA384 = 1, FIO_RSA_HASH_SHA512 = 2, } fio_rsa_hash_e; ``` `hash_len` must match the selected hash: 32 bytes for SHA-256, 48 for SHA-384, and 64 for SHA-512. ## Key Types ### `fio_rsa_pubkey_s` ```c typedef struct { const uint8_t *n; size_t n_len; const uint8_t *e; size_t e_len; } fio_rsa_pubkey_s; ``` Public key for verification. `n` is the modulus and `e` is the public exponent, both as big-endian byte arrays. This matches the DER layout used in X.509 certificates. ### `fio_rsa_privkey_s` ```c typedef struct { const uint8_t *n; size_t n_len; const uint8_t *d; size_t d_len; const uint8_t *e; size_t e_len; const uint8_t *p; size_t p_len; const uint8_t *q; size_t q_len; const uint8_t *dP; size_t dP_len; const uint8_t *dQ; size_t dQ_len; const uint8_t *qInv; size_t qInv_len; } fio_rsa_privkey_s; ``` Private key for RSA-PSS signing. `n` and `d` are required. The public exponent `e` and CRT fields (`p`, `q`, `dP`, `dQ`, `qInv`) are optional. When CRT parameters are available, signing uses CRT with message blinding. If only `n` and `d` are available, signing falls back to the non-CRT path. ## Verification ### `fio_rsa_verify_pkcs1` ```c SFUNC int fio_rsa_verify_pkcs1(const uint8_t *sig, size_t sig_len, const uint8_t *msg_hash, size_t hash_len, fio_rsa_hash_e hash_alg, const fio_rsa_pubkey_s *key); ``` Verifies an RSA PKCS#1 v1.5 signature with `DigestInfo` encoding. This covers the X.509 signature algorithms commonly named: - `sha256WithRSAEncryption` - `sha384WithRSAEncryption` - `sha512WithRSAEncryption` Parameters: - `sig`: signature bytes; length must match the modulus length. - `sig_len`: signature length in bytes. - `msg_hash`: pre-computed message hash. - `hash_len`: 32, 48, or 64 bytes. - `hash_alg`: matching `FIO_RSA_HASH_*` value. - `key`: RSA public key. Returns `0` for a valid signature, `-1` on invalid input or a bad signature. ### `fio_rsa_verify_pss` ```c SFUNC int fio_rsa_verify_pss(const uint8_t *sig, size_t sig_len, const uint8_t *msg_hash, size_t hash_len, fio_rsa_hash_e hash_alg, const fio_rsa_pubkey_s *key); ``` Verifies an RSA-PSS signature. This is the RSA signature style TLS 1.3 uses for `CertificateVerify`. The implementation uses: - MGF1 with the same hash function; - salt length equal to the hash length; - trailer byte `0xBC`. Returns `0` for a valid signature, `-1` on invalid input or a bad signature. ## Signing ### `fio_rsa_sign_pss` ```c SFUNC int fio_rsa_sign_pss(uint8_t *signature, size_t *sig_len, const uint8_t *msg_hash, size_t hash_len, fio_rsa_hash_e hash_alg, const fio_rsa_privkey_s *key); ``` Creates an RSA-PSS signature using `RSASSA-PSS-SIGN` from RFC 8017, with the TLS 1.3 settings listed above. - `signature` must have room for `key->n_len` bytes. - `sig_len` receives the signature length, which equals `key->n_len` on success. - `msg_hash` is already hashed; this function does not hash the message. - `key` must include `n` and `d`; CRT fields are optional. Returns `0` on success, `-1` on error. PKCS#1 v1.5 signing is not exposed. TLS 1.3 does not want it for `CertificateVerify`, and this header agrees. ## Example: Verify an RSA-PSS Signature ```c #define FIO_RSA #define FIO_SHA2 #include "fio-stl.h" int verify_pss(const uint8_t *sig, size_t sig_len, const uint8_t *modulus, size_t modulus_len, const uint8_t *exponent, size_t exponent_len, const void *message, size_t message_len) { fio_rsa_pubkey_s key = { .n = modulus, .n_len = modulus_len, .e = exponent, .e_len = exponent_len, }; fio_u256 hash = fio_sha256(message, message_len); return fio_rsa_verify_pss(sig, sig_len, hash.u8, 32, FIO_RSA_HASH_SHA256, &key); } ``` ## Practical Notes - The signature length must match the modulus length. - The public exponent is usually `65537` (`0x010001`), but the API accepts DER-style big-endian bytes. - The module works on pre-computed hashes. Pick the hash to match the certificate or protocol signature algorithm. - For X.509 parsing, this module is normally used through [X.509](https://facil.io/edge/http-1-x-parser/), which extracts the RSA key and dispatches verification. ------------------------------------------------------------ # ML-KEM-768 ```c #define FIO_MLKEM #include "fio-stl.h" ``` ML-KEM-768 is the FIPS 203 module-lattice key encapsulation mechanism: a post-quantum way to agree on a shared secret. This header also exposes the TLS hybrid `X25519MLKEM768` shape, which combines ML-KEM-768 with X25519. **Security note:** this implementation has not been independently audited. Use it carefully, and prefer audited crypto stacks for high-value deployments. ## ML-KEM-768 Sizes ```c #define FIO_MLKEM768_PUBLICKEYBYTES 1184 #define FIO_MLKEM768_SECRETKEYBYTES 2400 #define FIO_MLKEM768_CIPHERTEXTBYTES 1088 #define FIO_MLKEM768_SSBYTES 32 #define FIO_MLKEM768_SYMBYTES 32 ``` Parameters: `n = 256`, `k = 3`, `q = 3329`, `eta1 = 2`, `eta2 = 2`, `d_u = 10`, `d_v = 4`. ## ML-KEM-768 API ### `fio_mlkem768_keypair` ```c SFUNC int fio_mlkem768_keypair(uint8_t pk[1184], uint8_t sk[2400]); ``` Generates a random ML-KEM-768 key pair using the system CSPRNG. Returns `0` on success, `-1` on failure. ### `fio_mlkem768_keypair_derand` ```c SFUNC int fio_mlkem768_keypair_derand(uint8_t pk[1184], uint8_t sk[2400], const uint8_t coins[64]); ``` Generates a deterministic key pair. `coins` is exactly 64 bytes: `d || z`. This is mostly for test vectors and reproducible checks; production code should normally use `fio_mlkem768_keypair`. ### `fio_mlkem768_encaps` ```c SFUNC int fio_mlkem768_encaps(uint8_t ct[1088], uint8_t ss[32], const uint8_t pk[1184]); ``` Creates a ciphertext and shared secret for the holder of `pk`. Randomness comes from the system CSPRNG. Send `ct`; keep `ss` as the shared secret input to your KDF or protocol. ### `fio_mlkem768_encaps_derand` ```c SFUNC int fio_mlkem768_encaps_derand(uint8_t ct[1088], uint8_t ss[32], const uint8_t pk[1184], const uint8_t coins[32]); ``` Deterministic encapsulation using 32 bytes of caller-provided randomness. Useful for known-answer tests. Boring in production, which is exactly the point. ### `fio_mlkem768_decaps` ```c SFUNC int fio_mlkem768_decaps(uint8_t ss[32], const uint8_t ct[1088], const uint8_t sk[2400]); ``` Recovers the shared secret from a ciphertext and secret key. ML-KEM uses implicit rejection: invalid ciphertexts produce a pseudorandom shared secret derived from the secret key and ciphertext instead of a loud failure. That prevents chosen-ciphertext games from turning your error path into an oracle. Returns `0` for well-formed inputs. ## X25519MLKEM768 Hybrid `X25519MLKEM768` is the TLS 1.3 hybrid key exchange shape from the ECDHE/ML-KEM draft. It needs `FIO_ED25519` too, because this STL implements X25519 in the Ed25519/Curve25519 header. The name starts with X25519, but the bytes start with ML-KEM. Yes, naming is hard. ```c #define FIO_X25519MLKEM768_PUBLICKEYBYTES (32 + 1184) /* 1216 */ #define FIO_X25519MLKEM768_SECRETKEYBYTES (32 + 2400) /* 2432 */ #define FIO_X25519MLKEM768_CIPHERTEXTBYTES (32 + 1088) /* 1120 */ #define FIO_X25519MLKEM768_SSBYTES 64 ``` | Value | Layout | | --- | --- | | Public key | `ML-KEM-768_ek` (1184) `||` `X25519_pk` (32) | | Secret key | `ML-KEM-768_dk` (2400) `||` `X25519_sk` (32) | | Ciphertext | `ML-KEM-768_ct` (1088) `||` `X25519_ephemeral_pk` (32) | | Shared secret | `ML-KEM-768_ss` (32) `||` `X25519_ss` (32) | ### `fio_x25519mlkem768_keypair` ```c SFUNC int fio_x25519mlkem768_keypair(uint8_t pk[1216], uint8_t sk[2432]); ``` Generates both the ML-KEM-768 and X25519 key pairs with system randomness. Returns `0` on success. ### `fio_x25519mlkem768_encaps` ```c SFUNC int fio_x25519mlkem768_encaps(uint8_t ct[1120], uint8_t ss[64], const uint8_t pk[1216]); ``` Performs ML-KEM encapsulation and X25519 ephemeral key exchange. The returned shared secret is the concatenation `ML-KEM-768_ss || X25519_ss`. ### `fio_x25519mlkem768_decaps` ```c SFUNC int fio_x25519mlkem768_decaps(uint8_t ss[64], const uint8_t ct[1120], const uint8_t sk[2432]); ``` Decapsulates the ML-KEM part and computes the X25519 shared secret. Returns `0` on success, or `-1` if X25519 rejects the peer point. ML-KEM invalid ciphertexts still use implicit rejection. ## Example ```c #define FIO_MLKEM #include "fio-stl.h" int roundtrip(void) { uint8_t pk[FIO_MLKEM768_PUBLICKEYBYTES]; uint8_t sk[FIO_MLKEM768_SECRETKEYBYTES]; uint8_t ct[FIO_MLKEM768_CIPHERTEXTBYTES]; uint8_t sender_ss[FIO_MLKEM768_SSBYTES]; uint8_t receiver_ss[FIO_MLKEM768_SSBYTES]; if (fio_mlkem768_keypair(pk, sk)) return -1; if (fio_mlkem768_encaps(ct, sender_ss, pk)) return -1; if (fio_mlkem768_decaps(receiver_ss, ct, sk)) return -1; return fio_memcmp(sender_ss, receiver_ss, sizeof(sender_ss)); } ``` ## Implementation Notes The implementation uses ML-KEM-768 parameters, NTT arithmetic, Montgomery and Barrett reduction, and optional NEON / AVX2 paths for vectorized polynomial work. Those SIMD paths are speed knobs, not different APIs. ------------------------------------------------------------ # X.509 Certificates ```c #define FIO_X509 #include "fio-stl.h" ``` This module parses DER-encoded X.509v3 certificates for TLS 1.3 style certificate checks. It is a compact parser, verifier, trust-store checker, TLS Certificate-message splitter, and small self-signed certificate generator. It is non-allocating while parsing: `fio_x509_cert_s` stores pointers into the original DER bytes. Keep those bytes alive. Certificates dislike disappearing floors. **Scope note:** this is a minimal TLS-oriented X.509 implementation, not a full PKI policy engine. Not all X.509 features are supported. ## Supported Pieces - RSA, ECDSA P-256, ECDSA P-384, and Ed25519 public keys. - RSA PKCS#1 v1.5, RSA-PSS, ECDSA, and Ed25519 signature verification. - Validity period checks. - Hostname matching using SAN DNS names and CN fallback, with one-label wildcards. - Basic Constraints and Key Usage. - Certificate chain validation against an optional trust store. - TLS 1.3 Certificate message parsing. - Self-signed certificate generation for Ed25519 and P-256 keys. ## Main Types ### Key and Signature Enums ```c typedef enum { FIO_X509_KEY_UNKNOWN = 0, FIO_X509_KEY_RSA = 1, FIO_X509_KEY_ECDSA_P256 = 2, FIO_X509_KEY_ECDSA_P384 = 3, FIO_X509_KEY_ED25519 = 4, } fio_x509_key_algo_e; ``` ```c typedef enum { FIO_X509_SIGNATURE_UNKNOWN = 0, FIO_X509_SIGNATURE_RSA_PKCS1_SHA256 = 1, FIO_X509_SIGNATURE_RSA_PKCS1_SHA384 = 2, FIO_X509_SIGNATURE_RSA_PKCS1_SHA512 = 3, FIO_X509_SIGNATURE_RSA_PSS_SHA256 = 4, FIO_X509_SIGNATURE_RSA_PSS_SHA384 = 5, FIO_X509_SIGNATURE_RSA_PSS_SHA512 = 6, FIO_X509_SIGNATURE_ECDSA_SHA256 = 7, FIO_X509_SIGNATURE_ECDSA_SHA384 = 8, FIO_X509_SIGNATURE_ED25519 = 9, } fio_x509_signature_algo_e; ``` ### Key Usage and Errors `fio_x509_key_usage_e` defines RFC 5280 key-usage bits such as `FIO_X509_KU_DIGITAL_SIGNATURE`, `FIO_X509_KU_KEY_ENCIPHERMENT`, and `FIO_X509_KU_KEY_CERT_SIGN`. ASN.1 bit strings use MSB-first ordering, so the constants look a little backwards until they save you from off-by-one sadness. `fio_x509_error_e` gives chain validation results: `FIO_X509_OK`, parse failure, expired/not-yet-valid, signature failure, issuer mismatch, not-a-CA, no trust anchor, hostname mismatch, empty chain, and chain-too-long. ### `fio_x509_trust_store_s` ```c typedef struct { const uint8_t **roots; const size_t *root_lens; size_t root_count; } fio_x509_trust_store_s; ``` A simple root store: arrays of DER certificate pointers and lengths. ### `fio_tls_cert_entry_s` ```c typedef struct { const uint8_t *cert; size_t cert_len; } fio_tls_cert_entry_s; ``` One certificate entry extracted from a TLS 1.3 `Certificate` handshake message. ### `fio_x509_cert_s` ```c typedef struct fio_x509_cert_s fio_x509_cert_s; ``` The parsed certificate structure uses non-owning buffer views (`fio_buf_info_s` / `fio_ubuf_info_s`) and packs to 256 bytes with no interior padding: - `verified`: non-zero when a TLS backend verified this certificate's chain (peer inspection only; always zero after `fio_x509_parse`); - `der`: a reference to the original DER bytes (not a copy); - `fingerprint`: 32-byte SHA-256 of the DER bytes (see `fio_x509_fingerprint`); - `version`, `serial`, and validity timestamps; - raw subject and issuer DNs; - subject CN; - public key type and key data; - signature algorithm and signature bytes; - TBS certificate bytes used for verification; - Basic Constraints (`is_ca`), Key Usage (`has_key_usage`, `key_usage`), and SAN data (first DNS name, first IP address, and the raw SAN extension for iterating the rest). All view pointers reference the original DER buffer. ## Parsing and Single-Certificate Checks ### `fio_x509_parse` ```c SFUNC int fio_x509_parse(fio_x509_cert_s *cert, const uint8_t *der_data, size_t der_len); ``` Parses one DER certificate. `cert` is zeroed first. Returns `0` on success, `-1` on parse error. ### `fio_x509_fingerprint` ```c SFUNC void fio_x509_fingerprint(fio_x509_cert_s *cert); ``` Computes the SHA-256 of the certificate's DER bytes into `cert->fingerprint` (32 raw bytes). Call after `fio_x509_parse`; hashing is lazy so parsing never pays for it. Requires the SHA2 module. ### `fio_x509_verify_signature` ```c SFUNC int fio_x509_verify_signature(const fio_x509_cert_s *cert, const fio_x509_cert_s *issuer); ``` Verifies that `issuer` signed `cert`, using the issuer public key and the certificate signature algorithm. Returns `0` when the signature is valid, `-1` otherwise. ### `fio_x509_check_validity` ```c FIO_IFUNC int fio_x509_check_validity(const fio_x509_cert_s *cert, int64_t current_time); ``` Checks `not_before <= current_time <= not_after`. Returns `0` when the certificate is in its validity window. ### `fio_x509_match_hostname` ```c SFUNC int fio_x509_match_hostname(const fio_x509_cert_s *cert, const char *hostname, size_t hostname_len); ``` Checks whether a certificate matches a hostname. SAN DNS entries are supported, with CN fallback. Wildcards are one-label wildcards such as `*.example.com`; they do not swallow `a.b.example.com`. Returns `0` for a match, `-1` for no match. Distinguished Names are compared as raw DER with the core `FIO_BUF_INFO_IS_EQ` macro (e.g., `FIO_BUF_INFO_IS_EQ(cert.issuer, root.subject)`). ## Chain Validation ### `fio_x509_verify_chain` ```c SFUNC int fio_x509_verify_chain(const uint8_t **certs, const size_t *cert_lens, size_t cert_count, const char *hostname, int64_t current_time, fio_x509_trust_store_s *trust_store); ``` Validates a certificate chain for TLS-style use. Expected order: 1. `certs[0]`: end-entity / server certificate. 2. `certs[1]`: intermediate that signed `certs[0]`. 3. `certs[n-1]`: closest-to-root certificate. Validation parses all certificates, checks validity periods, optionally matches the hostname, verifies each signature with the next certificate, checks issuer/subject DN links, requires CA certificates where needed, and optionally checks the final certificate against `trust_store`. Trust anchors are matched by (subject DN, public key): the anchor's key must verify the final certificate's signature. This also covers self-signed certificates — a same-named impostor with a different key is rejected. Returns `FIO_X509_OK` (`0`) on success, or a `FIO_X509_ERR_*` code. ### `fio_x509_is_trusted` ```c SFUNC int fio_x509_is_trusted(const fio_x509_cert_s *cert, fio_x509_trust_store_s *trust_store); ``` Checks whether `cert` appears in the trust store by subject DN match. Returns `0` if trusted, `-1` if not found. ### `fio_x509_error_str` ```c FIO_IFUNC const char *fio_x509_error_str(int error); ``` Returns a static string for a chain validation error code. ## TLS Certificate Messages ```c #define FIO_TLS_CERT_PARSE_ERROR ((size_t)-1) ``` ### `fio_tls_parse_certificate_message` ```c SFUNC size_t fio_tls_parse_certificate_message(fio_tls_cert_entry_s *entries, size_t max_entries, const uint8_t *data, size_t data_len); ``` Parses a TLS 1.3 `Certificate` message body, after the handshake header. It extracts certificate entries and skips per-certificate extensions. Returns the number of certificates parsed, or `FIO_TLS_CERT_PARSE_ERROR` on malformed input. ## Self-Signed Certificate Generation Generation supports Ed25519 and P-256 key pairs. ```c typedef enum { FIO_X509_KEYPAIR_ED25519 = 1, FIO_X509_KEYPAIR_P256 = 2, } fio_x509_keypair_type_e; ``` `fio_x509_keypair_s` stores the selected key type, secret key bytes, public key bytes, and their lengths. Use `fio_x509_keypair_clear` when done. `fio_x509_cert_options_s` controls the subject CN, organization, organizational unit, country, validity window, SAN DNS names, CA flag, and key-usage bits. ### `fio_x509_keypair_ed25519` ```c SFUNC int fio_x509_keypair_ed25519(fio_x509_keypair_s *keypair); ``` Generates an Ed25519 key pair for certificate signing. Requires the Ed25519 module to be included. Returns `0` on success. ### `fio_x509_keypair_p256` ```c SFUNC int fio_x509_keypair_p256(fio_x509_keypair_s *keypair); ``` Generates a P-256 key pair for certificate signing. Requires the P-256 module to be included. Returns `0` on success. ### `fio_x509_self_signed_cert` ```c SFUNC size_t fio_x509_self_signed_cert(uint8_t *buf, size_t buf_len, const fio_x509_keypair_s *keypair, const fio_x509_cert_options_s *options); ``` Writes a DER-encoded self-signed X.509v3 certificate. Call with `buf == NULL` to get a worst-case buffer size, then call again with a real buffer. Returns bytes written, or `0` on error. ### `fio_x509_keypair_clear` ```c FIO_IFUNC void fio_x509_keypair_clear(fio_x509_keypair_s *keypair); ``` Securely clears key material and zeros the structure. ## Example: Parse and Check Hostname ```c #define FIO_X509 #include "fio-stl.h" int check_cert(const uint8_t *der, size_t der_len, int64_t now) { fio_x509_cert_s cert; if (fio_x509_parse(&cert, der, der_len)) return -1; if (fio_x509_check_validity(&cert, now)) return -1; if (fio_x509_match_hostname(&cert, "example.com", 11)) return -1; return 0; } ``` ## Practical Notes - Define the crypto modules you need before including the STL: RSA, P-256, P-384, Ed25519, SHA-2, and ASN.1 may all matter depending on certificate algorithms. - `trust_store == NULL` skips root trust checking in `fio_x509_verify_chain`; useful for structural tests, not for real trust decisions. - Hostname checks are DNS-name focused. IP address SAN handling is not a full replacement for platform certificate validation. - Parsed strings and DNs are raw certificate data. Normalize policy elsewhere if your application needs browser-grade PKI behavior. ------------------------------------------------------------ # PEM Parser ```c #define FIO_PEM #include "fio-stl.h" ``` PEM wraps DER bytes in Base64 armor: ```text -----BEGIN