ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
audit_log.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file audit_log.h
6 * @brief Tamper-evident, hash-chained audit log (PROTOCORE_ENABLE_AUDIT_LOG).
7 *
8 * An append-only security log where each entry carries
9 * `hash = SHA-256(prev_hash || seq || ts || category || msg)`, chaining every
10 * record to its predecessor. Altering, reordering, or deleting any retained
11 * record breaks the chain, which protocore_audit_verify() detects in O(n). All
12 * storage is a fixed RAM ring of PROTOCORE_AUDIT_LOG_ENTRIES records (no heap,
13 * bounded latency); when it wraps, the evicted record's hash becomes a moving
14 * chain anchor, so the *retained window* still verifies end-to-end.
15 *
16 * **Durability / forwarding.** The RAM ring is only the recent window for query
17 * and verification. Install a sink with protocore_audit_set_sink() to forward every
18 * record, at the moment it is created (before it can ever be evicted), to a
19 * durable or remote store - an SD-card file, a syslog / HTTP log service, a
20 * serial console. Because the sink receives the full record including its chain
21 * hash, the external store preserves the same tamper-evident chain. Use
22 * protocore_audit_format() to render a record as one JSON line for that sink.
23 *
24 * Pure and host-tested (the chain is the same on host and ESP32; SHA-256 comes
25 * from protocore_sha256, hardware-accelerated on ESP32). Single-accessor like the log
26 * buffer: append from one context (a worker / loop), not concurrently.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32#ifndef PROTOCORE_AUDIT_LOG_H
33#define PROTOCORE_AUDIT_LOG_H
34
35#include "protocore_config.h" // the entry point: protocore_types.h for the widths
36
37#if PROTOCORE_ENABLE_AUDIT_LOG
38
40
41// PROTOCORE_AUDIT_LOG_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums it
42// into its arena. A caller takes them once and passes the pointer to every call. How they are
43// carved is this module's and is never named here.
44
45#define PROTOCORE_AUDIT_HASH_LEN 32
46
47/** @brief Standard audit event categories (extend with your own values). */
48typedef enum PROTO_ENUM_PACKED
49{
50 PROTOCORE_AUDIT_SYSTEM = 0, ///< Boot, shutdown, time change, generic.
51 PROTOCORE_AUDIT_AUTH = 1, ///< Authentication success (login).
52 PROTOCORE_AUDIT_AUTH_FAIL = 2, ///< Authentication failure.
53 PROTOCORE_AUDIT_ACCESS = 3, ///< Resource access (request served / denied).
54 PROTOCORE_AUDIT_CONFIG = 4, ///< Configuration change.
55 PROTOCORE_AUDIT_ADMIN = 5, ///< Privileged / administrative action.
56} protocore_audit_cat;
57
58/** @brief One audit record. seq is monotonic and never reused across evictions. */
59typedef struct
60{
61 uint32_t seq; ///< Monotonic sequence number (1-based).
62 uint32_t ts; ///< Timestamp from protocore_millis() at append.
63 protocore_audit_cat category; ///< audit category (a ::protocore_audit_cat, or a user value cast in).
64 char msg[PROTOCORE_AUDIT_MSG_LEN]; ///< Null-terminated message (truncated).
65 uint8_t hash[PROTOCORE_AUDIT_HASH_LEN]; ///< SHA-256(prev_hash || fields).
66} protocore_audit_entry;
67
68/** @brief Sink invoked once per record, at append time, for durable forwarding. */
69typedef void (*protocore_audit_sink_fn)(const protocore_audit_entry *entry);
70
71/** @brief What set_sink takes. */
72typedef struct
73{
74 protocore_audit_sink_fn sink;
75} AuditLogSetSinkArgs;
76
77/** @brief What append takes. */
78typedef struct
79{
80 protocore_audit_cat category;
81 const char *msg;
82} AuditLogAppendArgs;
83
84/** @brief What at takes. */
85typedef struct
86{
87 uint16_t i;
88} AuditLogAtArgs;
89
90/** @brief What verify takes. */
91typedef struct
92{
93 uint32_t *first_broken_seq;
94} AuditLogVerifyArgs;
95
96/** @brief What cat_name takes. */
97typedef struct
98{
99 protocore_audit_cat category;
100} AuditLogCatNameArgs;
101
102/** @brief What format takes. */
103typedef struct
104{
105 const protocore_audit_entry *entry;
106 char *out;
107 size_t cap;
108} AuditLogFormatArgs;
109
110/** @brief What dump_json takes. */
111typedef struct
112{
113 char *out;
114 size_t cap;
115} AuditLogDumpJsonArgs;
116typedef struct
117{
118 AuditLogSetSinkArgs set_sink_args;
119 AuditLogAppendArgs append_args;
120 AuditLogAtArgs at_args;
121 AuditLogVerifyArgs verify_args;
122 AuditLogCatNameArgs cat_name_args;
123 AuditLogFormatArgs format_args;
124 AuditLogDumpJsonArgs dump_json_args;
125 proto_bool ok;
126 uint32_t ms;
127 uint16_t value;
128 const protocore_audit_entry *ptr;
129 const char *text;
130 int n;
131} AuditLogVars;
132
133/** @brief The operands and the outcome. */
134extern AuditLogVars AuditLogV;
135
136/** @brief The entries. */
137typedef struct
138{
139 void (*const reset)(uint8_t *work);
140 void (*const set_sink)(uint8_t *work);
141 void (*const append)(uint8_t *work);
142 void (*const count)(uint8_t *work);
143 void (*const at)(uint8_t *work);
144 void (*const verify)(uint8_t *work);
145 void (*const cat_name)(uint8_t *work);
146 void (*const format)(uint8_t *work);
147 void (*const dump_json)(uint8_t *work);
148} AuditLogNs;
149
150// What the table binds, defined once in the .c and taking one parameter each: everything
151// else an entry needs is an operand in AuditLogV or a region of the borrow at a fixed offset.
152void protocore_audit_log_reset(uint8_t *work);
153void protocore_audit_log_set_sink(uint8_t *work);
154void protocore_audit_log_append(uint8_t *work);
155void protocore_audit_log_count(uint8_t *work);
156void protocore_audit_log_at(uint8_t *work);
157void protocore_audit_log_verify(uint8_t *work);
158void protocore_audit_log_cat_name(uint8_t *work);
159void protocore_audit_log_format(uint8_t *work);
160void protocore_audit_log_dump_json(uint8_t *work);
161
162// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
163// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
164// `AuditLog.reset(work)` resolves to a named function and becomes a DIRECT call. An extern table
165// leaves the call indirect and the symbol live at every level, -O2 -flto included.
166static const AuditLogNs AuditLog __attribute__((unused)) = {
167 .reset = protocore_audit_log_reset,
168 .set_sink = protocore_audit_log_set_sink,
169 .append = protocore_audit_log_append,
170 .count = protocore_audit_log_count,
171 .at = protocore_audit_log_at,
172 .verify = protocore_audit_log_verify,
173 .cat_name = protocore_audit_log_cat_name,
174 .format = protocore_audit_log_format,
175 .dump_json = protocore_audit_log_dump_json,
176};
177
178/**
179 * @brief The PROTOCORE_AUDIT_LOG_BORROW bytes this module's state lives in.
180 *
181 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
182 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
183 * walks, so the state lasts the life of the program.
184 *
185 * @return the span.
186 */
187uint8_t *protocore_audit_log_span(void);
188
190
191#endif // PROTOCORE_ENABLE_AUDIT_LOG
192
193#endif // PROTOCORE_AUDIT_LOG_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97