Move console payloads to lazy PSRAM storage
This commit is contained in:
@@ -138,8 +138,9 @@ static bool admin_ssh_console_dispatch_is_current(void) {
|
||||
if (checks==revoke_check) owner_current=false;
|
||||
return owner_current && user_database_principal_is_current(&actor, ¤t)==ESP_OK && current;
|
||||
}
|
||||
static int admin_command_gate_take(void) { return ESP_OK; }
|
||||
static void admin_command_gate_give(void) {}
|
||||
static bool gate_held;
|
||||
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,
|
||||
size_t min, size_t max, size_t *n) {
|
||||
(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;
|
||||
}
|
||||
/* 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_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",
|
||||
"initialize_dummy_verifier", "user_database_init", "user_database_recover_empty",
|
||||
"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",
|
||||
"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 += "\n".join(function(db, n) for n in db_names)
|
||||
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)
|
||||
account_tests = (ROOT / "tests/admin_console_boundary/accounts.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(' 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
|
||||
unit += account_tests
|
||||
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",
|
||||
str(path / "test.c"), str(IDF / "components/console/split_argv.c"),
|
||||
"-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 "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: 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)")
|
||||
|
||||
@@ -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 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 const admin_console_owner_t s_admin_console_owner;
|
||||
static void test_adapter(void)
|
||||
{
|
||||
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" };
|
||||
assert(admin_ssh_console_init()==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));
|
||||
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. */
|
||||
@@ -82,16 +83,19 @@ static void test_adapter(void)
|
||||
admin.username_length=1; assert(!admin_console_is_current(&token,&admin)); admin.username_length=5;
|
||||
token.transport=1; assert(!admin_console_drained(&token));
|
||||
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));
|
||||
assert(!admin_console_is_current(&token,&admin));
|
||||
assert(admin_console_perform(&token,ADMIN_SSH_DEFER_STOP,0)==ESP_ERR_NOT_FOUND && stopped==0);
|
||||
token.slot_generation=3;
|
||||
/* Same physical session can be assigned the other console slot. */
|
||||
admin_ssh_console_close(&token);
|
||||
token.slot_index=1;
|
||||
assert(admin_ssh_console_open(&token,&admin)==ESP_OK);
|
||||
active.console_slot_index=1; publish_slot(&active,0);
|
||||
admin_ssh_console_token_t other={ .session_id=8, .slot_generation=1 };
|
||||
assert(admin_ssh_console_open_available(&other,&admin,&s_admin_console_owner)==ESP_OK);
|
||||
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(s_console_slot_indices[0]==1);
|
||||
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_RESET,0)==ESP_OK && reset==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