Move console payloads to lazy PSRAM storage
This commit is contained in:
@@ -224,6 +224,7 @@ Hardware diagnostics are synchronous console commands. RS-232 tests own the phys
|
|||||||
- Password authentication performs PBKDF2 outside the user-database mutex and revalidates afterward. Some password mutation paths currently derive verifiers while holding the mutation lock; do not generalize the authentication locking pattern without checking the exact path.
|
- Password authentication performs PBKDF2 outside the user-database mutex and revalidates afterward. Some password mutation paths currently derive verifiers while holding the mutation lock; do not generalize the authentication locking pattern without checking the exact path.
|
||||||
- Avoid holding service/database/broker locks across I2C, network sends, or other potentially long operations unless the existing contract explicitly requires it. Preserve the existing broker-before-serial lock order.
|
- Avoid holding service/database/broker locks across I2C, network sends, or other potentially long operations unless the existing contract explicitly requires it. Preserve the existing broker-before-serial lock order.
|
||||||
- Serial RX/TX stream payloads, broker per-client payloads, the transactional user-database candidate, and selected cryptographic allocations prefer PSRAM with internal fallback. The live user database, FreeRTOS control structures, UART driver buffers, and task stacks remain internal where deterministic/cache-disable access matters.
|
- Serial RX/TX stream payloads, broker per-client payloads, the transactional user-database candidate, and selected cryptographic allocations prefer PSRAM with internal fallback. The live user database, FreeRTOS control structures, UART driver buffers, and task stacks remain internal where deterministic/cache-disable access matters.
|
||||||
|
- Ping's 21-event payload (4,200 bytes) and the public user-console snapshot (2,156 bytes) are lazy PSRAM-only allocations retained for firmware lifetime, with no internal fallback. Queue control and synchronization stay internal; only ping or user status/list/show fails on allocation failure, never registration, mutations or UART0 recovery. Commands retain dispatcher/gate serialization; ping callbacks are task-context producers. The entire user snapshot is wiped after each display attempt. Never introduce ISR/cache-off access or free payloads while callbacks can retain them. Regression coverage: `tests/admin_console_boundary/psram_ping.py` and `accounts.py`.
|
||||||
- The build disables wolfSSL ESP32 AES/SHA acceleration, and the HTTPS path uses software AES for PSRAM-backed records. This preserves the validated workaround for uncoordinated mbedTLS/wolfSSL hardware-crypto locks and a prior mbedTLS external-RAM DMA watchdog stall.
|
- The build disables wolfSSL ESP32 AES/SHA acceleration, and the HTTPS path uses software AES for PSRAM-backed records. This preserves the validated workaround for uncoordinated mbedTLS/wolfSSL hardware-crypto locks and a prior mbedTLS external-RAM DMA watchdog stall.
|
||||||
|
|
||||||
## Legacy credential removal storage boundary
|
## Legacy credential removal storage boundary
|
||||||
|
|||||||
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), then [architecture](architecture.md) and [decisions](design-decisions.md).
|
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), then [architecture](architecture.md) and [decisions](design-decisions.md).
|
||||||
|
|
||||||
|
## Focused cleanup / PSRAM review — 2026-09-13
|
||||||
|
|
||||||
|
- User-authorized post-acceptance code review: removed superseded `admin_ssh_console_open()` SSH-only wrapper; production already uses available-slot admission. Updated adapter regression to actual production entry. Wrapper was already linker-discarded: no binary saving attributed to removal.
|
||||||
|
- Moved ping queue payload4,200 B and public user snapshot2,156 B to lazy PSRAM-only lifetime allocations, no fallback. Queue control/internal locks unchanged; allocation failure affects only ping or status/list/show, not UART0 registration/mutations/recovery. Snapshot fully wiped on success/error. No serial hot-path, stacks, CPU, queue bounds or external-BSS config changes.
|
||||||
|
- Baseline pio PASS23.79s100,556 RAM/1,828,573 flash. Final parent pio PASS22.11s **94,212 RAM/1,828,809 flash: −6,344 B linked internal RAM/+236 B flash**. Requested lazy PSRAM6,356 B plus allocator overhead; target pointer sizes4 B, controls84 B verified. This is not measured runtime-minimum improvement. CPU160 retained.
|
||||||
|
- Focused ping allocation/callback/end-capacity and accounts allocation/full-wipe/failure/retry tests PASS; console boundary/lifecycle/policy and SSH management/runtime suites PASS. Independent review found no actionable bugs and reran ping/accounts/boundary/diff PASS. Sanitizer linking unavailable (missing host runtimes), no sanitizer or hardware pass. No upload/erase/commit. Next target check: ping/user list/show repeatedly alongside NVS writes and full mix230400, collect serial/broker counters and memory before/after first allocations.
|
||||||
|
- Further audited opportunities, not implemented: OLED framebuffer1,024 B (internal I2C staging retained), local/remote completion scratch1,024 B each, optional web diagnostic ring2,816 B, remote console output payload8,192 B. Console rings need explicit secret-output wiping/lifecycle and admission-failure isolation; do not relocate mixed state wholesale. Leave authoritative user database, driver/DMA buffers, locks and task stacks internal. Global external-BSS enable is not surgical: it also changes SDK library placement.
|
||||||
|
|
||||||
## Accepted state — 2026-09-13
|
## Accepted state — 2026-09-13
|
||||||
|
|
||||||
- **8D.22 explicitly signed off by the user:** “Yep, I tested the firmware thats a 8d.22 signoff.” The retained Phase 8D scope is complete; earlier per-slice pending target/review/integration gates are superseded. Roadmap already records 8A/B/C as complete and target-hardware validated, so **Phase 8 is complete**. Acceptance does not manufacture individual unreported test passes.
|
- **8D.22 explicitly signed off by the user:** “Yep, I tested the firmware thats a 8d.22 signoff.” The retained Phase 8D scope is complete; earlier per-slice pending target/review/integration gates are superseded. Roadmap already records 8A/B/C as complete and target-hardware validated, so **Phase 8 is complete**. Acceptance does not manufacture individual unreported test passes.
|
||||||
@@ -23,7 +31,7 @@ Working memory, not an implementation timeline. Source is authoritative; begin w
|
|||||||
- Typed operations carry original-login IDs to the existing dispatcher. Owner-reserved generations fence stale/ABA changes; later revocation/timeout does not cancel admitted work. HTTPS commits before stop/restart; SSH stops before commit/restart; committed identity never rolls back on lifecycle failure. Lost ACK/result means uncertainty, never automatic replay.
|
- Typed operations carry original-login IDs to the existing dispatcher. Owner-reserved generations fence stale/ABA changes; later revocation/timeout does not cancel admitted work. HTTPS commits before stop/restart; SSH stops before commit/restart; committed identity never rolls back on lifecycle failure. Lost ACK/result means uncertainty, never automatic replay.
|
||||||
- Preserve private IDF HTTPD version guards, at-most-one owner-work reservations through failed destruction, retained SSH context until all slots retire, bounded queues/buffers and secret-free metadata. Canonical recovery survives conditional-token exhaustion.
|
- Preserve private IDF HTTPD version guards, at-most-one owner-work reservations through failed destruction, retained SSH context until all slots retire, bounded queues/buffers and secret-free metadata. Canonical recovery survives conditional-token exhaustion.
|
||||||
|
|
||||||
## Documentation handoff
|
## Previous documentation consolidation handoff
|
||||||
|
|
||||||
Initial Git status was clean. This task changes root `README.md`, `docs/` and five test-directory READMEs; executable source/tests/config/generated assets remain untouched. Independent documentation review checked acceptance scope, owner contracts and local links. It restored explicit pointer-backed HTTPD response-header lifetime and same-version SDK-patch audit warnings, updated test README links, and removed obsolete forwarding notes without reopening sign-off.
|
Initial Git status was clean. This task changes root `README.md`, `docs/` and five test-directory READMEs; executable source/tests/config/generated assets remain untouched. Independent documentation review checked acceptance scope, owner contracts and local links. It restored explicit pointer-backed HTTPD response-header lifetime and same-version SDK-patch audit warnings, updated test README links, and removed obsolete forwarding notes without reopening sign-off.
|
||||||
|
|
||||||
|
|||||||
@@ -142,12 +142,6 @@ esp_err_t admin_ssh_console_dispatch_read_input(
|
|||||||
esp_err_t admin_ssh_console_dispatch_defer(
|
esp_err_t admin_ssh_console_dispatch_defer(
|
||||||
admin_ssh_deferred_action_type_t action, uint32_t argument);
|
admin_ssh_deferred_action_type_t action, uint32_t argument);
|
||||||
|
|
||||||
/* SSH compatibility entry point, implemented by the owner in ssh_transport.c.
|
|
||||||
* Token/principal are copied; no SSH or socket objects cross this boundary.
|
|
||||||
* Existing feed/close/read/snapshot APIs below also accept open_owned tokens.
|
|
||||||
*/
|
|
||||||
esp_err_t admin_ssh_console_open(const admin_ssh_console_token_t *token,
|
|
||||||
const user_principal_t *principal);
|
|
||||||
void admin_ssh_console_close(const admin_ssh_console_token_t *token);
|
void admin_ssh_console_close(const admin_ssh_console_token_t *token);
|
||||||
|
|
||||||
/* Called by the session owner. Returns false when input must be backpressured. */
|
/* Called by the session owner. Returns false when input must be backpressured. */
|
||||||
|
|||||||
+13
-1
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "esp_console.h"
|
#include "esp_console.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/queue.h"
|
#include "freertos/queue.h"
|
||||||
@@ -186,7 +187,9 @@ typedef struct {
|
|||||||
|
|
||||||
#define PING_EVENT_QUEUE_LENGTH (PING_MAX_COUNT + 1U)
|
#define PING_EVENT_QUEUE_LENGTH (PING_MAX_COUNT + 1U)
|
||||||
static StaticQueue_t s_ping_queue_storage;
|
static StaticQueue_t s_ping_queue_storage;
|
||||||
static uint8_t s_ping_queue_bytes[PING_EVENT_QUEUE_LENGTH * sizeof(ping_event_t)];
|
/* Dispatcher-owned lazy payload; retain for firmware lifetime so callback queue
|
||||||
|
* storage cannot dangle. Queue control stays internal. No internal-RAM fallback. */
|
||||||
|
static uint8_t *s_ping_queue_bytes;
|
||||||
static QueueHandle_t s_ping_queue;
|
static QueueHandle_t s_ping_queue;
|
||||||
|
|
||||||
static void ping_on_success(esp_ping_handle_t handle, void *arguments)
|
static void ping_on_success(esp_ping_handle_t handle, void *arguments)
|
||||||
@@ -289,6 +292,15 @@ static int execute_ping(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (s_ping_queue_bytes == NULL) {
|
||||||
|
s_ping_queue_bytes = heap_caps_malloc(
|
||||||
|
PING_EVENT_QUEUE_LENGTH * sizeof(ping_event_t),
|
||||||
|
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||||
|
if (s_ping_queue_bytes == NULL) {
|
||||||
|
printf("ping: PSRAM event storage unavailable\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (s_ping_queue == NULL) {
|
if (s_ping_queue == NULL) {
|
||||||
s_ping_queue = xQueueCreateStatic(PING_EVENT_QUEUE_LENGTH, sizeof(ping_event_t),
|
s_ping_queue = xQueueCreateStatic(PING_EVENT_QUEUE_LENGTH, sizeof(ping_event_t),
|
||||||
s_ping_queue_bytes, &s_ping_queue_storage);
|
s_ping_queue_bytes, &s_ping_queue_storage);
|
||||||
|
|||||||
@@ -292,15 +292,6 @@ static const admin_console_owner_t s_admin_console_owner = {
|
|||||||
.perform = admin_console_perform,
|
.perform = admin_console_perform,
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_err_t admin_ssh_console_open(const admin_ssh_console_token_t *token,
|
|
||||||
const user_principal_t *principal)
|
|
||||||
{
|
|
||||||
if (token == NULL || token->transport != ADMIN_CONSOLE_TRANSPORT_SSH) {
|
|
||||||
return ESP_ERR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
return admin_ssh_console_open_owned(token, principal, &s_admin_console_owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool consume_external_close(const ssh_slot_t *slot, size_t slot_index)
|
static bool consume_external_close(const ssh_slot_t *slot, size_t slot_index)
|
||||||
{
|
{
|
||||||
taskENTER_CRITICAL(&s_lock);
|
taskENTER_CRITICAL(&s_lock);
|
||||||
|
|||||||
+21
-8
@@ -11,6 +11,7 @@
|
|||||||
#include "admin_ssh_console.h"
|
#include "admin_ssh_console.h"
|
||||||
#include "console_input.h"
|
#include "console_input.h"
|
||||||
#include "esp_console.h"
|
#include "esp_console.h"
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
#include "mbedtls/base64.h"
|
#include "mbedtls/base64.h"
|
||||||
#include "secure_random.h"
|
#include "secure_random.h"
|
||||||
#include "ssh_transport.h"
|
#include "ssh_transport.h"
|
||||||
@@ -19,8 +20,9 @@
|
|||||||
|
|
||||||
#define USER_CONSOLE_KEY_LINE_CAPACITY 256U
|
#define USER_CONSOLE_KEY_LINE_CAPACITY 256U
|
||||||
|
|
||||||
/* `user` commands are serialized by the administration gate. */
|
/* Gate-owned public projection (no verifiers). Lazily retained for firmware
|
||||||
static user_database_snapshot_t s_user_snapshot;
|
* lifetime; allocation failure must not disable mutations or UART0 recovery. */
|
||||||
|
static user_database_snapshot_t *s_user_snapshot;
|
||||||
|
|
||||||
static void print_usage(void)
|
static void print_usage(void)
|
||||||
{
|
{
|
||||||
@@ -92,21 +94,30 @@ static void print_user(const user_database_user_snapshot_t *user)
|
|||||||
|
|
||||||
static int show_users(const char *selected)
|
static int show_users(const char *selected)
|
||||||
{
|
{
|
||||||
esp_err_t error = user_database_get_snapshot(&s_user_snapshot);
|
if (s_user_snapshot == NULL) {
|
||||||
|
s_user_snapshot = heap_caps_malloc(sizeof(*s_user_snapshot),
|
||||||
|
MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||||
|
if (s_user_snapshot == NULL) {
|
||||||
|
printf("User status unavailable: PSRAM snapshot storage unavailable\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
esp_err_t error = user_database_get_snapshot(s_user_snapshot);
|
||||||
if (error != ESP_OK) {
|
if (error != ESP_OK) {
|
||||||
printf("User database unavailable: %s\n", esp_err_to_name(error));
|
printf("User database unavailable: %s\n", esp_err_to_name(error));
|
||||||
|
secure_wipe(s_user_snapshot, sizeof(*s_user_snapshot));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (selected == NULL) {
|
if (selected == NULL) {
|
||||||
printf("User database: generation=%lu users=%u/%u admins=%u\n",
|
printf("User database: generation=%lu users=%u/%u admins=%u\n",
|
||||||
(unsigned long)s_user_snapshot.generation,
|
(unsigned long)s_user_snapshot->generation,
|
||||||
(unsigned int)s_user_snapshot.user_count,
|
(unsigned int)s_user_snapshot->user_count,
|
||||||
USER_DATABASE_MAX_USERS,
|
USER_DATABASE_MAX_USERS,
|
||||||
(unsigned int)s_user_snapshot.admin_count);
|
(unsigned int)s_user_snapshot->admin_count);
|
||||||
}
|
}
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (size_t index = 0U; index < USER_DATABASE_MAX_USERS; ++index) {
|
for (size_t index = 0U; index < USER_DATABASE_MAX_USERS; ++index) {
|
||||||
const user_database_user_snapshot_t *user = &s_user_snapshot.users[index];
|
const user_database_user_snapshot_t *user = &s_user_snapshot->users[index];
|
||||||
if (!user->active ||
|
if (!user->active ||
|
||||||
(selected != NULL &&
|
(selected != NULL &&
|
||||||
(strlen(selected) != user->username_length ||
|
(strlen(selected) != user->username_length ||
|
||||||
@@ -118,11 +129,13 @@ static int show_users(const char *selected)
|
|||||||
}
|
}
|
||||||
if (selected != NULL && !found) {
|
if (selected != NULL && !found) {
|
||||||
printf("User '%s' not found.\n", selected);
|
printf("User '%s' not found.\n", selected);
|
||||||
|
secure_wipe(s_user_snapshot, sizeof(*s_user_snapshot));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (s_user_snapshot.admin_count == 0U) {
|
if (s_user_snapshot->admin_count == 0U) {
|
||||||
printf("No administrators; use 'user add <username> admin' on UART0.\n");
|
printf("No administrators; use 'user add <username> admin' on UART0.\n");
|
||||||
}
|
}
|
||||||
|
secure_wipe(s_user_snapshot, sizeof(*s_user_snapshot));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,8 +138,9 @@ static bool admin_ssh_console_dispatch_is_current(void) {
|
|||||||
if (checks==revoke_check) owner_current=false;
|
if (checks==revoke_check) owner_current=false;
|
||||||
return owner_current && user_database_principal_is_current(&actor, ¤t)==ESP_OK && current;
|
return owner_current && user_database_principal_is_current(&actor, ¤t)==ESP_OK && current;
|
||||||
}
|
}
|
||||||
static int admin_command_gate_take(void) { return ESP_OK; }
|
static bool gate_held;
|
||||||
static void admin_command_gate_give(void) {}
|
static int admin_command_gate_take(void) { assert(!gate_held); gate_held=true; return ESP_OK; }
|
||||||
|
static void admin_command_gate_give(void) { assert(gate_held); gate_held=false; }
|
||||||
static int console_input_read_hidden(const char *prompt, uint8_t *out, size_t cap,
|
static int console_input_read_hidden(const char *prompt, uint8_t *out, size_t cap,
|
||||||
size_t min, size_t max, size_t *n) {
|
size_t min, size_t max, size_t *n) {
|
||||||
(void)prompt; (void)min; (void)max; assert(cap>=13); ++prompts;
|
(void)prompt; (void)min; (void)max; assert(cap>=13); ++prompts;
|
||||||
@@ -158,7 +159,28 @@ static int ssh_transport_revoke_user(const uint8_t *u, size_t n) {
|
|||||||
++ssh_revokes; assert(strlen(revoked_name)==n && !memcmp(u,revoked_name,n)); return notify_error;
|
++ssh_revokes; assert(strlen(revoked_name)==n && !memcmp(u,revoked_name,n)); return notify_error;
|
||||||
}
|
}
|
||||||
/* Forbidden paths are traps rather than alternative implementations. */
|
/* Forbidden paths are traps rather than alternative implementations. */
|
||||||
static int show_users(const char *n) { (void)n; return 0; }
|
#define MALLOC_CAP_SPIRAM 1U
|
||||||
|
#define MALLOC_CAP_8BIT 2U
|
||||||
|
static bool fail_snapshot_alloc;
|
||||||
|
static unsigned snapshot_allocations;
|
||||||
|
static struct { uint64_t before; user_database_snapshot_t value; uint64_t after; } snapshot_memory;
|
||||||
|
static void *heap_caps_malloc(size_t n, unsigned caps) {
|
||||||
|
assert(gate_held && n==sizeof(snapshot_memory.value));
|
||||||
|
assert(caps==(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||||
|
++snapshot_allocations;
|
||||||
|
if (fail_snapshot_alloc) return NULL;
|
||||||
|
snapshot_memory.before=snapshot_memory.after=UINT64_C(0xaabbccdd11223344);
|
||||||
|
memset(&snapshot_memory.value,0xa5,n);
|
||||||
|
return &snapshot_memory.value;
|
||||||
|
}
|
||||||
|
static unsigned user_registrations;
|
||||||
|
typedef struct { const char *command,*help,*hint; int (*func)(int,char **); void *argtable; } esp_console_cmd_t;
|
||||||
|
static int esp_console_cmd_register(const esp_console_cmd_t *c) {
|
||||||
|
assert(!strcmp(c->command,"user") && c->func); ++user_registrations; return ESP_OK;
|
||||||
|
}
|
||||||
|
static int mbedtls_base64_encode(uint8_t *out,size_t cap,size_t *n,const uint8_t *in,size_t len) {
|
||||||
|
(void)in; assert(cap>=4 && len==32); memcpy(out,"AAAA",4); *n=4; return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int add_key(const char *n) { (void)n; assert(!"key mutation"); return 1; }
|
static int add_key(const char *n) { (void)n; assert(!"key mutation"); return 1; }
|
||||||
static int add_key_parts(const char *n,const uint8_t *t,size_t tl,const uint8_t *b,size_t bl) {
|
static int add_key_parts(const char *n,const uint8_t *t,size_t tl,const uint8_t *b,size_t bl) {
|
||||||
@@ -190,17 +212,21 @@ db_names = ["constant_time_equal", "all_zero", "user_database_username_valid",
|
|||||||
"user_database_clear_ssh_keys_current", "fill_principal", "user_database_authorize_ssh_public_key",
|
"user_database_clear_ssh_keys_current", "fill_principal", "user_database_authorize_ssh_public_key",
|
||||||
"initialize_dummy_verifier", "user_database_init", "user_database_recover_empty",
|
"initialize_dummy_verifier", "user_database_init", "user_database_recover_empty",
|
||||||
"user_database_get_snapshot"]
|
"user_database_get_snapshot"]
|
||||||
console_names = ["print_usage", "revoke_user_network_sessions", "read_password",
|
console_names = ["print_fingerprint", "print_user", "show_users", "print_usage", "revoke_user_network_sessions", "read_password",
|
||||||
"show_generated_password", "mutation_currentness", "add_user", "change_password",
|
"show_generated_password", "mutation_currentness", "add_user", "change_password",
|
||||||
"parse_key_index", "recover_database", "command_user_inner", "command_user"]
|
"parse_key_index", "recover_database", "command_user_inner", "command_user",
|
||||||
|
"user_console_register_commands"]
|
||||||
unit = prelude + header + "\n" + state + fakes
|
unit = prelude + header + "\n" + state + fakes
|
||||||
unit += "\n".join(function(db, n) for n in db_names)
|
unit += "\n".join(function(db, n) for n in db_names)
|
||||||
unit += function(admin, "admin_ssh_console_web_user_command_allowed")
|
unit += function(admin, "admin_ssh_console_web_user_command_allowed")
|
||||||
|
unit += console[console.index("static user_database_snapshot_t"):console.index("static void print_usage")]
|
||||||
unit += "\n".join(function(console, n) for n in console_names)
|
unit += "\n".join(function(console, n) for n in console_names)
|
||||||
account_tests = (ROOT / "tests/admin_console_boundary/accounts.c").read_text()
|
account_tests = (ROOT / "tests/admin_console_boundary/accounts.c").read_text()
|
||||||
key_tests = (ROOT / "tests/admin_console_boundary/account_keys.c").read_text()
|
key_tests = (ROOT / "tests/admin_console_boundary/account_keys.c").read_text()
|
||||||
account_tests = account_tests.replace('int main(void)', key_tests + '\nint main(void)')
|
account_tests = account_tests.replace('int main(void)', key_tests + '\nint main(void)')
|
||||||
account_tests = account_tests.replace(' typed_account_tests();', ' typed_key_tests();\n typed_account_tests();')
|
snapshot_tests = (ROOT / "tests/admin_console_boundary/psram_snapshot.c").read_text()
|
||||||
|
account_tests = account_tests.replace('int main(void)', snapshot_tests + '\nint main(void)')
|
||||||
|
account_tests = account_tests.replace(' typed_account_tests();', ' psram_snapshot_tests();\n typed_key_tests();\n typed_account_tests();')
|
||||||
assert ' typed_key_tests();' in account_tests
|
assert ' typed_key_tests();' in account_tests
|
||||||
unit += account_tests
|
unit += account_tests
|
||||||
with tempfile.TemporaryDirectory(prefix="admin-accounts-") as directory:
|
with tempfile.TemporaryDirectory(prefix="admin-accounts-") as directory:
|
||||||
@@ -209,9 +235,13 @@ with tempfile.TemporaryDirectory(prefix="admin-accounts-") as directory:
|
|||||||
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", "-Wno-unused-variable",
|
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", "-Wno-unused-variable",
|
||||||
str(path / "test.c"), str(IDF / "components/console/split_argv.c"),
|
str(path / "test.c"), str(IDF / "components/console/split_argv.c"),
|
||||||
"-lcrypto", "-o", str(path / "test")], check=True, timeout=30)
|
"-lcrypto", "-o", str(path / "test")], check=True, timeout=30)
|
||||||
result = subprocess.run([str(path / "test")], check=True, timeout=10, capture_output=True, text=True)
|
result = subprocess.run([str(path / "test")], timeout=10, capture_output=True, text=True)
|
||||||
|
if result.returncode:
|
||||||
|
print(result.stderr)
|
||||||
|
result.check_returncode()
|
||||||
assert "test-password" not in result.stdout
|
assert "test-password" not in result.stdout
|
||||||
assert "Generated password for" not in result.stdout
|
assert "Generated password for" not in result.stdout
|
||||||
|
print("PASS: PSRAM snapshot allocation failure/retry/retention, status/list/show/unknown/unavailable, full wipe and bounds, gate ownership, mutation/recovery isolation")
|
||||||
print("PASS: empty initialization/recovery, unchanged v1 records, corrupt/unsupported fail-closed loads, first UART0 administrator and removed bootstrap commands")
|
print("PASS: empty initialization/recovery, unchanged v1 records, corrupt/unsupported fail-closed loads, first UART0 administrator and removed bootstrap commands")
|
||||||
print("PASS: canonical SSH keys: Ed25519/P256 parser and authorization, malformed/off-curve/truncated inputs, zero-wait fingerprints, stale ID/generation/recreation, duplicates/capacity, sparse indices, failed persistence and CLI parity (OpenSSL-backed curve/SHA adapters)")
|
print("PASS: canonical SSH keys: Ed25519/P256 parser and authorization, malformed/off-curve/truncated inputs, zero-wait fingerprints, stale ID/generation/recreation, duplicates/capacity, sparse indices, failed persistence and CLI parity (OpenSSL-backed curve/SHA adapters)")
|
||||||
print("PASS: operation-admission semantics: browser invalidated in derivation double before NVS; admitted add/password transactions still commit, only target is revoked, next command rejects; persistence failure still preserves live state (not precommit cancellation or real concurrency)")
|
print("PASS: operation-admission semantics: browser invalidated in derivation double before NVS; admitted add/password transactions still commit, only target is revoked, next command rejects; persistence failure still preserves live state (not precommit cancellation or real concurrency)")
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ static bool admin_console_drained(const admin_ssh_console_token_t *);
|
|||||||
static bool admin_console_is_current(const admin_ssh_console_token_t *, const user_principal_t *);
|
static bool admin_console_is_current(const admin_ssh_console_token_t *, const user_principal_t *);
|
||||||
static bool consume_external_close(const ssh_slot_t *, size_t);
|
static bool consume_external_close(const ssh_slot_t *, size_t);
|
||||||
static esp_err_t admin_console_perform(const admin_ssh_console_token_t *, admin_ssh_deferred_action_type_t, uint32_t);
|
static esp_err_t admin_console_perform(const admin_ssh_console_token_t *, admin_ssh_deferred_action_type_t, uint32_t);
|
||||||
|
static const admin_console_owner_t s_admin_console_owner;
|
||||||
static void test_adapter(void)
|
static void test_adapter(void)
|
||||||
{
|
{
|
||||||
admin_ssh_console_token_t token={ .slot_index=0, .session_id=7, .slot_generation=3 };
|
admin_ssh_console_token_t token={ .slot_index=0, .session_id=7, .slot_generation=3 };
|
||||||
@@ -40,7 +41,7 @@ static void test_adapter(void)
|
|||||||
.username_length=5, .username="admin" };
|
.username_length=5, .username="admin" };
|
||||||
assert(admin_ssh_console_init()==ESP_OK);
|
assert(admin_ssh_console_init()==ESP_OK);
|
||||||
assert(admin_ssh_console_start_uart_frontend()==ESP_OK);
|
assert(admin_ssh_console_start_uart_frontend()==ESP_OK);
|
||||||
assert(admin_ssh_console_open(&token,&admin)==ESP_OK);
|
assert(admin_ssh_console_open_available(&token,&admin,&s_admin_console_owner)==ESP_OK);
|
||||||
assert(!admin_console_drained(&token));
|
assert(!admin_console_drained(&token));
|
||||||
s_session_snapshots[0]=(ssh_transport_session_snapshot_t){ .active=true, .session_id=7, .generation=3 };
|
s_session_snapshots[0]=(ssh_transport_session_snapshot_t){ .active=true, .session_id=7, .generation=3 };
|
||||||
assert(!admin_console_drained(&token)); /* No published console binding. */
|
assert(!admin_console_drained(&token)); /* No published console binding. */
|
||||||
@@ -82,16 +83,19 @@ static void test_adapter(void)
|
|||||||
admin.username_length=1; assert(!admin_console_is_current(&token,&admin)); admin.username_length=5;
|
admin.username_length=1; assert(!admin_console_is_current(&token,&admin)); admin.username_length=5;
|
||||||
token.transport=1; assert(!admin_console_drained(&token));
|
token.transport=1; assert(!admin_console_drained(&token));
|
||||||
assert(!admin_console_is_current(&token,&admin));
|
assert(!admin_console_is_current(&token,&admin));
|
||||||
assert(admin_ssh_console_open(&token,&admin)==ESP_ERR_INVALID_ARG);
|
|
||||||
token.transport=0; token.slot_generation=4; assert(!admin_console_drained(&token));
|
token.transport=0; token.slot_generation=4; assert(!admin_console_drained(&token));
|
||||||
assert(!admin_console_is_current(&token,&admin));
|
assert(!admin_console_is_current(&token,&admin));
|
||||||
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_STOP,0)==ESP_ERR_NOT_FOUND && stopped==0);
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_STOP,0)==ESP_ERR_NOT_FOUND && stopped==0);
|
||||||
token.slot_generation=3;
|
token.slot_generation=3;
|
||||||
/* Same physical session can be assigned the other console slot. */
|
/* Same physical session can be assigned the other console slot. */
|
||||||
admin_ssh_console_close(&token);
|
admin_ssh_console_close(&token);
|
||||||
token.slot_index=1;
|
admin_ssh_console_token_t other={ .session_id=8, .slot_generation=1 };
|
||||||
assert(admin_ssh_console_open(&token,&admin)==ESP_OK);
|
assert(admin_ssh_console_open_available(&other,&admin,&s_admin_console_owner)==ESP_OK);
|
||||||
active.console_slot_index=1; publish_slot(&active,0);
|
assert(other.slot_index==0);
|
||||||
|
assert(admin_ssh_console_open_available(&token,&admin,&s_admin_console_owner)==ESP_OK);
|
||||||
|
assert(token.slot_index==1);
|
||||||
|
active.console_slot_index=token.slot_index; publish_slot(&active,0);
|
||||||
assert(admin_console_is_current(&token,&admin));
|
assert(admin_console_is_current(&token,&admin));
|
||||||
assert(s_console_slot_indices[0]==1);
|
assert(s_console_slot_indices[0]==1);
|
||||||
admin_ssh_console_token_t wrong=token; wrong.slot_index=0;
|
admin_ssh_console_token_t wrong=token; wrong.slot_index=0;
|
||||||
@@ -111,5 +115,5 @@ static void test_adapter(void)
|
|||||||
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_HOST_KEY_ROTATE,0)==ESP_OK && rotated==1);
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_HOST_KEY_ROTATE,0)==ESP_OK && rotated==1);
|
||||||
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_HOST_KEY_RESET,0)==ESP_OK && reset==1);
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_HOST_KEY_RESET,0)==ESP_OK && reset==1);
|
||||||
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_REBOOT,0)==ESP_OK && restarted==1);
|
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_REBOOT,0)==ESP_OK && restarted==1);
|
||||||
puts("PASS: actual SSH snapshot/principal publication and wiping, adapter identity/drain checks, legacy admission and lifecycle action routing");
|
puts("PASS: actual SSH snapshot/principal publication and wiping, adapter identity/drain checks, shared-slot admission and lifecycle action routing");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
static int run_ping(const char *count)
|
||||||
|
{
|
||||||
|
char *argv[]={"ping","localhost",(char *)count};
|
||||||
|
int result=network_console_execute(count?3:2,argv);
|
||||||
|
if(worker_started) { assert(!pthread_join(worker,NULL)); worker_started=false; }
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
_Static_assert(sizeof(ping_event_t)==200,"audited event payload changed");
|
||||||
|
assert(network_console_register_root_commands()==ESP_OK && registrations==3 && !allocations);
|
||||||
|
assert(run_ping("0")==1 && run_ping("21")==1 && run_ping("-1")==1);
|
||||||
|
assert(run_ping("999999999999999999999")==1 && !allocations);
|
||||||
|
resolve_fail=true; assert(run_ping(NULL)==1 && !allocations); resolve_fail=false;
|
||||||
|
fail_alloc=true; assert(run_ping(NULL)==1 && allocations==1 && !creations && !starts);
|
||||||
|
char *lookup[]={"nslookup","localhost"};
|
||||||
|
assert(network_console_execute(2,lookup)==0);
|
||||||
|
assert(network_console_register_root_commands()==ESP_OK && allocations==1);
|
||||||
|
fail_alloc=false; fail_queue=true;
|
||||||
|
assert(run_ping(NULL)==1 && allocations==2 && creations==1 && !starts);
|
||||||
|
void *retained=s_ping_queue_bytes;
|
||||||
|
fail_alloc=true; fail_queue=false; fail_new=true;
|
||||||
|
assert(run_ping(NULL)==1 && allocations==2 && creations==2 && !starts);
|
||||||
|
fail_new=false; fail_start=true;
|
||||||
|
assert(run_ping(NULL)==1 && deletes==1);
|
||||||
|
fail_start=false;
|
||||||
|
for(unsigned i=0;i<4;++i) {
|
||||||
|
burst=i%2==0;
|
||||||
|
assert(run_ping("20")==0);
|
||||||
|
assert(s_ping_queue_bytes==retained && allocations==2 && creations==2);
|
||||||
|
assert(queue.count==0);
|
||||||
|
assert(memory.before==UINT64_C(0x1122334455667788));
|
||||||
|
assert(memory.after==UINT64_C(0x1122334455667788));
|
||||||
|
}
|
||||||
|
/* Reset stale payload before a new session, preserving the end-event slot. */
|
||||||
|
ping_event_t stale={.kind=PING_EVENT_END,.profile_error=ESP_FAIL};
|
||||||
|
xQueueSend(s_ping_queue,&stale,0);
|
||||||
|
assert(run_ping("1")==0);
|
||||||
|
all_timeouts=true; assert(run_ping(NULL)==1); all_timeouts=false;
|
||||||
|
fail_profile=true; assert(run_ping(NULL)==1); fail_profile=false;
|
||||||
|
fail_delete=true; assert(run_ping(NULL)==1); fail_delete=false;
|
||||||
|
assert(run_ping(NULL)==0 && resets>=10 && allocations==2);
|
||||||
|
assert(s_ping_queue==&queue && s_ping_queue_bytes==retained);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Production ping command/callbacks with bounded pthread queue and SDK doubles."""
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
s = (ROOT / 'src/network_console.c').read_text()
|
||||||
|
def function(name):
|
||||||
|
start = s.rfind('\n', 0, s.index(name + '(')) + 1
|
||||||
|
return s[start:s.index('\n}', start) + 2] + '\n'
|
||||||
|
prelude = (ROOT / 'tests/admin_console_boundary/psram_ping_fakes.c').read_text()
|
||||||
|
unit = prelude + '\n'
|
||||||
|
unit += s[s.index('#define PING_DEFAULT_COUNT'):s.index('/* This covers')]
|
||||||
|
unit += function('print_command_usage') + function('parse_bounded_u32')
|
||||||
|
unit += s[s.index('typedef enum {\n PING_EVENT_LINE'):s.index('static bool socket_addresses_equal')]
|
||||||
|
unit += function('network_console_is_command') + function('network_console_execute')
|
||||||
|
unit += function('network_console_register_root_commands')
|
||||||
|
unit += (ROOT / 'tests/admin_console_boundary/psram_ping.c').read_text()
|
||||||
|
with tempfile.TemporaryDirectory(prefix='psram-ping-') as d:
|
||||||
|
p = Path(d)
|
||||||
|
(p / 'test.c').write_text(unit)
|
||||||
|
subprocess.run(['cc', '-std=c11', '-Wall', '-Wextra', '-Werror', '-pthread',
|
||||||
|
'-g', str(p / 'test.c'), '-o', str(p / 'test')],
|
||||||
|
check=True, timeout=30)
|
||||||
|
subprocess.run([str(p / 'test')], check=True, timeout=20)
|
||||||
|
print('PASS: production ping allocation/create-queue/session failures, retry/retention/reset, bounds, asynchronous success/timeout/end callbacks and registration isolation (host doubles)')
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/* Deterministic SDK boundaries; queue data movement uses the supplied payload. */
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
typedef int esp_err_t;
|
||||||
|
enum { ESP_OK, ESP_FAIL };
|
||||||
|
#define MALLOC_CAP_SPIRAM 1U
|
||||||
|
#define MALLOC_CAP_8BIT 2U
|
||||||
|
#define portMAX_DELAY UINT32_MAX
|
||||||
|
#define pdTRUE 1
|
||||||
|
static bool fail_alloc, fail_queue, fail_new, fail_start, fail_delete, fail_profile, resolve_fail;
|
||||||
|
static unsigned allocations, creations, resets, registrations, starts, deletes;
|
||||||
|
static struct { uint64_t before; uint8_t bytes[4200]; uint64_t after; } memory;
|
||||||
|
static void *heap_caps_malloc(size_t n, unsigned caps) {
|
||||||
|
++allocations; assert(n==sizeof(memory.bytes));
|
||||||
|
assert(caps==(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||||
|
if (fail_alloc) return NULL;
|
||||||
|
memory.before=memory.after=UINT64_C(0x1122334455667788);
|
||||||
|
return memory.bytes;
|
||||||
|
}
|
||||||
|
typedef struct { unsigned marker; } StaticQueue_t;
|
||||||
|
typedef struct { uint8_t *bytes; size_t length, size, read, count; } queue_t;
|
||||||
|
typedef queue_t *QueueHandle_t;
|
||||||
|
static queue_t queue;
|
||||||
|
static pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static pthread_cond_t ready=PTHREAD_COND_INITIALIZER;
|
||||||
|
static QueueHandle_t xQueueCreateStatic(size_t length,size_t size,uint8_t *bytes,StaticQueue_t *control) {
|
||||||
|
++creations; assert(length==21 && size==200 && bytes==memory.bytes && control);
|
||||||
|
if (fail_queue) return NULL;
|
||||||
|
queue=(queue_t){.bytes=bytes,.length=length,.size=size}; return &queue;
|
||||||
|
}
|
||||||
|
static int xQueueReset(QueueHandle_t q) {
|
||||||
|
pthread_mutex_lock(&mutex); ++resets; q->read=q->count=0;
|
||||||
|
pthread_mutex_unlock(&mutex); return pdTRUE;
|
||||||
|
}
|
||||||
|
static int xQueueSend(QueueHandle_t q,const void *event,unsigned wait) {
|
||||||
|
assert(wait==0); pthread_mutex_lock(&mutex); assert(q->count<q->length);
|
||||||
|
memcpy(q->bytes+((q->read+q->count)%q->length)*q->size,event,q->size);
|
||||||
|
++q->count; pthread_cond_signal(&ready); pthread_mutex_unlock(&mutex); return pdTRUE;
|
||||||
|
}
|
||||||
|
static int xQueueReceive(QueueHandle_t q,void *event,unsigned wait) {
|
||||||
|
assert(wait==portMAX_DELAY); pthread_mutex_lock(&mutex);
|
||||||
|
while (!q->count) pthread_cond_wait(&ready,&mutex);
|
||||||
|
memcpy(event,q->bytes+q->read*q->size,q->size);
|
||||||
|
q->read=(q->read+1)%q->length; --q->count;
|
||||||
|
pthread_mutex_unlock(&mutex); return pdTRUE;
|
||||||
|
}
|
||||||
|
typedef uint32_t ip_addr_t;
|
||||||
|
#define IP_IS_V4(p) ((void)(p), 1)
|
||||||
|
static char *ipaddr_ntoa_r(const ip_addr_t *ip,char *out,int n) {
|
||||||
|
(void)ip; assert(n>=10); strcpy(out,"127.0.0.1"); return out;
|
||||||
|
}
|
||||||
|
static size_t strlcpy(char *out,const char *in,size_t n) {
|
||||||
|
size_t len=strlen(in); if(n) { size_t copy=len<n-1?len:n-1; memcpy(out,in,copy); out[copy]=0; } return len;
|
||||||
|
}
|
||||||
|
static int resolve_ping_target(const char *host,ip_addr_t *ip,char *out,size_t n) {
|
||||||
|
(void)host; *ip=0; ipaddr_ntoa_r(ip,out,(int)n); return resolve_fail;
|
||||||
|
}
|
||||||
|
static const char *esp_err_to_name(int e) { (void)e; return "injected"; }
|
||||||
|
typedef void *esp_ping_handle_t;
|
||||||
|
typedef struct { uint32_t count; ip_addr_t target_addr; } esp_ping_config_t;
|
||||||
|
#define ESP_PING_DEFAULT_CONFIG() ((esp_ping_config_t){0})
|
||||||
|
typedef struct {
|
||||||
|
void *cb_args;
|
||||||
|
void (*on_ping_success)(esp_ping_handle_t,void *);
|
||||||
|
void (*on_ping_timeout)(esp_ping_handle_t,void *);
|
||||||
|
void (*on_ping_end)(esp_ping_handle_t,void *);
|
||||||
|
} esp_ping_callbacks_t;
|
||||||
|
enum { ESP_PING_PROF_SEQNO, ESP_PING_PROF_SIZE, ESP_PING_PROF_TIMEGAP,
|
||||||
|
ESP_PING_PROF_IPADDR, ESP_PING_PROF_TTL, ESP_PING_PROF_REQUEST,
|
||||||
|
ESP_PING_PROF_REPLY, ESP_PING_PROF_DURATION };
|
||||||
|
static esp_ping_callbacks_t callbacks;
|
||||||
|
static uint32_t probes, replies;
|
||||||
|
static uint16_t sequence;
|
||||||
|
static pthread_t worker;
|
||||||
|
static bool worker_started, all_timeouts, burst;
|
||||||
|
static int esp_ping_get_profile(esp_ping_handle_t h,int profile,void *out,size_t n) {
|
||||||
|
assert(h==(void *)1);
|
||||||
|
if (fail_profile) return ESP_FAIL;
|
||||||
|
if (profile==ESP_PING_PROF_SEQNO) { assert(n==2); memcpy(out,&sequence,n); }
|
||||||
|
else if(profile==ESP_PING_PROF_TTL) { assert(n==1); *(uint8_t *)out=64; }
|
||||||
|
else { assert(n==4); uint32_t value=profile==ESP_PING_PROF_REQUEST?probes:
|
||||||
|
profile==ESP_PING_PROF_REPLY?replies:1; memcpy(out,&value,n); }
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static int esp_ping_delete_session(esp_ping_handle_t h) {
|
||||||
|
assert(h==(void *)1); ++deletes; return fail_delete?ESP_FAIL:ESP_OK;
|
||||||
|
}
|
||||||
|
static void *produce(void *unused) {
|
||||||
|
(void)unused;
|
||||||
|
for(sequence=1;sequence<=probes;++sequence) {
|
||||||
|
if(all_timeouts || sequence%2==0) callbacks.on_ping_timeout((void *)1,callbacks.cb_args);
|
||||||
|
else callbacks.on_ping_success((void *)1,callbacks.cb_args);
|
||||||
|
}
|
||||||
|
callbacks.on_ping_end((void *)1,callbacks.cb_args); return NULL;
|
||||||
|
}
|
||||||
|
static int esp_ping_new_session(const esp_ping_config_t *c,const esp_ping_callbacks_t *cb,esp_ping_handle_t *h) {
|
||||||
|
assert(queue.bytes==memory.bytes && c->count>=1 && c->count<=20);
|
||||||
|
if(fail_new) return ESP_FAIL;
|
||||||
|
callbacks=*cb; probes=c->count; replies=all_timeouts?0:(probes+1)/2; *h=(void *)1; return ESP_OK;
|
||||||
|
}
|
||||||
|
static int esp_ping_start(esp_ping_handle_t h) {
|
||||||
|
assert(h==(void *)1); ++starts; if(fail_start) return ESP_FAIL;
|
||||||
|
assert(!pthread_create(&worker,NULL,produce,NULL)); worker_started=true;
|
||||||
|
if(burst) { assert(!pthread_join(worker,NULL)); worker_started=false; assert(queue.count==probes+1); }
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
static int execute_nslookup(int argc,char **argv) { (void)argc; (void)argv; return 0; }
|
||||||
|
static int execute_traceroute(int argc,char **argv) { (void)argc; (void)argv; return 0; }
|
||||||
|
typedef struct { const char *command,*help,*hint; int (*func)(int,char **); void *argtable; } esp_console_cmd_t;
|
||||||
|
static int esp_console_cmd_register(const esp_console_cmd_t *c) {
|
||||||
|
assert(c->func); ++registrations; return ESP_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/* Actual canonical handlers and database projection; included by accounts.py. */
|
||||||
|
static void psram_snapshot_tests(void)
|
||||||
|
{
|
||||||
|
reset(); remote=web=false;
|
||||||
|
fail_snapshot_alloc=true;
|
||||||
|
assert(user_console_register_commands()==ESP_OK && user_registrations==1);
|
||||||
|
assert(!snapshot_allocations);
|
||||||
|
assert(run("user status")==1);
|
||||||
|
assert(run("user list")==1);
|
||||||
|
assert(run("user show other")==1);
|
||||||
|
assert(snapshot_allocations==3 && s_user_snapshot==NULL && !gate_held);
|
||||||
|
assert(run("user add newcomer user")==0);
|
||||||
|
assert(snapshot_allocations==3);
|
||||||
|
s_initialized=false; s_mutex=NULL; storage_test=true;
|
||||||
|
assert(run("user recover --force")==0);
|
||||||
|
storage_test=false;
|
||||||
|
assert(snapshot_allocations==3);
|
||||||
|
reset(); remote=web=false;
|
||||||
|
fail_snapshot_alloc=false;
|
||||||
|
assert(run("user status")==0 && snapshot_allocations==4);
|
||||||
|
assert(all_zero(s_user_snapshot,sizeof(*s_user_snapshot)));
|
||||||
|
void *retained=s_user_snapshot;
|
||||||
|
fail_snapshot_alloc=true;
|
||||||
|
for (unsigned i=0;i<3;++i) {
|
||||||
|
assert(run("user")==0);
|
||||||
|
assert(all_zero(s_user_snapshot,sizeof(*s_user_snapshot)));
|
||||||
|
assert(run("user list")==0);
|
||||||
|
assert(all_zero(s_user_snapshot,sizeof(*s_user_snapshot)));
|
||||||
|
assert(run("user show other")==0);
|
||||||
|
assert(all_zero(s_user_snapshot,sizeof(*s_user_snapshot)));
|
||||||
|
assert(run("user show missing")==1);
|
||||||
|
assert(all_zero(s_user_snapshot,sizeof(*s_user_snapshot)));
|
||||||
|
s_initialized=false;
|
||||||
|
memset(s_user_snapshot,0xa5,sizeof(*s_user_snapshot));
|
||||||
|
assert(run("user status")==1);
|
||||||
|
assert(all_zero(s_user_snapshot,sizeof(*s_user_snapshot)));
|
||||||
|
s_initialized=true;
|
||||||
|
assert(s_user_snapshot==retained && snapshot_allocations==4 && !gate_held);
|
||||||
|
assert(snapshot_memory.before==UINT64_C(0xaabbccdd11223344));
|
||||||
|
assert(snapshot_memory.after==UINT64_C(0xaabbccdd11223344));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user