Layer: L7 Application ยท Build flags: PC_ENABLE_AUDIT_LOG
What this example teaches
Security-relevant events (logins, config changes) need a log you can trust after the fact. This records them in an append-only log where each record chains SHA-256(prev_hash || fields) - so altering or removing any retained record breaks the chain, and /audit reports the break via "intact":false plus the first broken sequence number. A sink forwards every record to durable storage at the moment it is created, before the RAM ring can evict it.
Reset the chain, register a sink, append events.
pc_audit_reset();
pc_audit_set_sink(audit_sink);
pc_audit_append(pc_audit_cat::PC_AUDIT_SYSTEM, "boot");
Events are appended with a category and a message; the library computes the chain hash:
bool ok = pass && strcmp(pass, "secret") == 0;
pc_audit_append(ok ? pc_audit_cat::PC_AUDIT_AUTH : pc_audit_cat::PC_AUDIT_AUTH_FAIL, msg);
The sink is what makes it durable. It runs once per record at append time and receives the full record including its chain hash, so the external copy keeps the same tamper-evident chain even after the in-RAM ring rolls over:
static void audit_sink(const pc_audit_entry *e) {
char line[256];
if (pc_audit_format(e, line, sizeof(line)) > 0) {
Serial.print("[AUDIT] "); Serial.println(line);
}
}
pc_audit_dump_json() serializes the chain plus the integrity status for the /audit endpoint.
Build and run
pio ci --board=esp32dev --project-option="framework=arduino" \
--project-option="build_flags=-DPC_ENABLE_AUDIT_LOG=1" \
--lib="." examples/L7-Application/AuditLog/AuditLog.ino
curl "http://<ip>/login?user=alice&pass=secret" # logs auth
curl "http://<ip>/config?http_port=8080" # logs a config change
curl http://<ip>/audit # chain dump + {"intact":true}
Annotated source
The complete sketch (AuditLog.ino), reproduced verbatim with added explanatory comments:
#define PC_ENABLE_AUDIT_LOG 1
static const char *SSID = "YOUR_SSID";
static const char *PASSWORD = "YOUR_PASSWORD";
static void audit_sink(const pc_audit_entry *e)
{
char line[256];
if (pc_audit_format(e, line, sizeof(line)) > 0)
{
Serial.print("[AUDIT] ");
Serial.println(line);
}
}
void setup()
{
delay(250);
Serial.print("IP: ");
Serial.printf("IP: %u.%u.%u.%u\n", (unsigned)(ip & 0xFF), (unsigned)((ip >> 8) & 0xFF),
(unsigned)((ip >> 16) & 0xFF), (unsigned)((ip >> 24) & 0xFF));
pc_audit_reset();
pc_audit_set_sink(audit_sink);
pc_audit_append(pc_audit_cat::PC_AUDIT_SYSTEM, "boot");
char msg[PC_AUDIT_MSG_LEN];
bool ok = pass && strcmp(pass, "secret") == 0;
snprintf(msg, sizeof(msg), "login %s", user ? user : "?");
pc_audit_append(ok ? pc_audit_cat::PC_AUDIT_AUTH : pc_audit_cat::PC_AUDIT_AUTH_FAIL, msg);
server.
send(
id, ok ? 200 : 401,
"application/json", ok ?
"{\"ok\":true}" :
"{\"ok\":false}");
});
char msg[PC_AUDIT_MSG_LEN];
snprintf(msg, sizeof(msg), "set http_port=%s", port ? port : "?");
pc_audit_append(pc_audit_cat::PC_AUDIT_CONFIG, msg);
server.
send(
id, 200,
"application/json",
"{\"ok\":true}");
});
char doc[2048];
if (pc_audit_dump_json(doc, sizeof(doc)) > 0)
server.
send(
id, 200,
"application/json", doc);
else
server.
send(
id, 500,
"application/json",
"{\"error\":\"buffer\"}");
});
}
void loop()
{
}
Tamper-evident, hash-chained audit log (PC_ENABLE_AUDIT_LOG).
Single-port HTTP server with deterministic, zero-allocation execution.
void send(uint8_t slot_id, int code, const char *content_type, const char *payload)
Send an HTTP response with a body and close the connection.
int32_t begin(const WebServerConfig *cfg=nullptr)
Initialize all connection slots and open all registered listeners.
void on(const char *path, HttpMethod method, Handler callback)
Register a route handler.
void handle()
Drive the server - call every Arduino loop() iteration.
const char * http_get_query(const HttpReq *req, const char *key)
Look up a query parameter value by name (case-sensitive).
bool init_wifi_physical(const char *, const char *)
Connect to a WiFi access point.
uint32_t pc_net_egress_ip(void)
IPv4 (network byte order) of the current egress interface, or 0 if none.
bool wifi_ready()
True if the WiFi station link is up (associated + an IP is assigned).
Layer 1 (Physical) - link bring-up and live egress-interface reporting.
Layer 7 (Application) - public HTTP routing API.
@ HTTP_GET
Safe, idempotent read.
Fully-parsed HTTP/1.1 request.