feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
@@ -0,0 +1,56 @@
// Compile the real endpoint with the exact ESP-IDF cJSON implementation on a host.
const fs = require('node:fs'), path = require('node:path');
const src=fs.readFileSync(path.join(__dirname,'../main/ble_setup.c'),'utf8');
const jsonDir=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const jsonHeader=fs.readFileSync(path.join(jsonDir,'cJSON.h'),'utf8');
const jsonSource=fs.readFileSync(path.join(jsonDir,'cJSON.c'),'utf8').replace('#include "cJSON.h"','');
const globals=src.slice(src.indexOf('typedef struct { uint32_t version;'),src.indexOf('static void show('));
const endpoint=src.slice(src.indexOf('static bool valid_password('),src.indexOf('static void terminate_link('));
process.stdout.write(jsonHeader+'\n'+jsonSource+`
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
typedef int esp_err_t;
#define ESP_OK 0
#define ESP_ERR_INVALID_ARG 1
#define ESP_ERR_NO_MEM 2
#define portMAX_DELAY 0
static int state_mutex;
static int64_t now=1,window_end=300000000;
static int64_t esp_timer_get_time(void){return now;}
static void xSemaphoreTake(int m,int t){(void)m;(void)t;}
static void xSemaphoreGive(int m){(void)m;}
static void mbedtls_platform_zeroize(void *p,size_t n){memset(p,0,n);}
static void show(const char *s){(void)s;}
static const char *ble_factory_id(void){return "KP01-B43A451634A8";}
static const char *ble_factory_model(void){return "ECHOEAR-S3";}
`+globals+endpoint+`
static cJSON *run(const char *text){uint8_t *out=NULL;ssize_t len=0;assert(endpoint(0,(uint8_t*)text,strlen(text),&out,&len,NULL)==ESP_OK);assert(out&&len==(ssize_t)strlen((char*)out));cJSON *j=cJSON_Parse((char*)out);free(out);return j;}
static void status_is(const char *request,const char *expected){cJSON *r=run(request);assert(!strcmp(json_string(r,"status"),expected));cJSON_Delete(r);}
int main(void){
const char *wifi="{\\"cmd\\":\\"configure\\",\\"wifi_ssid\\":\\"Home\\",\\"wifi_password\\":\\"TestOnly123\\",\\"bind_token\\":\\"\\"}";
cJSON *r=run("{\\"cmd\\":\\"get_info\\"}");assert(cJSON_GetObjectItem(r,"protocol_version")->valueint==2);assert(!strcmp(json_string(r,"device_id"),ble_factory_id()));cJSON_Delete(r);
status_is("{\\"cmd\\":\\"get_status\\"}","idle");
status_is(wifi,"received");assert(job.version==2&&job.token[0]==0);int64_t deadline=confirm_until;
status_is(wifi,"received");assert(confirm_until==deadline);
status_is("{\\"cmd\\":\\"configure\\",\\"wifi_ssid\\":\\"Other\\",\\"wifi_password\\":\\"TestOnly123\\",\\"bind_token\\":\\"\\"}","failed");assert(!strcmp(job.ssid,"Home"));
now=confirm_until;status_is("{\\"cmd\\":\\"get_status\\"}","failed");
status_is(wifi,"received");state=SUCCESS;status_is(wifi,"success");state=FAILED;
status_is("{\\"cmd\\":\\"configure\\",\\"wifi_ssid\\":\\"Home\\",\\"wifi_password\\":\\"TestOnly123\\",\\"bind_token\\":\\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\"}","received");assert(strlen(job.token)==43);
status_is(wifi,"failed");assert(strlen(job.token)==43);state=FAILED;
status_is("{\\"cmd\\":\\"configure\\",\\"wifi_ssid\\":\\"Home\\",\\"wifi_password\\":\\"short\\",\\"bind_token\\":\\"\\"}","failed");
status_is("{\\"cmd\\":\\"configure\\",\\"wifi_ssid\\":\\"Home\\",\\"wifi_password\\":\\"TestOnly123\\",\\"bind_token\\":\\"short\\"}","failed");
const char *bad[]={"{\\"cmd\\":\\"get_info\\",\\"cmd\\":\\"get_info\\"}","{\\"cmd\\":\\"get_info\\",\\"extra\\":\\"x\\"}","{\\"cmd\\":\\"get_info\\"}{}","{\\"cmd\\":\\"unknown\\"}"};
for(size_t i=0;i<sizeof(bad)/sizeof(bad[0]);i++){uint8_t *out=NULL;ssize_t len=0;assert(endpoint(0,(uint8_t*)bad[i],strlen(bad[i]),&out,&len,NULL)==ESP_ERR_INVALID_ARG);}
state=FAILED;
const char *reset="{\\\"cmd\\\":\\\"factory_reset\\\",\\\"wifi_ssid\\\":\\\"Home\\\",\\\"wifi_password\\\":\\\"TestOnly123\\\",\\\"bind_token\\\":\\\"\\\"}";
status_is(reset,"received");assert(job.version==3&&job.press[0]==0&&job.token[0]==0);
status_is(reset,"received");status_is(wifi,"failed");assert(job.version==3);
r=run("{\\\"cmd\\\":\\\"get_info\\\"}");assert(cJSON_IsTrue(cJSON_GetObjectItem(r,"factory_reset_supported")));cJSON_Delete(r);
state=FAILED;
status_is("{\\\"cmd\\\":\\\"factory_reset\\\",\\\"wifi_ssid\\\":\\\"Home\\\",\\\"wifi_password\\\":\\\"TestOnly123\\\",\\\"bind_token\\\":\\\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\\\"}","failed");
now=window_end;status_is(wifi,"failed");status_is(reset,"failed");
puts("PASS: plaintext C endpoint: info, bind/WiFi-only, idempotency, conflict, retries, timeout, malformed input");return 0;
}
`.replace(/\\\\"/g,'\\"'));
@@ -0,0 +1,69 @@
// Test the actual physical confirmation and expired-window recovery functions.
const fs = require('node:fs'), path = require('node:path');
const src = fs.readFileSync(path.join(__dirname, '../main/ble_setup.c'), 'utf8');
const globals = src.slice(src.indexOf('typedef struct { uint32_t version;'), src.indexOf('static void show('));
const functions = src.slice(src.indexOf('bool ble_setup_confirm('), src.indexOf('static bool valid_password('));
process.stdout.write(`#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdatomic.h>
#define portMAX_DELAY 0
#define ESP_OK 0
#define ESP_FAIL 1
#define ESP_ERR_NVS_NOT_FOUND 2
#define NVS_READONLY 0
#define NVS_READWRITE 1
typedef int esp_err_t;
typedef int nvs_handle_t;
static int state_mutex,binding_task;
static atomic_bool enabled;
static int64_t now,window_end;
static int restarted,notified,setup_requested,commits;
static bool pending,storage_error;
static void xSemaphoreTake(int m,int t){(void)m;(void)t;}
static void xSemaphoreGive(int m){(void)m;}
static int64_t esp_timer_get_time(void){return now;}
static void ble_random_uuid(char *out){strcpy(out,"40000000-0000-4000-8000-000000000001");}
static void xTaskNotifyGive(int t){(void)t;notified++;}
static esp_err_t nvs_open(const char *name,int mode,nvs_handle_t *out){(void)mode;*out=!strcmp(name,"ble_setup")?1:2;return storage_error?ESP_FAIL:ESP_OK;}
static esp_err_t nvs_get_blob(nvs_handle_t n,const char *key,void *out,size_t *len){assert(n==1&&!strcmp(key,"pending")&&!out);*len=pending?100:0;return pending?ESP_OK:ESP_ERR_NVS_NOT_FOUND;}
static esp_err_t nvs_set_u8(nvs_handle_t n,const char *key,uint8_t value){assert(n==2&&!strcmp(key,"setup_requested"));setup_requested=value;return ESP_OK;}
static esp_err_t nvs_commit(nvs_handle_t n){assert(n==2);commits++;return ESP_OK;}
static void nvs_close(nvs_handle_t n){(void)n;}
static void show(const char *text){(void)text;}
static void esp_restart(void){restarted++;}
static const char *esp_err_to_name(esp_err_t err){(void)err;return "mock";}
static const char *TAG="ble_setup";
static void mock_log(const char *tag,const char *format,...){(void)tag;(void)format;}
#define ESP_LOGI(...) mock_log(__VA_ARGS__)
#define ESP_LOGE(...) mock_log(__VA_ARGS__)
` + globals + functions + `
static void reset(void){
memset(&job,0,sizeof(job));enabled=true;recover_saved_wifi=true;timeout_handled=false;
now=1;window_end=300000000;confirm_until=60000000;state=IDLE;failure_message="";
pending=storage_error=false;restarted=notified=commits=0;setup_requested=1;
}
int main(void){
reset();recover_wifi_after_timeout();assert(!restarted&&setup_requested==1);
now=window_end;recover_wifi_after_timeout();assert(restarted==1&&setup_requested==0&&commits==1);
recover_wifi_after_timeout();assert(restarted==1);
reset();state=RECEIVED;now=confirm_until;assert(ble_setup_confirm()&&state==FAILED&&!notified);
now=window_end;assert(!ble_setup_confirm());recover_wifi_after_timeout();assert(restarted==1);
reset();state=RECEIVED;assert(ble_setup_confirm()&&state==WIFI_CONNECTING&&notified==1&&strlen(job.press)==36);
now=window_end;recover_wifi_after_timeout();assert(!restarted&&setup_requested==1);
const state_t busy[]={RECEIVED,WIFI_CONNECTING,BINDING,SUCCESS};
for(size_t i=0;i<sizeof(busy)/sizeof(busy[0]);i++){
reset();state=busy[i];confirm_until=window_end+60000000;now=window_end;recover_wifi_after_timeout();assert(!restarted&&!timeout_handled&&setup_requested==1);
}
reset();pending=true;now=window_end;recover_wifi_after_timeout();assert(!restarted&&setup_requested==1);
reset();recover_saved_wifi=false;now=window_end;recover_wifi_after_timeout();assert(!restarted&&setup_requested==1);
reset();storage_error=true;now=window_end;recover_wifi_after_timeout();assert(!restarted&&setup_requested==1);
reset();enabled=false;now=window_end;recover_wifi_after_timeout();assert(!restarted&&!timeout_handled);
reset();state=BINDING;now=window_end;recover_wifi_after_timeout();assert(!restarted);
state=FAILED;recover_wifi_after_timeout();assert(restarted==1&&setup_requested==0);
puts("PASS production BLE: confirmation timeout/reopen, unattended window restores saved WiFi, pending/first-time/storage/busy guards, one restart");
return 0;
}
`);
@@ -0,0 +1,208 @@
// Host SDK shim: only SDK calls are mocked; the driver lifecycle under test is
// inserted verbatim from main/ble_wifi.c by ble-wifi-host.cjs.
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
typedef int esp_err_t;
#define ESP_OK 0
#define ESP_ERR_NO_MEM 1
#define ESP_ERR_INVALID_STATE 2
#define ESP_ERR_INVALID_ARG 3
#define ESP_ERR_TIMEOUT 4
#define ESP_FAIL 5
#define BIT0 1
#define BIT1 2
#define BIT2 4
#define pdFALSE 0
#define pdMS_TO_TICKS(x) (x)
#define MALLOC_CAP_INTERNAL 1
#define ESP_EVENT_ANY_ID (-1)
#define WIFI_EVENT 1
#define IP_EVENT 2
#define IP_EVENT_STA_GOT_IP 1
#define WIFI_EVENT_STA_DISCONNECTED 2
#define WIFI_EVENT_STA_STOP 3
#define WIFI_STORAGE_RAM 1
#define WIFI_MODE_STA 1
#define WIFI_IF_STA 0
#define WIFI_AUTH_WPA2_PSK 3
#define WIFI_ALL_CHANNEL_SCAN 1
#define WIFI_CONNECT_AP_BY_SIGNAL 1
typedef int esp_event_base_t;
typedef int *esp_event_handler_instance_t;
typedef unsigned EventBits_t;
typedef EventBits_t *EventGroupHandle_t;
typedef struct { int placeholder; } esp_netif_t;
typedef struct { int placeholder; } esp_netif_config_t;
#define ESP_NETIF_DEFAULT_WIFI_STA() ((esp_netif_config_t){0})
typedef struct { unsigned reason; } wifi_event_sta_disconnected_t;
typedef struct {
int static_rx_buf_num, dynamic_rx_buf_num, static_tx_buf_num, cache_tx_buf_num;
int ampdu_rx_enable, ampdu_tx_enable, amsdu_tx_enable;
} wifi_init_config_t;
#define WIFI_INIT_CONFIG_DEFAULT() ((wifi_init_config_t){16,32,16,32,1,1,0})
typedef struct { struct {
char ssid[32], password[64];
struct { int authmode; } threshold;
int scan_method, sort_method;
} sta; } wifi_config_t;
static void mock_log(const char *tag, const char *format, ...) {(void)tag; (void)format;}
#define ESP_LOGI(...) mock_log(__VA_ARGS__)
#define ESP_LOGW(...) mock_log(__VA_ARGS__)
#define ESP_LOGE(...) mock_log(__VA_ARGS__)
static const char *esp_err_to_name(esp_err_t err) {(void)err; return "mock";}
static unsigned heap_caps_get_free_size(int cap) {(void)cap; return 50000;}
static unsigned heap_caps_get_largest_free_block(int cap) {(void)cap; return 30000;}
enum point { NONE, GROUP, NEW_NETIF, ATTACH, DEFAULT_HANDLERS, DRIVER,
WIFI_HANDLER, IP_HANDLER, STORAGE, MODE, CONFIG, START, CONNECT, STOP, POINTS };
static int calls[POINTS], fail_point, failures, destroys, foreign;
static bool should_connect, emit_disconnect, stop_ack;
static EventBits_t bits;
static esp_netif_t mock_station, other_station;
static int handler_handles[2];
static wifi_config_t last_config;
static void (*callback)(void *, esp_event_base_t, int32_t, void *);
static bool fail(enum point point) {
calls[point]++;
if (fail_point == (int)point && failures) {failures--; return true;}
return false;
}
static EventGroupHandle_t xEventGroupCreate(void) {return fail(GROUP) ? NULL : &bits;}
static EventBits_t xEventGroupGetBits(EventGroupHandle_t e) {assert(e); return *e;}
static EventBits_t xEventGroupSetBits(EventGroupHandle_t e, EventBits_t b) {return *e |= b;}
static EventBits_t xEventGroupClearBits(EventGroupHandle_t e, EventBits_t b) {EventBits_t old=*e; *e &= ~b; return old;}
static EventBits_t xEventGroupWaitBits(EventGroupHandle_t e, EventBits_t b, int clear, int all, int ticks) {
(void)b; (void)clear; (void)all; (void)ticks; return *e;
}
static void vTaskDelay(int ticks) {(void)ticks;}
static esp_netif_t *esp_netif_get_handle_from_ifkey(const char *key) {
assert(!strcmp(key, "WIFI_STA")); return foreign ? &other_station : NULL;
}
static esp_netif_t *esp_netif_new(const esp_netif_config_t *c) {
(void)c; return fail(NEW_NETIF) ? NULL : &mock_station;
}
static esp_err_t esp_netif_attach_wifi_station(esp_netif_t *n) {assert(n); return fail(ATTACH) ? ESP_FAIL : ESP_OK;}
static esp_err_t esp_wifi_set_default_wifi_sta_handlers(void) {return fail(DEFAULT_HANDLERS) ? ESP_FAIL : ESP_OK;}
static void esp_netif_destroy_default_wifi(esp_netif_t *n) {assert(n == &mock_station); destroys++;}
static esp_err_t esp_wifi_init(const wifi_init_config_t *c) {
assert(c->static_rx_buf_num == 4 && c->static_tx_buf_num == 4);
assert(c->dynamic_rx_buf_num >= c->static_rx_buf_num && c->cache_tx_buf_num == 4);
assert(!c->ampdu_rx_enable && !c->ampdu_tx_enable && !c->amsdu_tx_enable);
return fail(DRIVER) ? ESP_ERR_NO_MEM : ESP_OK;
}
static esp_err_t esp_event_handler_instance_register(esp_event_base_t base, int id,
void (*cb)(void *, esp_event_base_t, int32_t, void *), void *arg, esp_event_handler_instance_t *instance) {
(void)id; (void)arg;
if (fail(base == WIFI_EVENT ? WIFI_HANDLER : IP_HANDLER)) return ESP_ERR_NO_MEM;
callback = cb; *instance = &handler_handles[base == WIFI_EVENT ? 0 : 1]; return ESP_OK;
}
static esp_err_t esp_wifi_set_storage(int storage) {assert(storage == WIFI_STORAGE_RAM); return fail(STORAGE) ? ESP_FAIL : ESP_OK;}
static esp_err_t esp_wifi_set_mode(int mode) {assert(mode == WIFI_MODE_STA); return fail(MODE) ? ESP_FAIL : ESP_OK;}
static esp_err_t esp_wifi_set_config(int iface, const wifi_config_t *c) {
assert(iface == WIFI_IF_STA); last_config = *c; return fail(CONFIG) ? ESP_FAIL : ESP_OK;
}
static esp_err_t esp_wifi_start(void) {return fail(START) ? ESP_ERR_NO_MEM : ESP_OK;}
static esp_err_t esp_wifi_connect(void) {
if (fail(CONNECT)) return ESP_FAIL;
if (should_connect) callback(NULL, IP_EVENT, IP_EVENT_STA_GOT_IP, NULL);
else if (emit_disconnect) {wifi_event_sta_disconnected_t e = {201}; callback(NULL, WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &e);}
return ESP_OK;
}
static esp_err_t esp_wifi_stop(void) {
if (fail(STOP)) return ESP_FAIL;
if (stop_ack) callback(NULL, WIFI_EVENT, WIFI_EVENT_STA_STOP, NULL);
return ESP_OK;
}
/* PRODUCTION_SOURCE */
static void reset(void) {
station=NULL; events=NULL; wifi_handler=NULL; ip_handler=NULL;
driver_ready=false; prepared=false; started=false; stopping=false;
memset(calls,0,sizeof(calls)); memset(&last_config,0,sizeof(last_config));
fail_point=NONE; failures=0; destroys=0; foreign=0; bits=0;
should_connect=true; emit_disconnect=false; stop_ack=true; callback=NULL;
}
int main(void) {
unsigned tests=0;
reset();
assert(!ble_wifi_connected());
assert(ble_wifi_prepare()==ESP_OK && ble_wifi_prepare()==ESP_OK);
assert(calls[DRIVER]==1 && calls[NEW_NETIF]==1 && calls[GROUP]==1);
assert(calls[START]==0 && calls[CONNECT]==0); tests++;
// Every initialization failure is recoverable, without duplicate owners.
for (enum point p=GROUP; p<=MODE; ++p) {
reset(); fail_point=p; failures=1;
assert(ble_wifi_prepare()!=ESP_OK);
assert(ble_wifi_prepare()==ESP_OK);
assert(ble_wifi_prepare()==ESP_OK);
assert(calls[NEW_NETIF] <= ((p==NEW_NETIF || p==ATTACH || p==DEFAULT_HANDLERS) ? 2 : 1));
assert(calls[GROUP] == (p==GROUP ? 2 : 1));
assert(calls[DRIVER] == (p==DRIVER ? 2 : 1));
assert(destroys == (p==ATTACH || p==DEFAULT_HANDLERS ? 1 : 0));
tests++;
}
reset(); fail_point=DRIVER; failures=10;
for (int i=0; i<10; ++i) assert(ble_wifi_prepare()==ESP_ERR_NO_MEM);
assert(ble_wifi_prepare()==ESP_OK);
assert(calls[NEW_NETIF]==1 && calls[GROUP]==1 && calls[DEFAULT_HANDLERS]==1); tests++;
reset(); foreign=1;
assert(ble_wifi_prepare()==ESP_ERR_INVALID_STATE);
assert(calls[NEW_NETIF]==0 && destroys==0); tests++;
reset(); assert(ble_wifi_connect("test-network","test-password")==ESP_OK);
assert(ble_wifi_connected());
assert(!strcmp(last_config.sta.ssid,"test-network"));
assert(!strcmp(last_config.sta.password,"test-password"));
assert(last_config.sta.threshold.authmode==WIFI_AUTH_WPA2_PSK);
assert(ble_wifi_connect("new-network","new-password")==ESP_OK);
assert(calls[DRIVER]==1 && calls[NEW_NETIF]==1 && calls[STOP]==1);
assert(!strcmp(last_config.sta.ssid,"new-network")); tests++;
for (enum point p=CONFIG; p<=CONNECT; ++p) {
reset(); fail_point=p; failures=3;
while(failures) assert(ble_wifi_connect("test-network","test-password")!=ESP_OK);
assert(ble_wifi_connect("test-network","test-password")==ESP_OK);
assert(calls[DRIVER]==1 && calls[NEW_NETIF]==1 && calls[GROUP]==1); tests++;
}
for (int disconnect=0; disconnect<2; ++disconnect) {
reset(); should_connect=false; emit_disconnect=disconnect;
assert(ble_wifi_connect("test-network","test-password")==ESP_ERR_TIMEOUT);
assert(!ble_wifi_connected() && calls[START]==3 && calls[STOP]==3);
should_connect=true;
assert(ble_wifi_connect("test-network","test-password")==ESP_OK);
assert(calls[NEW_NETIF]==1 && calls[DRIVER]==1); tests++;
}
reset(); assert(ble_wifi_connect("test-network","test-password")==ESP_OK);
wifi_event_sta_disconnected_t e={201};
callback(NULL,WIFI_EVENT,WIFI_EVENT_STA_DISCONNECTED,&e);
assert(!ble_wifi_connected()); tests++;
reset(); assert(ble_wifi_connect("test-network","test-password")==ESP_OK);
fail_point=STOP; failures=1;
assert(ble_wifi_connect("test-network","test-password")==ESP_FAIL);
assert(ble_wifi_connect("test-network","test-password")==ESP_OK);
assert(calls[DRIVER]==1); tests++;
reset(); assert(ble_wifi_connect("test-network","test-password")==ESP_OK);
stop_ack=false;
assert(ble_wifi_connect("test-network","test-password")==ESP_ERR_TIMEOUT);
assert(ble_wifi_connect("test-network","test-password")==ESP_ERR_TIMEOUT);
assert(calls[STOP]==1);
assert(calls[CONNECT]==1);
callback(NULL,WIFI_EVENT,WIFI_EVENT_STA_STOP,NULL); stop_ack=true;
assert(ble_wifi_connect("test-network","test-password")==ESP_OK); tests++;
reset(); assert(ble_wifi_connect(NULL,"password")==ESP_ERR_INVALID_ARG);
assert(ble_wifi_connect("","password")==ESP_ERR_INVALID_ARG);
assert(ble_wifi_connect("network",NULL)==ESP_ERR_INVALID_ARG);
assert(calls[NEW_NETIF]==0); tests++;
printf("PASS: %u Wi-Fi lifecycle/fault-injection cases (production C)\n",tests);
return 0;
}
@@ -0,0 +1,9 @@
// Emit a host test translation unit using the actual production C functions.
// Usage: node tests/ble-wifi-host.cjs | cc -x c -std=c11 -Wall -Wextra -Werror \
// -fsanitize=address,undefined -o /tmp/ble-wifi-test -
const fs = require('node:fs');
const path = require('node:path');
const source = fs.readFileSync(path.join(__dirname, '../main/ble_wifi.c'), 'utf8');
const harness = fs.readFileSync(path.join(__dirname, 'ble-wifi-host.c'), 'utf8');
process.stdout.write(harness.replace('/* PRODUCTION_SOURCE */',
source.replace(/^#include[^\n]*$/gm, '') + '\n'));
@@ -0,0 +1,154 @@
// Executes the actual device adapter against deterministic transport/NVS/clock
// doubles. These tests are NOT supplier release evidence or hardware acceptance.
const fs=require('node:fs'),path=require('node:path');
const base=path.join(__dirname,'..');
const jsonDir=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const strip=s=>s.replace(/^#include[^\n]*$/gm,'').replace(/^#pragma once/gm,'');
const sdk=strip(fs.readFileSync(path.join(base,'components/volc_conv_ai/src/base/volc_device_manager.h'),'utf8'));
const version=strip(fs.readFileSync(path.join(base,'main/firmware_version.h'),'utf8'));
process.stdout.write(fs.readFileSync(path.join(jsonDir,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(jsonDir,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+`
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdatomic.h>
#include <sys/time.h>
typedef int esp_err_t;
typedef int nvs_handle_t;
typedef int StaticSemaphore_t;
typedef int SemaphoreHandle_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_NO_MEM 2
#define ESP_ERR_INVALID_STATE 3
#define ESP_ERR_INVALID_RESPONSE 4
#define ESP_ERR_INVALID_SIZE 5
#define ESP_ERR_TIMEOUT 6
#define ESP_ERR_INVALID_ARG 7
#define ESP_ERR_NVS_NOT_FOUND 7
#define NVS_READWRITE 1
#define NVS_READONLY 0
#define pdTRUE 1
#define pdPASS 1
#define portMAX_DELAY 0
#define pdMS_TO_TICKS(x) (x)
#define ESP_LOGI(...) ((void)0)
static int64_t test_us=1000000;
static int64_t esp_timer_get_time(void){return test_us;}
static int xSemaphoreCreateMutexStatic(void *p){return 1;}
static int xSemaphoreTake(int m,int t){return pdTRUE;}
static void xSemaphoreGive(int m){}
static void vTaskDelay(int t){}
static int xTaskCreate(void(*f)(void*),const char*n,int stack,void*a,int priority,void*out){return pdPASS;}
static int nvs_open(const char *s,int mode,int *n){*n=1;return 0;}
static int credential_writes, storage_error; static unsigned char saved_record[2048]; static size_t saved_len;
static int nvs_set_blob(int n,const char *key,const void *data,size_t len){if(!strcmp(key,"credential")){credential_writes++;if(!storage_error){assert(len<=sizeof(saved_record));memcpy(saved_record,data,len);saved_len=len;}}return 0;}
static int nvs_get_blob(int n,const char *key,void *data,size_t *len){if(!strcmp(key,"credential")&&saved_len==*len){memcpy(data,saved_record,saved_len);return 0;}return ESP_ERR_NVS_NOT_FOUND;}
static int nvs_erase_key(int n,const char *key){return 0;}
static int nvs_commit(int n){return storage_error;}
static void nvs_close(int n){}
static void mbedtls_platform_zeroize(void*p,size_t n){memset(p,0,n);}
static void mbedtls_sha256(const unsigned char*p,size_t n,unsigned char*out,int x){memset(out,0,32);for(size_t i=0;i<n;i++)out[i%32]^=p[i];}
static bool ble_cloud_configured(void){return true;}
static const char*ble_factory_id(void){return "KP01-000000000001";}
static const char*ble_factory_model(void){return "ECHOEAR-S3";}
static const char*ble_cloud_boot_id(void){return "11111111-1111-4111-8111-111111111111";}
static const char*ble_cloud_challenge_id(void){return ble_cloud_boot_id();}
static int ble_cloud_sync_time(void){return 0;}
static char *test_config;
static int config_mode, imported, supplier_calls;
static char *ble_cloud_decode(const char*d,const char*e,const char*s){return test_config?strdup(test_config):NULL;}
static unsigned uuid_counter;
static void ble_random_uuid(char out[37]){snprintf(out,37,"11111111-1111-4111-8111-%012u",++uuid_counter);}
`+sdk+'\n'+version+`
int volc_device_register(volc_iot_info_t*i,char**o){supplier_calls++;return -1;}
`+strip(fs.readFileSync(path.join(base,'main/platform_client.h'),'utf8'))+'\n'+strip(fs.readFileSync(path.join(base,'main/call_client.h'),'utf8'))+`
static int response_mode, requests;
static char *last_body;
static char last_id[37];
static int64_t request_advance;
const char *platform_last_error_code(void) {return response_mode==2 ? "CHAT_ENDED" : "";}
esp_err_t platform_request(const char*method,const char*path,const char*body,const char*rid,char**out){
requests++; free(last_body);last_body=strdup(body);snprintf(last_id,37,"%s",rid?rid:"");
if(response_mode) return ESP_FAIL;
cJSON *j=cJSON_Parse(body),*root=cJSON_CreateObject(),*data=cJSON_AddObjectToObject(root,"data");cJSON_AddBoolToObject(root,"ok",true);
assert(!strstr(path,"/voice-runtime")&&!strstr(path,"/activation-")&&!strstr(path,"/device/register"));
if(strstr(path,"/ble-state"))cJSON_AddBoolToObject(data,"bound",config_mode!=4);
if(strstr(path,"/voice-config")){
imported++;
cJSON_AddStringToObject(data,"voice_status",config_mode==1?"preparing":"ready");
cJSON *iot=cJSON_AddObjectToObject(data,"voice");
cJSON_AddStringToObject(iot,"instance_id","instance");cJSON_AddStringToObject(iot,"product_key","product");
cJSON_AddStringToObject(iot,"device_name",ble_factory_id());cJSON_AddStringToObject(iot,"device_secret","fixture-device-secret-123456789");
if(config_mode!=3)cJSON_AddStringToObject(iot,"RTCAppID","rtc-app");
}
if(strstr(path,"/chat/start")){
struct timeval tv;gettimeofday(&tv,NULL);int64_t now=(int64_t)tv.tv_sec*1000+tv.tv_usec/1000+2000;
cJSON_AddNumberToObject(root,"server_time_ms",now);
cJSON_AddStringToObject(data,"session_id",cJSON_GetStringValue(cJSON_GetObjectItem(j,"session_id")));
cJSON_AddStringToObject(data,"bot_id","bot");cJSON_AddObjectToObject(data,"params");
cJSON_AddNumberToObject(data,"lease_expires_at_ms",now+45000);cJSON_AddNumberToObject(data,"expires_at_ms",now+3600000);
test_us+=request_advance*1000;
}
if(out)*out=cJSON_PrintUnformatted(root);cJSON_Delete(j);cJSON_Delete(root);return 0;
}
`+strip(fs.readFileSync(path.join(base,'main/call_client.c'),'utf8'))+`
static void reset(void){
free(params);params=NULL;memset(&call,0,sizeof(call));call.version=2;
snprintf(call.session,37,"%s",ble_cloud_boot_id());snprintf(call.boot,37,"%s",ble_cloud_boot_id());
snprintf(call.end_id,37,"%s",ble_cloud_boot_id());test_us=1000000;
atomic_store(&audio_allowed,true);atomic_store(&lease_deadline,mono()+45000);
response_mode=requests=0;request_advance=0;state_mutex=1;engine_ready=true;sdk_stopped=false;
}
int main(void){
cJSON *bound=cJSON_CreateObject();
cJSON_AddStringToObject(bound,"instance_id","instance");cJSON_AddStringToObject(bound,"product_key","product");
cJSON_AddStringToObject(bound,"device_name","device");cJSON_AddStringToObject(bound,"device_secret","fixture-device-secret-123456789");
assert(call_client_save_bind_voice(bound)==ESP_ERR_INVALID_RESPONSE);assert(credential_writes==0);
cJSON_AddStringToObject(bound,"RTCAppID","rtc-app");
assert(call_client_save_bind_voice(bound)==ESP_OK);assert(credential_writes==1);
storage_error=ESP_FAIL;assert(call_client_save_bind_voice(bound)==ESP_FAIL);storage_error=0;cJSON_Delete(bound);
reset();assert(call_client_audio_allowed());test_us+=45000000;assert(!call_client_audio_allowed());
reset();assert(volc_platform_recording_prepare("app","room","device")==ESP_OK);
assert(strstr(last_body,"rtc_app_id")&&strstr(last_body,"room_id")&&strstr(last_body,"rtc_user_id"));
assert(!strstr(last_body,"device_secret")&&!strstr(last_body,"audio"));
int recorded_requests=requests;call.finished=true;
assert(volc_platform_recording_prepare("app","room","device")==ESP_ERR_INVALID_STATE);assert(requests==recorded_requests);
reset();response_mode=1;assert(volc_platform_recording_prepare("app","room","device")!=ESP_OK);assert(call_client_audio_allowed());
reset();assert(fetch_parameters(true)==0);assert(params && !strcmp(bot,"bot"));
reset();request_advance=46000;assert(fetch_parameters(false)!=0);assert(!call_client_audio_allowed());
reset();int before=requests;assert(call_client_turn("subject","1","private text")==0);assert(requests==before);
reset();call.finished=true;call.sdk_stopped=true;strcpy(call.reason,"hangup");response_mode=1;
finish_pending();assert(call.session[0]);char *original=strdup(last_body);char rid[37];strcpy(rid,last_id);
response_mode=0;finish_pending();assert(!call.session[0]);assert(!strcmp(original,last_body)&&!strcmp(rid,last_id));free(original);
reset();response_mode=2;renew_due=0;assert(call_client_poll()==1);assert(!call_client_audio_allowed());
reset();memset(&call,0,sizeof(call));response_mode=1;assert(call_client_begin()!=0);assert(call.finished&&call.session[0]);
response_mode=0;finish_pending();assert(!call.session[0]);
reset();memset(&call,0,sizeof(call));memset(&voice,0,sizeof(voice));boot_loaded=false;config_mode=0;imported=0;
assert(call_client_boot()==ESP_OK);assert(!strcmp(voice.rtc_app,"rtc-app"));assert(imported==0);assert(supplier_calls==0);
before=requests;call_client_engine_ready(true);assert(engine_ready);assert(requests==before);
config_mode=4;assert(call_client_boot()==ESP_ERR_INVALID_STATE);assert(!strcmp(call_client_start_error(),"DEVICE_UNBOUND"));assert(!call_client_audio_allowed());
config_mode=0;memset(&voice,0,sizeof(voice));saved_len=0;boot_loaded=false;
assert(call_client_boot()==ESP_OK);assert(imported==1);assert(supplier_calls==0);
memset(&voice,0,sizeof(voice));saved_len=0;boot_loaded=false;config_mode=1;
assert(call_client_boot()==ESP_ERR_INVALID_STATE);assert(!strcmp(call_client_start_error(),"VOICE_PREPARING"));
config_mode=3;assert(call_client_boot()==ESP_ERR_INVALID_RESPONSE);assert(supplier_calls==0);
config_mode=0;assert(call_client_boot()==ESP_OK);
// A disruptive operation atomically excludes a new call, including a race
// between command delivery and the user's next screen tap.
memset(&call,0,sizeof(call));call_client_engine_ready(true);
assert(!call_client_busy());assert(call_client_enter_maintenance());
assert(call_client_busy());assert(call_client_begin()==ESP_ERR_INVALID_STATE);
assert(call_client_set_standby(true)==ESP_OK);
call_client_leave_maintenance();assert(!call_client_busy());
assert(call_client_standby());assert(call_client_begin()==ESP_ERR_INVALID_STATE);
assert(call_client_set_standby(false)==ESP_ERR_INVALID_STATE);
assert(call_client_enter_maintenance());assert(call_client_set_standby(false)==ESP_OK);
call_client_leave_maintenance();assert(!call_client_standby());
strcpy(call.session,"11111111-1111-4111-8111-111111111111");
assert(call_client_busy());assert(!call_client_enter_maintenance());
free(test_config);test_config=NULL;
reset();free(params);free(last_body);puts("PASS direct chat C: bind credential persistence, cached boot, recovery only, no activation handshake, parameter fetch, stable end retry, unbound refusal and lease expiry");return 0;
}
`);
@@ -0,0 +1,243 @@
// Emit a standalone host TU containing the actual production state machine,
// SDK event/status callbacks and conversation task. No device or cloud access.
const fs = require('node:fs'), path = require('node:path');
const root = path.resolve(__dirname, '..');
const inc = fs.readFileSync(path.join(root, 'components/application/service/conv_service/inc/call_display_state.h'), 'utf8');
const state = fs.readFileSync(path.join(root, 'components/application/service/conv_service/src/call_display_state.c'), 'utf8').replace('#include "call_display_state.h"', '');
const task = fs.readFileSync(path.join(root, 'components/application/service/conv_service/src/volc_conv.c'), 'utf8');
const voiceHeader = fs.readFileSync(path.join(root, 'components/application/service/conv_service/inc/call_voice_end.h'), 'utf8').replace('#pragma once', '');
const voiceState = fs.readFileSync(path.join(root, 'components/application/service/conv_service/src/call_voice_end.c'), 'utf8').replace('#include "call_voice_end.h"', '');
function section(first, next) {
const start = task.indexOf(first), end = task.indexOf(next, start + first.length);
if (start < 0 || end < 0) throw new Error(`production section missing: ${first}`);
return task.slice(start, end);
}
process.stdout.write(inc + '\n' + state + '\n' + voiceHeader + '\n' + voiceState + '\n' + String.raw`
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdatomic.h>
typedef void *volc_engine_t;
typedef void *volc_hal_display_t;
typedef void *volc_hal_capture_t;
typedef struct {int code;} volc_event_t;
typedef enum {VOLC_CONV_STATUS_LISTENING,VOLC_CONV_STATUS_THINKING,VOLC_CONV_STATUS_ANSWERING,VOLC_CONV_STATUS_INTERRUPTED,VOLC_CONV_STATUS_ANSWER_FINISH} volc_conv_status_e;
typedef struct {void *display_handle,*capture_handle[1],*player_handle[1];} volc_hal_context_t;
typedef struct {int media_type;void (*data_cb)(void);void (*audio_wakeup_cb)(void*,void*);void *user_data;} volc_hal_capture_config_t;
typedef struct {int type;} esp_gmf_afe_evt_t;
#define ESP_GMF_AFE_EVT_VAD_START 1
#define ESP_GMF_AFE_EVT_VAD_END 2
typedef struct {int media_type;} volc_hal_player_config_t;
typedef struct {int mode;char *bot_id,*params;} volc_opt_t;
typedef struct {bool is_binary;} volc_message_info_t;
typedef struct {void *engine;int wait_time;} volc_conv_service_t;
#define VOLC_EV_CONNECTED 1
#define VOLC_EV_DISCONNECTED 2
#define VOLC_DISPLAY_OBJ_STATUS 0
#define VOLC_DISPLAY_OBJ_MAIN 1
#define VOLC_DISPLAY_OBJ_SUBTITLE 2
#define VOLC_DISPLAY_TEXT 0
#define VOLC_DISPLAY_IMAGE 1
#define MMAP_EAF_HAPPY_EAF 0
#define MMAP_EAF_EMOTION_BLINK_FAST_284_126_EAF 1
#define MMAP_EAF_EMOTION_DIZZY_284_126_EAF 2
#define VOLC_HAL_PLAYER_AUDIO 0
#define VOLC_HAL_CAPTURE_AUDIO 0
#define VOLC_MEDIA_TYPE_AUDIO 0
#define VOLC_AUDIO_MODE_CAPTURE 0
#define VOLC_AUDIO_MODE_WAKEUP 1
#define VOLC_MODE_RTC 1
#define VOLC_MODE_WS 2
#define VOLC_AGENT_TYPE_LLM 0
#define VOLC_SERVICE_AI_CONVERSATION_OVER 1
#define CONV_SERVICE_TIMEOUT 60
#define LOGI(...) do { if (0) printf(__VA_ARGS__); } while (0)
#define LOGE(...) do { if (0) printf(__VA_ARGS__); } while (0)
#define pdMS_TO_TICKS(value) (value)
enum {NORMAL,DELAYED_CONNECTION,START_FAILURE,BEGIN_FAILURE,REMOTE_DROP,SLOW_END_ACK,AUDIO_STOP_FAILURE,RESPONSE_FINISHED,TIMEOUT_WITHOUT_CONNECTION,IDLE_CONNECTED,ACTIVE_LONG_CALL,VOICE_GOODBYE,VOICE_RETRACT,VOICE_MISSING_REPLY};
static int scenario,polls,frames,ended_frames,interrupted_frames,failed_frames,ending_frames,talking_frames,over_count;
static uint64_t now_ms;
static char text[64];
static bool audio_stopped,output_stopped,sdk_stopped;
static atomic_bool is_stop=true,stop_requested=false,provider_disconnected=false,rtc_connected=false,display_connected=false,user_stop_requested=false;
static call_display_state_t call_display;
static volc_hal_display_t global_display;
static bool session_started_reported,diagnostic_say_pending;
static call_voice_end_t voice_end;
static atomic_uint voice_finish_serial,last_voice_received_ms;
typedef struct {double valuedouble;} cJSON;
static cJSON *cJSON_GetObjectItemCaseSensitive(const cJSON *j,const char *k){(void)j;(void)k;return NULL;}
static const char *cJSON_GetStringValue(const cJSON *j){(void)j;return NULL;}
static bool cJSON_IsNumber(const cJSON *j){return j!=NULL;}
static bool cJSON_IsTrue(const cJSON *j){(void)j;return false;}
static void *platform_report_queue;
static int xQueueReceive(void *q,cJSON **out,unsigned ticks){(void)q;(void)out;(void)ticks;return 0;}
static void cJSON_Delete(cJSON *root){(void)root;}
#define pdTRUE 1
enum { DIAGNOSTIC_NONE, DIAGNOSTIC_SAY, DIAGNOSTIC_TTS, DIAGNOSTIC_PENDING_MASK=3 };
static atomic_uint diagnostic_state;
static void diagnostic_prepare(void){}
static void diagnostic_close(void){}
static unsigned diagnostic_take_pending(void){return 0;}
static void diagnostic_send_tts(void){}
static unsigned uxTaskPriorityGet(void *task){(void)task;return 5;}
static int xTaskGetCoreID(void *task){(void)task;return 0;}
static volc_conv_service_t conv_service;
static volc_hal_context_t hal={(void*)1,{(void*)2},{(void*)3}};
static void __on_volc_event(volc_engine_t,volc_event_t*,void*);
static void __on_volc_conversation_status(volc_engine_t,volc_conv_status_e,void*);
void conv_ai_service_task_stop(void);
static uint64_t volc_osal_get_time_ms(void){return now_ms;}
` + section('/* CALL_IDLE_BEGIN:', '/* CALL_IDLE_END */') + String.raw`
static void vTaskDelay(unsigned ms){now_ms+=ms;}
static void sleep(unsigned seconds){now_ms+=seconds*1000;}
static void volc_osal_thread_exit(void *unused){(void)unused;}
static void *volc_osal_malloc(size_t size){return malloc(size);}
static void volc_osal_free(void *p){free(p);}
static volc_hal_context_t *volc_get_global_hal_context(void){return &hal;}
static int volc_hal_display_set_content(void *display,int obj,int type,const void *content){
(void)display;(void)type;
if(obj!=VOLC_DISPLAY_OBJ_STATUS)return 0;
snprintf(text,sizeof(text),"%s",(const char*)content);frames++;
if(!strcmp(text,"通话中")){assert(atomic_load(&display_connected));talking_frames++;}
if(!strcmp(text,"正在结束"))ending_frames++;
if(!strcmp(text,"通话结束")||!strcmp(text,"连接失败")||!strcmp(text,"连接中断")){
assert(audio_stopped&&output_stopped); // never claim ended before local shutdown
if(!strcmp(text,"通话结束"))ended_frames++;
if(!strcmp(text,"连接失败"))failed_frames++;
if(!strcmp(text,"连接中断"))interrupted_frames++;
}
return 0;
}
static void platform_conversation_observe(const char *type){(void)type;}
static bool platform_runtime_rtc(void){return true;}
static bool platform_conversation_audio_authorized(void){return true;}
static int platform_conversation_begin(void){return scenario==BEGIN_FAILURE?-1:0;}
static const char *platform_last_error_code(void){return "";}
static const char *call_client_start_error(void){return "";}
static const char *platform_conversation_update_json(void){return "{}";}
static const char *platform_conversation_bot_id(void){return "test";}
static const char *volc_current_user_id(void *engine,bool remote){(void)engine;(void)remote;return "test";}
static void process_platform_reports(void){}
static void platform_conversation_stop_result(bool started,int result){(void)started;(void)result;}
static void platform_conversation_finish(bool failed,const char *error){
(void)failed;(void)error;
if(scenario==AUDIO_STOP_FAILURE){assert(!strcmp(text,"正在结束"));return;}
assert(ended_frames+failed_frames+interrupted_frames>0); // visible before HTTP ACK
assert(sdk_stopped);
if(scenario==SLOW_END_ACK)now_ms+=5000;
}
static bool conversation_output_allowed(void){return !stop_requested;}
static bool volc_hal_player_voice_quiet(void *player,unsigned ms){(void)player;(void)ms;return true;}
` + section('/* Called only by the conversation task,', '/* HTTPS reporting') + String.raw`
static void __audio_capture_cb(void){}
static void *volc_hal_capture_create(const volc_hal_capture_config_t *cfg){(void)cfg;return (void*)2;}
static int volc_hal_capture_stop(void *capture){(void)capture;if(stop_requested&&scenario==AUDIO_STOP_FAILURE)return -1;audio_stopped=true;return 0;}
static int volc_hal_capture_update_config(void *capture,const volc_hal_capture_config_t *cfg){(void)capture;(void)cfg;return 0;}
static int volc_hal_capture_start(void *capture,int mode){(void)capture;if(mode==VOLC_AUDIO_MODE_CAPTURE)audio_stopped=false;return 0;}
static void *volc_hal_player_create(const volc_hal_player_config_t *cfg){(void)cfg;return (void*)3;}
static void volc_hal_player_set_authorization_gate(void *player,bool(*gate)(void)){(void)player;(void)gate;}
static void volc_hal_player_start(void *player){(void)player;output_stopped=false;}
static int volc_hal_player_stop(void *player){(void)player;output_stopped=true;return 0;}
static void volc_hal_player_discard_pending(void *player){(void)player;}
static void volc_hal_player_destroy(void *player){(void)player;}
static int volc_start(void *engine,const volc_opt_t *opt){
(void)engine;(void)opt;
if(scenario==START_FAILURE)return -1;
if(scenario!=DELAYED_CONNECTION&&scenario!=TIMEOUT_WITHOUT_CONNECTION){volc_event_t event={VOLC_EV_CONNECTED};__on_volc_event(NULL,&event,NULL);}
return 0;
}
static int volc_stop(void *engine){(void)engine;sdk_stopped=true;return 0;}
static int platform_conversation_poll(void){
polls++;
if(scenario==VOICE_GOODBYE||scenario==VOICE_RETRACT||scenario==VOICE_MISSING_REPLY){
if(polls==1)process_voice_end_transcript("subject","就聊到这吧",true);
if(scenario==VOICE_GOODBYE&&polls==2)__on_volc_conversation_status(NULL,VOLC_CONV_STATUS_ANSWER_FINISH,NULL);
if(scenario==VOICE_RETRACT&&polls==2)process_voice_end_transcript("subject","等等,我还想聊",true);
if(scenario==VOICE_RETRACT&&polls==3){assert(!voice_end.pending);conv_ai_service_task_stop();}
assert(polls<15);return 0;
}
if(scenario==IDLE_CONNECTED){
__on_volc_conversation_status(NULL,VOLC_CONV_STATUS_LISTENING,NULL);
return 0; // Persistent listening + cloud renewals must still time out.
}
if(scenario==ACTIVE_LONG_CALL){
if(polls<=150&&polls%30==1)call_idle_activity();
return 0; // Activity keeps a call alive beyond 60s, then silence ends it.
}
if(scenario==TIMEOUT_WITHOUT_CONNECTION){assert(!talking_frames);return 0;}
if(scenario==DELAYED_CONNECTION){assert(!talking_frames);assert(!strcmp(text,"正在连接"));}
if(scenario==REMOTE_DROP){volc_event_t event={VOLC_EV_DISCONNECTED};__on_volc_event(NULL,&event,NULL);}
else if(scenario==RESPONSE_FINISHED&&polls==1){
__on_volc_conversation_status(NULL,VOLC_CONV_STATUS_ANSWER_FINISH,NULL);
assert(!stop_requested);assert(!strcmp(text,"通话中"));assert(!ended_frames);
}else conv_ai_service_task_stop();
return 0;
}
static int volc_send_text_to_agent(void *engine,const char *text,int type,int mode){(void)engine;(void)text;(void)type;(void)mode;return 0;}
static int volc_send_message(void *engine,const char *data,size_t len,const volc_message_info_t *info){(void)engine;(void)data;(void)len;(void)info;return 0;}
static void aios_event_pub(int type,void *a,void *b){
(void)a;(void)b;assert(type==VOLC_SERVICE_AI_CONVERSATION_OVER);over_count++;
if(scenario!=AUDIO_STOP_FAILURE)assert(!strcmp(text,"待机"));
}
` + section('static void publish_call_display(', '\nstatic bool conversation_output_allowed(')
+ section('static void __on_volc_event(', '\nstatic void __on_volc_audio_data(')
+ section('void conv_ai_service_prepare_task(', '\n#if __cplusplus') + String.raw`
static void reset(int next){
scenario=next;polls=frames=ended_frames=interrupted_frames=failed_frames=ending_frames=talking_frames=over_count=0;
now_ms=10000;text[0]=0;audio_stopped=output_stopped=true;sdk_stopped=false;
memset(&call_display,0,sizeof(call_display));memset(&conv_service,0,sizeof(conv_service));
hal.player_handle[0]=(void*)3;global_display=hal.display_handle;diagnostic_say_pending=false;
conv_ai_service_prepare_task();
}
int main(void){
// State transitions are fenced by generation, even if a former terminal
// deadline or delayed CONNECTED observation is delivered after a new begin.
call_display_state_t state={0};unsigned first=call_display_begin(&state);
call_display_connected(&state,first);call_display_stopping(&state,first);
call_display_finished(&state,first,CALL_DISPLAY_STOP_NORMAL,false,100);
assert(state.phase==CALL_DISPLAY_ENDING);
call_display_finished(&state,first,CALL_DISPLAY_STOP_NORMAL,true,100);
assert(!call_display_idle_if_due(&state,first,2099));
unsigned next=call_display_begin(&state);
assert(!call_display_idle_if_due(&state,first,9999));call_display_connected(&state,first);
assert(state.phase==CALL_DISPLAY_CONNECTING);call_display_connected(&state,next);assert(state.phase==CALL_DISPLAY_TALKING);
for(int test=NORMAL;test<=VOICE_MISSING_REPLY;test++){
reset(test);conv_ai_service_task(NULL);assert(over_count==1);assert(ending_frames);
if(test==START_FAILURE||test==BEGIN_FAILURE||test==TIMEOUT_WITHOUT_CONNECTION){assert(failed_frames&& !talking_frames && !ended_frames);}
else if(test==REMOTE_DROP){assert(interrupted_frames&&!ended_frames);}
else if(test==AUDIO_STOP_FAILURE){assert(!ended_frames&&!failed_frames&&!interrupted_frames);}
else{assert(ended_frames&&!failed_frames&&!interrupted_frames);}
if(test!=AUDIO_STOP_FAILURE){assert(call_display.phase==CALL_DISPLAY_IDLE);assert(now_ms>=12000);}
if(test==IDLE_CONNECTED||test==TIMEOUT_WITHOUT_CONNECTION)assert(polls==60);
if(test==ACTIVE_LONG_CALL)assert(polls==180);
if(test==VOICE_GOODBYE)assert(polls==2&&atomic_load(&user_stop_requested));
if(test==VOICE_RETRACT)assert(polls==3);
if(test==VOICE_MISSING_REPLY)assert(polls==12&&atomic_load(&user_stop_requested));
// Late supplier turn callbacks after teardown cannot overwrite idle/end.
char before[sizeof(text)];strcpy(before,text);
__on_volc_conversation_status(NULL,VOLC_CONV_STATUS_ANSWER_FINISH,NULL);
__on_volc_conversation_status(NULL,VOLC_CONV_STATUS_LISTENING,NULL);
assert(!strcmp(before,text));
}
// Actual VAD callback records both edges, and starts a new quiet window.
int16_t quiet[320]={0}, comfort[320], speech[320];
for(int i=0;i<320;i++){comfort[i]=(i&1)?-20:20;speech[i]=(i&1)?-500:500;}
assert(!call_idle_pcm_active(quiet,sizeof(quiet)));
assert(!call_idle_pcm_active(comfort,sizeof(comfort)));
assert(call_idle_pcm_active(speech,sizeof(speech)));
assert(!call_idle_pcm_active(NULL,640)&&!call_idle_pcm_active(speech,639));
reset(NORMAL);
esp_gmf_afe_evt_t vad={ESP_GMF_AFE_EVT_VAD_START};
now_ms+=59000;call_idle_capture_event(&vad,NULL);
assert(atomic_load(&local_speech_active)&&!call_idle_expired());
vad.type=ESP_GMF_AFE_EVT_VAD_END;now_ms+=90000;call_idle_capture_event(&vad,NULL);
assert(!atomic_load(&local_speech_active)&&!call_idle_expired());
now_ms+=59999;assert(!call_idle_expired());now_ms++;assert(call_idle_expired());
now_ms=UINT32_MAX-1000ull;call_idle_activity();
now_ms+=59999;assert(!call_idle_expired());now_ms++;assert(call_idle_expired());
puts("PASS call display + idle: actual task/callbacks, connected-only talking, local shutdown before terminal, start failure, provider drop, answer-finish continuity, slow end ACK, failed local stop, 60s quiet timeout, 180s activity call, VAD edges, silence/comfort PCM rejection, clock wrap, generation-fenced hold");
return 0;
}
`);
@@ -0,0 +1,134 @@
// Compile actual capture lifecycle and recorder close/read functions with
// concurrent workers. No device, microphone or provider access.
const fs=require('node:fs'),path=require('node:path');
const root=path.resolve(__dirname,'..');
const capture=fs.readFileSync(path.join(root,'components/application/platform/src/espressif/volc_hal_capture.c'),'utf8').replace(/^#include .*$/gm,'');
const recorder=fs.readFileSync(path.join(root,'components/av_processor/audio_processor.c'),'utf8');
function section(first,last){const a=recorder.indexOf(first),b=recorder.indexOf(last,a);if(a<0||b<0)throw Error(first);return recorder.slice(a,b);}
process.stdout.write(String.raw`
#include <assert.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
typedef int esp_err_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_LOGI(...) ((void)0)
#define ESP_LOGW(...) ((void)0)
#define ESP_LOGE(...) ((void)0)
#define LOGI(...) ((void)0)
#define LOGE(...) ((void)0)
#define pdMS_TO_TICKS(x) (x)
#define portMAX_DELAY 0
#define MALLOC_CAP_SPIRAM 1
#define MALLOC_CAP_8BIT 2
#define pdPASS 1
#define VOLC_MEDIA_TYPE_AUDIO 0
#define VOLC_MEDIA_TYPE_VIDEO 1
#define VOLC_HAL_CAPTURE_AUDIO 0
#define VOLC_AUDIO_DATA_TYPE_PCM 0
#define AV_PROCESSOR_FORMAT_ID_PCM 0
typedef int volc_media_type_e;
typedef enum {VOLC_AUDIO_MODE_WAKEUP,VOLC_AUDIO_MODE_CAPTURE} volc_hal_audio_capture_mode_e;
typedef void *volc_hal_capture_t;
typedef struct {int data_type;void *user_data;} volc_hal_frame_info_t;
typedef void (*volc_hal_capture_data_cb_t)(void*,const void*,int,volc_hal_frame_info_t*);
typedef void (*volc_hal_audio_wakeup_cb_t)(void*,void*);
typedef struct {int media_type;volc_hal_capture_data_cb_t data_cb;volc_hal_audio_wakeup_cb_t audio_wakeup_cb;void *user_data;} volc_hal_capture_config_t;
typedef struct {void *capture_handle[1];} volc_hal_context_t;
static volc_hal_context_t hal;
static volc_hal_context_t *volc_get_global_hal_context(void){return &hal;}
typedef struct {void *rec_dev;} basic_board_periph_t;
basic_board_periph_t global_periph;
static void esp_codec_dev_set_in_gain(void *d,float g){(void)d;(void)g;}
typedef struct {int format;struct {struct {struct {int sample_rate,sample_bits,channels,frame_duration;} audio_info;} g711;} params;} av_processor_encoder_config_t;
typedef struct {bool ai_mode_wakeup;} av_processor_afe_config_t;
typedef struct {av_processor_encoder_config_t encoder_cfg;av_processor_afe_config_t afe_config;volc_hal_audio_wakeup_cb_t recorder_event_cb;void *recorder_ctx;} audio_recorder_config_t;
#define DEFAULT_AV_PROCESSOR_AFE_CONFIG() {0}
#define DEFAULT_AUDIO_RECORDER_CONFIG() {0}
typedef unsigned UBaseType_t;
static UBaseType_t uxTaskPriorityGet(void *t){(void)t;return 5;}
static void vTaskPrioritySet(void *t,unsigned p){(void)t;(void)p;}
static void vTaskDelay(unsigned ticks){(void)ticks;struct timespec t={0,1000000};nanosleep(&t,NULL);}
typedef struct {pthread_mutex_t mutex;pthread_cond_t cond;int value;} *SemaphoreHandle_t;
static unsigned semaphores,tasks,allocations,pipelines;
static SemaphoreHandle_t xSemaphoreCreateBinary(void){
SemaphoreHandle_t s=calloc(1,sizeof(*s));assert(s);pthread_mutex_init(&s->mutex,NULL);pthread_cond_init(&s->cond,NULL);semaphores++;return s;
}
static int xSemaphoreTake(SemaphoreHandle_t s,unsigned timeout){(void)timeout;pthread_mutex_lock(&s->mutex);while(!s->value)pthread_cond_wait(&s->cond,&s->mutex);s->value--;pthread_mutex_unlock(&s->mutex);return 1;}
static void xSemaphoreGive(SemaphoreHandle_t s){pthread_mutex_lock(&s->mutex);s->value++;pthread_cond_signal(&s->cond);pthread_mutex_unlock(&s->mutex);}
static void vSemaphoreDelete(SemaphoreHandle_t s){pthread_mutex_destroy(&s->mutex);pthread_cond_destroy(&s->cond);free(s);semaphores--;}
typedef struct {pthread_t thread;void (*fn)(void*);void *arg;} *TaskHandle_t;
static bool task_fail,open_fail,buffer_fail;
static void *worker(void *p){TaskHandle_t t=p;t->fn(t->arg);assert(0);return NULL;}
static int xTaskCreatePinnedToCoreWithCaps(void (*fn)(void*),const char *name,unsigned stack,void *arg,unsigned priority,TaskHandle_t *h,int core,unsigned caps){
(void)name;(void)stack;(void)priority;(void)core;(void)caps;if(task_fail)return 0;
TaskHandle_t t=calloc(1,sizeof(*t));assert(t);t->fn=fn;t->arg=arg;*h=t;tasks++;assert(!pthread_create(&t->thread,NULL,worker,t));return pdPASS;
}
static void vTaskSuspend(void *h){assert(!h);pthread_exit(NULL);}
static void vTaskDeleteWithCaps(TaskHandle_t t){assert(!pthread_join(t->thread,NULL));free(t);tasks--;}
static void *volc_osal_malloc(size_t n){if(buffer_fail)return NULL;void *p=malloc(n);assert(p);allocations++;return p;}
static void *volc_osal_calloc(size_t n,size_t s){void *p=calloc(n,s);assert(p);allocations++;return p;}
static void volc_osal_free(void *p){if(p){free(p);allocations--;}}
enum {AUDIO_RUN_STATE_CLOSED,AUDIO_RUN_STATE_PLAYING};
typedef struct {int valid_size;void *buf;} esp_gmf_data_bus_block_t;
typedef struct {pthread_mutex_t mutex;pthread_cond_t cond;bool aborted,supply;int borrowers;char data[1920];} fifo_t;
static struct {int state;void *pipe,*task,*afe_cfg,*afe_manager,*pool;fifo_t *fifo;void *cb,*ctx;} audio_recorder;
static atomic_bool recorder_closing;
static atomic_uint recorder_readers,frames;
static SemaphoreHandle_t control;
static SemaphoreHandle_t recorder_control_lock(void){return control;}
static atomic_uint fifo_waiters;
static int esp_gmf_fifo_acquire_read(fifo_t *f,esp_gmf_data_bus_block_t *b,int n,unsigned timeout){
(void)timeout;pthread_mutex_lock(&f->mutex);atomic_fetch_add(&fifo_waiters,1);
while(!f->aborted&&!f->supply)pthread_cond_wait(&f->cond,&f->mutex);
atomic_fetch_sub(&fifo_waiters,1);
int ret=-1;if(!f->aborted){f->supply=false;f->borrowers++;b->valid_size=n;b->buf=f->data;ret=0;}
pthread_mutex_unlock(&f->mutex);return ret;
}
static void esp_gmf_fifo_release_read(fifo_t *f,esp_gmf_data_bus_block_t *b,unsigned t){(void)b;(void)t;pthread_mutex_lock(&f->mutex);assert(f->borrowers==1);f->borrowers--;pthread_mutex_unlock(&f->mutex);}
static void esp_gmf_fifo_abort(fifo_t *f){pthread_mutex_lock(&f->mutex);f->aborted=true;pthread_cond_broadcast(&f->cond);pthread_mutex_unlock(&f->mutex);}
static void esp_gmf_pipeline_stop(void *p){assert(p);}
static void esp_gmf_pipeline_destroy(void *p){assert(p);free(p);pipelines--;}
static void esp_gmf_task_deinit(void *p){assert(p);free(p);}
static void esp_gmf_fifo_destroy(fifo_t *f){assert(!atomic_load(&recorder_readers)&&!f->borrowers);pthread_mutex_destroy(&f->mutex);pthread_cond_destroy(&f->cond);free(f);}
static void afe_config_free(void *p){free(p);}
static void esp_gmf_afe_manager_destroy(void *p){free(p);}
static void av_audio_recorder_pool_deinit(void *p){free(p);}
`+section('static void recorder_cleanup_on_error(void)','static esp_err_t recorder_configure_all_encoders(void)')+section('static esp_err_t recorder_close_internal(void)','esp_err_t audio_feeder_feed_data(uint8_t')+String.raw`
static int audio_recorder_open(audio_recorder_config_t *c){
(void)c;assert(audio_recorder.state==AUDIO_RUN_STATE_CLOSED);
audio_recorder.pipe=malloc(1);pipelines++;audio_recorder.task=malloc(1);audio_recorder.afe_cfg=malloc(1);audio_recorder.afe_manager=malloc(1);audio_recorder.pool=malloc(1);
audio_recorder.fifo=calloc(1,sizeof(fifo_t));assert(audio_recorder.fifo);pthread_mutex_init(&audio_recorder.fifo->mutex,NULL);pthread_cond_init(&audio_recorder.fifo->cond,NULL);audio_recorder.fifo->supply=true;
if(open_fail){recorder_cleanup_on_error();assert(!audio_recorder.pool&&!audio_recorder.pipe&&!audio_recorder.task&&!audio_recorder.fifo&&!audio_recorder.afe_cfg&&!audio_recorder.afe_manager);return -1;}
audio_recorder.state=AUDIO_RUN_STATE_PLAYING;return 0;
}
`+capture+String.raw`
static void captured(void *c,const void *b,int n,volc_hal_frame_info_t *f){(void)c;(void)b;(void)n;(void)f;atomic_fetch_add(&frames,1);}
int main(void){
control=xSemaphoreCreateBinary();xSemaphoreGive(control);
volc_hal_capture_config_t cfg={.media_type=VOLC_MEDIA_TYPE_AUDIO,.data_cb=captured};
volc_hal_capture_t c=volc_hal_capture_create(&cfg);assert(c);
for(unsigned i=0;i<100;i++){
assert(!volc_hal_capture_start(c,i%2?VOLC_AUDIO_MODE_CAPTURE:VOLC_AUDIO_MODE_WAKEUP));
while(!atomic_load(&fifo_waiters))vTaskDelay(1);
assert(pipelines==1&&tasks==1);
assert(!volc_hal_capture_stop(c));assert(!pipelines&&!tasks&&!recorder_readers&&!fifo_waiters);
assert(!((volc_hal_capture_impl_t*)c)->capture_thread);
assert(!volc_hal_capture_update_config(c,&cfg));assert(!volc_hal_capture_stop(c));
}
task_fail=true;assert(volc_hal_capture_start(c,VOLC_AUDIO_MODE_CAPTURE)==-1);assert(!pipelines&&!tasks);task_fail=false;
open_fail=true;assert(volc_hal_capture_start(c,VOLC_AUDIO_MODE_CAPTURE)==-1);assert(!pipelines&&!tasks);open_fail=false;
buffer_fail=true;assert(volc_hal_capture_start(c,VOLC_AUDIO_MODE_CAPTURE)==-1);buffer_fail=false;
assert(!volc_hal_capture_start(c,VOLC_AUDIO_MODE_CAPTURE));while(!fifo_waiters)vTaskDelay(1);
volc_hal_capture_destroy(c);assert(!hal.capture_handle[0]&&!pipelines&&!tasks&&!allocations);
vSemaphoreDelete(control);assert(!semaphores&&frames>=50);
puts("PASS 100 concurrent capture/wakeup restarts: blocked reader abort and join, pipeline/WithCaps/handle/buffer cleanup, duplicate stop, open/buffer/task failure recovery, destroy");
return 0;
}
`);
@@ -0,0 +1,66 @@
// Compile production configuration/URL policy functions against hardware and
// NVS doubles. Hardware acceptance remains a separate COM6 check.
const fs=require('fs'),path=require('path');
const base=path.resolve(__dirname,'..');
const json=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const source=fs.readFileSync(path.join(base,'main/device_control.c'),'utf8');
const apply=source.slice(source.indexOf('static esp_err_t apply_config('),source.indexOf('static cJSON *diagnostics('));
const ota=fs.readFileSync(path.join(base,'main/device_ota.c'),'utf8');
const url=ota.slice(ota.indexOf('static bool same_origin_download('),ota.indexOf('static esp_err_t download('));
process.stdout.write(fs.readFileSync(path.join(json,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(json,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+`
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
typedef int esp_err_t;
typedef int nvs_handle_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_INVALID_RESPONSE 1
#define ESP_ERR_NOT_SUPPORTED 2
#define NVS_READWRITE 1
static int64_t applied_revision;
static char device_display_name[97], stored_name[97], staged_name[97];
static int read_error;
static int power_error;
static int device_power_apply(const char *mode,bool held){return power_error;}
static int hardware_volume=70,stored=-1,staged=-1,codec_error,commit_error,commits;
static int volc_hal_get_device_volume(void){return hardware_volume;}
static int volc_hal_set_device_volume(int n){if(codec_error)return -1;hardware_volume=n;return 0;}
static int nvs_open(const char *s,int mode,int *n){*n=1;return 0;}
static int nvs_get_i32(int n,const char *k,int32_t *v){*v=stored;return stored<0?-1:0;}
static int nvs_set_i32(int n,const char *k,int32_t v){staged=v;return 0;}
static int nvs_commit(int n){if(commit_error)return -1;commits++;stored=staged;strcpy(stored_name,staged_name);return 0;}
static int nvs_set_str(int n,const char *k,const char *s){strcpy(staged_name,s);return 0;}
static int nvs_get_str(int n,const char *k,char *out,size_t *size){if(read_error)return -1;size_t len=strlen(stored_name)+1;if(*size<len)return -1;memcpy(out,stored_name,len);*size=len;return 0;}
static void nvs_close(int n){}
static const char *ble_cloud_origin(void){return "https://82.157.201.114";}
`+apply+'\n'+url+`
static int apply(const char *s){cJSON *j=cJSON_Parse(s);int e=apply_config(j,false);cJSON_Delete(j);return e;}
int main(void){
assert(apply("{\\"revision\\":2,\\"desired_config\\":{\\"volume\\":{\\"speaker\\":60},\\"voice_runtime\\":\\"volc_hardware_agent\\"}}" )==0);
assert(hardware_volume==60 && stored==60 && applied_revision==2 && commits==1);
assert(apply("{\\"revision\\":2,\\"desired_config\\":{\\"volume\\":{\\"speaker\\":60}}}")==0);assert(commits==1);
codec_error=1;assert(apply("{\\"revision\\":3,\\"desired_config\\":{\\"volume\\":{\\"speaker\\":45}}}")!=0);
assert(hardware_volume==60 && applied_revision==2);codec_error=0;
commit_error=1;assert(apply("{\\"revision\\":3,\\"desired_config\\":{\\"volume\\":{\\"speaker\\":45}}}")!=0);
assert(hardware_volume==45 && stored==60 && applied_revision==2);commit_error=0;
assert(apply("{\\"revision\\":3,\\"desired_config\\":{\\"volume\\":{\\"speaker\\":45}}}")==0);assert(stored==45 && applied_revision==3);
assert(apply("{\\"revision\\":4,\\"desired_config\\":{\\"volume\\":{\\"speaker\\":-1}}}")!=0);
assert(apply("{\\"revision\\":4,\\"desired_config\\":{\\"volume\\":{\\"speaker\\":100.5}}}")!=0);
assert(apply("{\\"revision\\":4,\\"desired_config\\":{\\"volume\\":{\\"speaker\\":50},\\"unsupported\\":{}}}")!=0);
assert(hardware_volume==45 && applied_revision==3);
commit_error=1;assert(apply("{\\"revision\\":4,\\"desired_config\\":{\\"identity\\":{\\"display_name\\":\\"客厅陪伴设备\\"}}}")!=0);assert(device_display_name[0]==0 && applied_revision==3);commit_error=0;
read_error=1;assert(apply("{\\"revision\\":4,\\"desired_config\\":{\\"identity\\":{\\"display_name\\":\\"客厅陪伴设备\\"}}}")!=0);assert(device_display_name[0]==0 && applied_revision==3);read_error=0;
assert(apply("{\\"revision\\":4,\\"desired_config\\":{\\"identity\\":{\\"display_name\\":\\"客厅陪伴设备\\"}}}")==0);assert(!strcmp(device_display_name,"客厅陪伴设备") && applied_revision==4);
int previous_commits=commits;assert(apply("{\\"revision\\":4,\\"desired_config\\":{\\"identity\\":{\\"display_name\\":\\"客厅陪伴设备\\"}}}")==0);assert(commits==previous_commits);
assert(apply("{\\"revision\\":5,\\"desired_config\\":{\\"identity\\":{\\"display_name\\":\\"名名名名名名名名名名名名名名名名名名名名名名名名名名名名名名名名名\\"}}}")!=0);assert(applied_revision==4);
assert(apply("{\\"revision\\":5,\\"desired_config\\":{\\"identity\\":{\\"display_name\\":\\"尾部空格 \\"}}}")!=0);assert(applied_revision==4);
assert(apply("{\\"revision\\":5,\\"desired_config\\":{\\"identity\\":{\\"display_name\\":\\"\\"}}}")==0);assert(device_display_name[0]==0 && stored_name[0]==0 && applied_revision==5);
assert(same_origin_download("https://82.157.201.114/f/ticket"));
assert(!same_origin_download("http://82.157.201.114/f/ticket"));
assert(!same_origin_download("https://82.157.201.114.evil.test/f/ticket"));
assert(!same_origin_download("https://82.157.201.114@evil.test/f/ticket"));
assert(!same_origin_download("https://82.157.201.114/other/ticket"));
puts("PASS production C: idle codec volume, NVS durability, revision accuracy, invalid config and HTTPS OTA origin policy");return 0;
}
`);
@@ -0,0 +1,602 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <setjmp.h>
#include <openssl/sha.h>
typedef int esp_err_t;
typedef int nvs_handle_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_NO_MEM 1
#define ESP_ERR_INVALID_RESPONSE 2
#define ESP_ERR_NOT_FOUND 3
#define ESP_ERR_INVALID_ARG 4
#define ESP_ERR_INVALID_SIZE 5
#define ESP_ERR_TIMEOUT 6
#define ESP_ERR_INVALID_CRC 7
#define ESP_ERR_NVS_NOT_FOUND 8
#define ESP_ERR_HTTP_EAGAIN 0x7007
#define NVS_READONLY 0
#define NVS_READWRITE 1
#define DEVICE_FIRMWARE_VERSION "host-original"
#define ESP_PARTITION_SUBTYPE_APP_OTA_MIN 16
#define ESP_PARTITION_SUBTYPE_APP_OTA_MAX 31
#define ESP_OTA_IMG_PENDING_VERIFY 1
#define ESP_OTA_IMG_VALID 2
#define ESP_LOGI(...) ((void)0)
#define ESP_LOGE(...) ((void)0)
#define ESP_LOGW(...) ((void)0)
#define MALLOC_CAP_INTERNAL (1U<<11)
#define MALLOC_CAP_8BIT (1U<<2)
#define OTA_WITH_SEQUENTIAL_WRITES 0
#define pdTRUE 1
#define pdPASS 1
#define portMAX_DELAY UINT32_MAX
typedef struct { uint32_t address,size,subtype; } esp_partition_t;
typedef int esp_ota_img_states_t;
typedef int esp_ota_handle_t;
static const esp_partition_t parts[]={{0x10000,131072,16},{0x410000,131072,17}};
static uint8_t flash[2][131072],image_bytes[65536+123];
static size_t image_size=2048;
static int running,boot_partition,image_states[2],mark_error,boot_error,end_error;
static int begun,written,aborted,restarts,rollbacks,marked,downloaded,reported_success,reported_failed,accepted;
static int manifest_requests,manifest_error,report_error,busy,maintenance,leaves,http_status,header_size,short_read,read_error;
static int timer_create_error,timer_start_error,timer_armed,timer_deleted;
static int64_t monotonic,clock_step;
static int read_script[64],read_script_len,read_script_index,read_calls,read_timeout_ms,http_errno;
static size_t read_at_offsets[64],read_wanted[64],write_sizes[64];
static int timeout_values[64],timeout_count,write_calls,write_fail_on,write_fail_code;
static int64_t read_advances[64],heartbeat_advance,write_advance,begin_advance,open_advance,header_advance,end_advance;
static int64_t sha_start_advance,sha_update_advance,sha_finish_advance;
static int begin_error,begin_error_handle,open_error,http_init_error,sha_error_stage;
static int opened,headers_fetched,closed,client_cleaned,client_live,ota_handle_live,ended,boot_sets,transfer_first_read_call;
static size_t begin_size;
static const esp_partition_t *begin_target;
static unsigned alloc_calls,heap_fail_first,heap_frees,heap_live;
static size_t alloc_sizes[8],allocated_size;
static unsigned alloc_caps[8];
static void *allocated_buffer;
static size_t read_offset,write_offset;
static char last_report[16];
static char last_report_message[100];
static cJSON *manifest_json;
static jmp_buf restart_jump;
static bool allow_restart;
// Execute the real production worker synchronously when the parent takes the
// semaphore. Suspend escapes with longjmp; parent must join/delete before reset.
// This proves SDK-call context and ownership, not ESP32 cache/MMU behavior.
typedef struct fake_sem { bool given; } *SemaphoreHandle_t;
typedef struct fake_task { void (*entry)(void *); void *arg; bool suspended; } *TaskHandle_t;
static TaskHandle_t live_worker;
static SemaphoreHandle_t live_sem;
static bool internal_worker_context;
static jmp_buf worker_suspend_jump;
static int sem_attempts,sem_created,sem_deleted,task_attempts,task_created,task_deleted;
static int sem_fail_on,task_fail_on,worker_runs,sem_gives,sem_takes,sha_live,rollback_error;
static int64_t cleanup_advance;
static SemaphoreHandle_t xSemaphoreCreateBinary(void){
assert(!internal_worker_context&&!live_sem&&!live_worker);
if(++sem_attempts==sem_fail_on)return NULL;
live_sem=calloc(1,sizeof(*live_sem));assert(live_sem);sem_created++;return live_sem;
}
static int xTaskCreateWithCaps(void (*entry)(void *),const char *name,unsigned stack,void *arg,unsigned priority,TaskHandle_t *result,unsigned caps){
assert(!internal_worker_context&&live_sem&&!live_worker);
assert(!strcmp(name,"ota_flash")&&stack==8192&&priority==3&&caps==(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
// The real TLS client, internal write buffer and SHA context must be released
// before the temporary 8KiB DRAM stack allocation, including low-memory cases.
assert(!client_live&&!heap_live&&!sha_live);
if(++task_attempts==task_fail_on)return 0;
live_worker=calloc(1,sizeof(*live_worker));assert(live_worker);
live_worker->entry=entry;live_worker->arg=arg;*result=live_worker;task_created++;return pdPASS;
}
static int xSemaphoreGive(SemaphoreHandle_t sem){
assert(internal_worker_context&&sem==live_sem&&!sem->given);sem->given=true;sem_gives++;return pdTRUE;
}
static void vTaskSuspend(TaskHandle_t task){
assert(internal_worker_context&&task==NULL&&live_worker&&live_sem->given);
live_worker->suspended=true;longjmp(worker_suspend_jump,1);
}
static int xSemaphoreTake(SemaphoreHandle_t sem,unsigned timeout){
assert(!internal_worker_context&&sem==live_sem&&live_worker&&timeout==portMAX_DELAY);
assert(!sem->given);worker_runs++;internal_worker_context=true;
if(!setjmp(worker_suspend_jump)){live_worker->entry(live_worker->arg);assert(!"worker returned without suspend");}
internal_worker_context=false;assert(sem->given&&live_worker->suspended);sem_takes++;return pdTRUE;
}
static void vTaskDeleteWithCaps(TaskHandle_t task){
assert(!internal_worker_context&&task==live_worker&&task->suspended&&live_sem->given);
free(task);live_worker=NULL;task_deleted++;
}
static void vSemaphoreDelete(SemaphoreHandle_t sem){
assert(!internal_worker_context&&sem==live_sem&&!live_worker);
free(sem);live_sem=NULL;sem_deleted++;
}
static void assert_worker_cleanup(void){
assert(!live_worker&&!live_sem&&!internal_worker_context);
assert(task_created==task_deleted&&sem_created==sem_deleted);
assert(worker_runs==sem_gives&&sem_gives==sem_takes&&sem_takes==task_deleted);
}
static const char *platform_error="";
static const char *task="40000000-0000-4000-8000-000000000001";
static struct {char key[16];unsigned char bytes[512];size_t size;} records[8];
static int record_count,fail_save_phase,fail_installed,fail_readback;
static bool fail_receipt_commit;
static unsigned char staged[512];static size_t staged_size;static char staged_key[16];
static void *heap_caps_malloc(size_t size,unsigned caps){
assert(alloc_calls<8);alloc_sizes[alloc_calls]=size;alloc_caps[alloc_calls]=caps;
if(++alloc_calls<=heap_fail_first)return NULL;
assert(!heap_live);allocated_buffer=malloc(size);assert(allocated_buffer);allocated_size=size;heap_live++;return allocated_buffer;
}
static void heap_caps_free(void *buffer){
if(!buffer)return;assert(heap_live==1&&buffer==allocated_buffer);free(buffer);allocated_buffer=NULL;heap_live--;heap_frees++;
}
static int nvs_open(const char *space,int mode,int *handle){assert(!strcmp(space,"control"));*handle=1;return 0;}
static void nvs_close(int n){}
static int nvs_set_blob(int n,const char *key,const void *bytes,size_t size){assert(size<=sizeof(staged)&&strlen(key)<sizeof(staged_key));strcpy(staged_key,key);memcpy(staged,bytes,size);staged_size=size;return 0;}
static int nvs_commit(int n){
if(!strcmp(staged_key,"ota")){uint32_t phase;memcpy(&phase,staged+4,4);if((fail_save_phase&&phase==(unsigned)fail_save_phase)||(fail_receipt_commit&&phase==0))return ESP_FAIL;}
if(!strncmp(staged_key,"installed",9)&&fail_installed)return ESP_FAIL;
int i;for(i=0;i<record_count;i++)if(!strcmp(records[i].key,staged_key))break;
assert(i<8);if(i==record_count)record_count++;strcpy(records[i].key,staged_key);memcpy(records[i].bytes,staged,staged_size);records[i].size=staged_size;return 0;
}
static int nvs_get_blob(int n,const char *key,void *bytes,size_t *size){
for(int i=0;i<record_count;i++)if(!strcmp(records[i].key,key)){if(*size<records[i].size)return ESP_ERR_INVALID_SIZE;*size=records[i].size;memcpy(bytes,records[i].bytes,*size);if(fail_readback)return ESP_FAIL;return 0;}return ESP_ERR_NVS_NOT_FOUND;
}
typedef struct {char item[37];bool occupied;} StaticQueue_t;
typedef StaticQueue_t *QueueHandle_t;
static QueueHandle_t xQueueCreateStatic(int n,int size,uint8_t *storage,StaticQueue_t *q){assert(n==1&&size==37);q->occupied=false;return q;}
static int xQueueSend(QueueHandle_t q,const void *item,int timeout){if(q->occupied)return 0;memcpy(q->item,item,37);q->occupied=true;return pdTRUE;}
static int xQueueReceive(QueueHandle_t q,void *item,int timeout){if(!q->occupied)return 0;memcpy(item,q->item,37);q->occupied=false;return pdTRUE;}
typedef struct {void (*callback)(void *);const char *name;} esp_timer_create_args_t;
typedef void *esp_timer_handle_t;
static void (*timer_callback)(void *);
static int esp_timer_create(const esp_timer_create_args_t *a,esp_timer_handle_t *h){if(timer_create_error)return ESP_FAIL;*h=(void*)1;timer_callback=a->callback;return 0;}
static int esp_timer_start_once(esp_timer_handle_t h,int64_t us){assert(us==180000000);if(timer_start_error)return ESP_FAIL;timer_armed=1;return 0;}
static int esp_timer_stop(esp_timer_handle_t h){timer_armed=0;return 0;}
static int esp_timer_delete(esp_timer_handle_t h){timer_deleted++;return 0;}
static int64_t esp_timer_get_time(void){monotonic+=clock_step;return monotonic;}
static const esp_partition_t *esp_ota_get_running_partition(void){return &parts[running];}
static const esp_partition_t *esp_ota_get_next_update_partition(const esp_partition_t *p){return &parts[1-running];}
static int esp_ota_get_state_partition(const esp_partition_t *p,int *value){*value=image_states[p==&parts[1]];return *value<0?ESP_ERR_NOT_FOUND:0;}
static int esp_ota_set_boot_partition(const esp_partition_t *p){assert(internal_worker_context&&!heap_live&&!client_live&&!sha_live);boot_sets++;if(boot_error)return ESP_FAIL;boot_partition=p==&parts[1];return 0;}
static int esp_ota_mark_app_valid_cancel_rollback(void){if(mark_error)return ESP_FAIL;marked++;image_states[running]=ESP_OTA_IMG_VALID;return 0;}
static void esp_restart(void){assert_worker_cleanup();restarts++;assert(allow_restart);longjmp(restart_jump,1);}
static int esp_ota_mark_app_invalid_rollback(void){assert(internal_worker_context);rollbacks++;if(rollback_error)return ESP_FAIL;boot_partition=1-running;return 0;}
static int esp_partition_read(const esp_partition_t *p,uint32_t offset,void *bytes,size_t size){assert(offset+size<=p->size);memcpy(bytes,flash[p==&parts[1]]+offset,size);return 0;}
static int esp_ota_begin(const esp_partition_t *p,size_t size,esp_ota_handle_t *handle){
assert(p==&parts[1-running]&&read_calls==transfer_first_read_call&&!ota_handle_live);begun++;begin_target=p;begin_size=size;write_offset=0;
assert(size>=1024&&size<=p->size);monotonic+=begin_advance;
if(!begin_error||begin_error_handle){*handle=(p==&parts[1])+1;ota_handle_live=*handle;}
if(begin_error)return begin_error;
size_t erased=(size+4095)&~(size_t)4095;assert(erased<=p->size);memset(flash[p==&parts[1]],0xff,erased);return 0;
}
static int esp_ota_write(esp_ota_handle_t handle,const void *bytes,size_t size){
assert(handle==ota_handle_live&&handle-1!=running&&write_calls<64);write_sizes[write_calls++]=size;monotonic+=write_advance;
if(write_fail_on&&write_calls==write_fail_on)return write_fail_code;
assert(write_offset+size<=parts[handle-1].size);memcpy(flash[handle-1]+write_offset,bytes,size);write_offset+=size;written++;return 0;
}
static int esp_ota_end(esp_ota_handle_t handle){assert(internal_worker_context&&!heap_live&&!client_live&&!sha_live);assert(handle==ota_handle_live);ended++;monotonic+=end_advance;ota_handle_live=0;return end_error?ESP_FAIL:0;}
static int esp_ota_abort(esp_ota_handle_t handle){assert(handle==ota_handle_live);ota_handle_live=0;aborted++;return 0;}
static const char *esp_err_to_name(int err){return "INJECTED_FAILURE";}
static int esp_crt_bundle_attach(void *v){return 0;}
typedef struct {const char *url;int (*crt_bundle_attach)(void *);int timeout_ms,disable_auto_redirect,buffer_size,buffer_size_tx,keep_alive_enable;} esp_http_client_config_t;
typedef void *esp_http_client_handle_t;
static void *esp_http_client_init(const esp_http_client_config_t *cfg){assert(cfg->disable_auto_redirect&&cfg->crt_bundle_attach&&cfg->buffer_size==16384&&!client_live);read_offset=0;transfer_first_read_call=read_calls;read_timeout_ms=cfg->timeout_ms;downloaded++;if(http_init_error)return NULL;client_live=1;return (void*)1;}
static int esp_http_client_open(void *h,int length){assert(h==(void*)1&&client_live);opened++;monotonic+=open_advance;return open_error;}
static int64_t esp_http_client_fetch_headers(void *h){assert(h==(void*)1&&client_live);headers_fetched++;monotonic+=header_advance;return header_size;}
static int esp_http_client_get_status_code(void *h){return http_status;}
static int esp_http_client_set_timeout_ms(void *h,int timeout_ms){assert(h==(void*)1&&client_live&&timeout_ms>0&&timeout_ms<=20000&&timeout_count<64);timeout_values[timeout_count++]=timeout_ms;read_timeout_ms=timeout_ms;return 0;}
static int esp_http_client_get_errno(void *h){assert(h==(void*)1);return http_errno;}
static int esp_http_client_read(void *h,char *buf,size_t size){
assert(h==(void*)1&&client_live&&read_calls<64&&heap_live&&buf==allocated_buffer&&size<=allocated_size);int index=read_calls++;read_at_offsets[index]=read_offset;read_wanted[index]=size;
int result=read_error?-1:short_read?0:read_script_index<read_script_len?read_script[read_script_index++]:(int)size;
http_errno=result==-ESP_ERR_HTTP_EAGAIN?11:result<0?5:0;
monotonic+=read_advances[index];if(result==-ESP_ERR_HTTP_EAGAIN)monotonic+=(int64_t)read_timeout_ms*1000;
if(result<=0)return result;
assert((size_t)result<=size&&read_offset+(size_t)result<=image_size);memcpy(buf,image_bytes+read_offset,result);read_offset+=result;return result;
}
static void esp_http_client_close(void *h){assert(h==(void*)1&&client_live);closed++;}
static void esp_http_client_cleanup(void *h){assert(h==(void*)1&&client_live);client_live=0;client_cleaned++;monotonic+=cleanup_advance;}
typedef SHA256_CTX mbedtls_sha256_context;
static void mbedtls_sha256_init(mbedtls_sha256_context *ctx){sha_live++;memset(ctx,0,sizeof(*ctx));}
static void mbedtls_sha256_free(mbedtls_sha256_context *ctx){assert(sha_live>0);sha_live--;}
static int mbedtls_sha256_starts(mbedtls_sha256_context *ctx,int is224){monotonic+=sha_start_advance;if(sha_error_stage==1)return -1;return SHA256_Init(ctx)?0:-1;}
static int mbedtls_sha256_update(mbedtls_sha256_context *ctx,const void *bytes,size_t size){monotonic+=sha_update_advance;if(sha_error_stage==2)return -1;return SHA256_Update(ctx,bytes,size)?0:-1;}
static int mbedtls_sha256_finish(mbedtls_sha256_context *ctx,unsigned char hash[32]){monotonic+=sha_finish_advance;if(sha_error_stage==3)return -1;return SHA256_Final(hash,ctx)?0:-1;}
static void mbedtls_platform_zeroize(void *bytes,size_t size){memset(bytes,0,size);}
static bool call_client_enter_maintenance(void){if(busy)return false;assert(!maintenance);maintenance=1;return true;}
static void call_client_leave_maintenance(void){assert(maintenance);maintenance=0;leaves++;}
static void device_control_heartbeat(void){monotonic+=heartbeat_advance;heartbeat_advance=0;}
static void device_control_prepare_restart(void){}
static const char *ble_cloud_origin(void){return "https://platform.example";}
static const char *ble_factory_model(void){return "ECHOEAR-S3";}
static const char *platform_last_error_code(void){return platform_error;}
const char *device_ota_version(void);
int64_t device_ota_version_code(void);
static int platform_request(const char *method,const char *path,const char *body,const char *id,char **raw){
assert(!strcmp(method,"POST"));*raw=NULL;cJSON *request=cJSON_Parse(body);assert(request);
assert(!strcmp(cJSON_GetObjectItem(request,"task_id")->valuestring,task));
if(!strcmp(path,"/api/v1/device/ota/manifest")){manifest_requests++;cJSON_Delete(request);if(manifest_error)return ESP_FAIL;cJSON *root=cJSON_CreateObject();cJSON_AddItemToObject(root,"data",cJSON_Duplicate(manifest_json,true));*raw=cJSON_PrintUnformatted(root);cJSON_Delete(root);return 0;}
assert(!strcmp(path,"/api/v1/device/ota/report"));const char *status=cJSON_GetObjectItem(request,"status")->valuestring;snprintf(last_report,sizeof(last_report),"%s",status);
const cJSON *message=cJSON_GetObjectItem(request,"message");snprintf(last_report_message,sizeof(last_report_message),"%s",cJSON_IsString(message)?message->valuestring:"");
if(!strcmp(status,"accepted"))accepted++;else if(!strcmp(status,"success"))reported_success++;else if(!strcmp(status,"failed"))reported_failed++;else assert(false);
cJSON_Delete(request);if(report_error)return ESP_FAIL;*raw=strdup("{\"data\":{}}");return 0;
}
/* PRODUCTION_OTA */
static void reboot_to(int slot){
running=slot;maintenance=0;ready=false;boot_timer=NULL;requests=NULL;timer_armed=0;timer_callback=NULL;memset(&state,0,sizeof(state));memset(&installed,0,sizeof(installed));
device_ota_boot_guard();
}
static void fresh(void){
assert(!heap_live&&!client_live&&!ota_handle_live&&!sha_live);assert_worker_cleanup();
sem_attempts=sem_created=sem_deleted=task_attempts=task_created=task_deleted=0;
sem_fail_on=task_fail_on=worker_runs=sem_gives=sem_takes=rollback_error=0;cleanup_advance=0;
task="40000000-0000-4000-8000-000000000001";
cJSON_Delete(manifest_json);manifest_json=NULL;
running=boot_partition=record_count=begun=written=aborted=restarts=rollbacks=marked=downloaded=reported_success=reported_failed=accepted=manifest_requests=0;
mark_error=boot_error=end_error=manifest_error=report_error=busy=maintenance=leaves=short_read=read_error=timer_create_error=timer_start_error=timer_deleted=0;
fail_save_phase=fail_installed=fail_readback=0;fail_receipt_commit=false;last_report[0]=0;monotonic=0;clock_step=1000;
read_script_len=read_script_index=read_calls=timeout_count=write_calls=write_fail_on=http_errno=0;write_fail_code=95;
heartbeat_advance=write_advance=begin_advance=open_advance=header_advance=end_advance=sha_start_advance=sha_update_advance=sha_finish_advance=0;
begin_error=open_error=http_init_error=sha_error_stage=opened=headers_fetched=closed=client_cleaned=ended=boot_sets=transfer_first_read_call=0;begin_error_handle=1;begin_target=NULL;begin_size=0;
alloc_calls=heap_fail_first=heap_frees=0;allocated_size=0;allocated_buffer=NULL;image_size=2048;
memset(alloc_sizes,0,sizeof(alloc_sizes));memset(alloc_caps,0,sizeof(alloc_caps));memset(write_sizes,0,sizeof(write_sizes));memset(read_wanted,0,sizeof(read_wanted));
memset(read_script,0,sizeof(read_script));memset(read_at_offsets,0,sizeof(read_at_offsets));memset(read_advances,0,sizeof(read_advances));last_report_message[0]=0;
platform_error="";
image_states[0]=ESP_OTA_IMG_VALID;image_states[1]=ESP_OTA_IMG_PENDING_VERIFY;
for(size_t i=0;i<sizeof(image_bytes);i++)image_bytes[i]=(i*31+17)%251;
memset(flash,0,sizeof(flash));header_size=image_size;http_status=200;
unsigned char hash[32];char hex[65];SHA256(image_bytes,image_size,hash);hash_hex(hash,hex);
struct timeval now;gettimeofday(&now,NULL);
manifest_json=cJSON_CreateObject();cJSON_AddStringToObject(manifest_json,"task_id",task);cJSON_AddStringToObject(manifest_json,"model","ECHOEAR-S3");
cJSON_AddStringToObject(manifest_json,"version","new-11");cJSON_AddNumberToObject(manifest_json,"version_code",11);cJSON_AddNumberToObject(manifest_json,"size",image_size);
cJSON_AddStringToObject(manifest_json,"sha256",hex);cJSON_AddStringToObject(manifest_json,"download_url","https://platform.example/f/test-ticket");cJSON_AddNumberToObject(manifest_json,"expires_at_ms",(double)now.tv_sec*1000+30000);
reboot_to(0);device_ota_boot_ready();assert(ready);
}
static void set_string(const char *key,const char *value){assert(cJSON_ReplaceItemInObject(manifest_json,key,cJSON_CreateString(value)));}
static void set_number(const char *key,double value){assert(cJSON_ReplaceItemInObject(manifest_json,key,cJSON_CreateNumber(value)));}
static void install(void){device_ota_request(task);allow_restart=true;if(!setjmp(restart_jump))device_ota_poll();allow_restart=false;}
static void boot_ready_allow_rollback(void){allow_restart=true;if(!setjmp(restart_jump))device_ota_boot_ready();allow_restart=false;}
static void original_record(void){installed_t old={.format=1,.address=parts[0].address,.code=10};strcpy(old.version,"old-10");assert(persist("installed",&old,sizeof(old))==0);reboot_to(0);device_ota_boot_ready();assert(device_ota_version_code()==10);}
static void test_normal(void){
fresh();original_record();install();assert(restarts==1&&boot_partition==1&&state.phase==2&&accepted==1);assert(!memcmp(flash[1],image_bytes,image_size));
reboot_to(1);assert(timer_armed);device_ota_boot_ready();assert(ready&&state.phase==3&&device_ota_version_code()==11&&marked==1&&!timer_armed);
device_ota_poll();assert(reported_success==1&&state.phase==0);int prior=manifest_requests;device_ota_request(task);device_ota_poll();assert(prior==manifest_requests&&downloaded==1);
reboot_to(1);device_ota_boot_ready();assert(device_ota_version_code()==11);
}
static void test_rejections(void){
for(int i=0;i<8;i++){fresh();switch(i){case 0:set_string("model","wrong");break;case 1:set_number("size",999999);break;case 2:set_number("version_code",0);break;case 3:set_number("expires_at_ms",1);break;case 4:set_string("sha256","bad");break;case 5:set_string("download_url","https://evil.example/f/test-ticket");break;case 6:set_string("task_id","wrong");break;case 7:busy=1;break;}
install();assert(!begun&&!restarts&&boot_partition==0&&state.phase==4&&!maintenance);device_ota_poll();assert(reported_failed==1);}
fresh();manifest_error=1;install();assert(!begun&&!restarts&&!maintenance);
}
static void test_download_failures(void){
for(int i=0;i<7;i++){fresh();switch(i){case 0:image_bytes[0]^=1;break;case 1:http_status=302;break;case 2:header_size--;break;case 3:short_read=1;break;case 4:read_error=1;break;case 5:end_error=1;break;case 6:clock_step=500000000;break;}
install();assert(!restarts&&boot_partition==0&&state.phase==4&&!maintenance);device_ota_poll();assert(reported_failed==1);}
for(int phase=1;phase<=2;phase++){fresh();fail_save_phase=phase;install();assert(!restarts&&boot_partition==0&&!maintenance);if(phase==1)assert(!accepted&&!begun);}
fresh();report_error=1;install();assert(!begun&&!restarts&&!maintenance);
fresh();boot_error=1;install();assert(!restarts&&boot_partition==0&&state.phase==4);
}
static void script_reads(const int *values,size_t count){assert(count<=64);memcpy(read_script,values,count*sizeof(*values));read_script_len=count;}
static void assert_download_receipt(const char *detail){
assert(state.phase==4&&!restarts&&boot_partition==0&&!maintenance&&aborted==1);
assert(strstr(state.message,detail)&&strlen(state.message)<sizeof(state.message));
assert(!strstr(state.message,"https://")&&!strstr(state.message,"test-ticket"));
char expected[sizeof(state.message)];strcpy(expected,state.message);device_ota_poll();
assert(reported_failed==1&&!strcmp(last_report_message,expected)&&state.phase==0);
}
static void test_read_recovery(void){
fresh();clock_step=0;const int reads[]={-ESP_ERR_HTTP_EAGAIN,-ESP_ERR_HTTP_EAGAIN,512,-ESP_ERR_HTTP_EAGAIN,-ESP_ERR_HTTP_EAGAIN,1536};script_reads(reads,6);
install();assert(restarts==1&&boot_partition==1&&state.phase==2&&downloaded==1&&begun==1&&read_calls==6&&written==2);
assert(!memcmp(flash[1],image_bytes,image_size)&&read_offset==image_size&&write_offset==image_size);
assert(read_at_offsets[0]==0&&read_at_offsets[1]==0&&read_at_offsets[2]==0&&read_at_offsets[3]==512&&read_at_offsets[4]==512&&read_at_offsets[5]==512);
assert(timeout_count==8);for(int i=0;i<timeout_count;i++)assert(timeout_values[i]==20000);
assert(monotonic==80000000&&!aborted&&strstr(state.message,"OTA ms="));
}
static void test_read_stall_limit(void){
fresh();clock_step=0;const int reads[]={-ESP_ERR_HTTP_EAGAIN,-ESP_ERR_HTTP_EAGAIN,-ESP_ERR_HTTP_EAGAIN,2048};script_reads(reads,4);
install();assert(read_calls==3&&downloaded==1&&begun==1&&!written&&read_offset==0&&write_offset==0&&monotonic==60000000);
assert_download_receipt("OTA read e=0x7007 off=0/2048 rc=-28679 errno=11");
}
static void test_read_eof_and_error(void){
const int failures[]={0,-1,-0x7005};
for(size_t i=0;i<sizeof(failures)/sizeof(failures[0]);i++){
fresh();clock_step=0;const int reads[]={512,failures[i],1536};script_reads(reads,3);install();
assert(read_calls==2&&written==1&&downloaded==1&&read_offset==512&&write_offset==512);
char detail[80];snprintf(detail,sizeof(detail),"OTA read e=0xffffffff off=512/2048 rc=%d errno=%d",failures[i],failures[i]<0?5:0);
assert_download_receipt(detail);
}
}
static void test_write_failure_offset(void){
fresh();clock_step=0;const int reads[]={512,512,1024};script_reads(reads,3);write_fail_on=2;install();
assert(read_calls==2&&write_calls==2&&written==1&&read_offset==1024&&write_offset==512);
assert(!memcmp(flash[1],image_bytes,512)&&flash[1][512]==0xff);
assert_download_receipt("OTA write e=0x5f off=512/2048 rc=512 errno=0");
}
static void test_read_deadline(void){
// A pending heartbeat must not extend the fixed 900-second budget.
fresh();clock_step=0;heartbeat_advance=900000001;install();
assert(!read_calls&&timeout_count==2&&!written);assert_download_receipt("OTA deadline e=0x6 off=0/2048 rc=0 errno=0");
// The last wait is clipped to the remaining 500ms and cannot be retried.
fresh();clock_step=0;heartbeat_advance=899500000;const int stalled[]={-ESP_ERR_HTTP_EAGAIN,2048};script_reads(stalled,2);install();
assert(read_calls==1&&timeout_count==3&&timeout_values[2]==500&&monotonic==900000000&&!written);
assert_download_receipt("OTA deadline e=0x6 off=0/2048 rc=-28679 errno=11");
// Even a positive read that finishes at the deadline cannot start a write.
fresh();clock_step=0;heartbeat_advance=899500000;const int positive[]={512,1536};script_reads(positive,2);read_advances[0]=500000;install();
assert(read_calls==1&&timeout_values[2]==500&&read_offset==512&&!written&&write_offset==0);
assert_download_receipt("OTA deadline e=0x6 off=0/2048 rc=512 errno=0");
// If a write reaches the deadline, diagnostics count only completed writes.
fresh();clock_step=0;script_reads(positive,2);write_advance=900000000;install();
assert(read_calls==1&&written==1&&write_offset==512&&monotonic==900000000);
assert_download_receipt("OTA deadline e=0x6 off=512/2048 rc=512 errno=0");
}
static void test_long_download(void){
fresh();clock_step=0;const int reads[]={1024,1024};script_reads(reads,2);read_advances[0]=read_advances[1]=350000000;install();
assert(restarts==1&&boot_partition==1&&state.phase==2&&monotonic==700000000&&downloaded==1&&begun==1&&written==2&&!aborted);
assert(!memcmp(flash[1],image_bytes,image_size)&&strstr(state.message,"OTA ms="));
}
static void assert_resources_clean(unsigned allocations,unsigned frees,int aborts,int clients){
assert(alloc_calls==allocations&&heap_frees==frees&&!heap_live&&!client_live&&!ota_handle_live);
assert(aborted==aborts&&closed==clients&&client_cleaned==clients);
}
static void assert_failed_safe(const char *detail,unsigned allocations,unsigned frees,int aborts,int clients){
assert(state.phase==4&&!restarts&&!boot_sets&&boot_partition==0&&!maintenance);
assert(strstr(state.message,detail)&&!strstr(state.message,"OTA ms=")&&!strstr(state.message,"https://")&&!strstr(state.message,"test-ticket"));
assert_resources_clean(allocations,frees,aborts,clients);
char expected[sizeof(state.message)];strcpy(expected,state.message);device_ota_poll();
assert(reported_failed==1&&!strcmp(last_report_message,expected)&&state.phase==0);
}
static void test_known_size_preerase(void){
fresh();clock_step=0;memset(flash[0],0xa7,sizeof(flash[0]));install();
assert(restarts==1&&boot_partition==1&&boot_sets==1&&begun==1&&begin_target==&parts[1]&&begin_size==image_size);
assert(opened==1&&headers_fetched==1&&read_calls==1&&read_wanted[0]==image_size&&write_sizes[0]==image_size);
assert(alloc_sizes[0]==8192&&alloc_caps[0]==(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
assert(!memcmp(flash[1],image_bytes,image_size));
for(size_t i=0;i<sizeof(flash[0]);i++)assert(flash[0][i]==0xa7);
for(size_t i=image_size;i<4096;i++)assert(flash[1][i]==0xff);
assert(flash[1][4096]==0);assert_resources_clean(1,1,0,1);
}
static void test_target_guard(void){
for(int i=0;i<5;i++){
fresh();clock_step=0;state.size=image_size;
esp_partition_t alias=parts[0];const esp_partition_t *target=&parts[1];
if(i==0)target=&parts[0];else if(i==1)target=&alias;else if(i==2)target=NULL;
else if(i==3)state.size=1023;else state.size=parts[1].size+1;
assert(download("https://platform.example/f/test-ticket",target)==ESP_ERR_INVALID_SIZE);
assert(!begun&&!downloaded&&!opened&&!headers_fetched&&!read_calls&&!written&&!restarts&&!boot_sets);
assert_resources_clean(0,0,0,0);
}
}
static void test_begin_error_and_deadline(void){
for(int returns_handle=0;returns_handle<2;returns_handle++){
fresh();clock_step=0;begin_error=ESP_FAIL;begin_error_handle=returns_handle;begin_advance=7000;install();
assert(begun==1&&begin_size==image_size&&!read_calls&&!written&&!ended&&monotonic==7000);
assert_failed_safe("OTA begin e=0xffffffff off=0/2048 rc=0 errno=0",1,1,returns_handle,1);
}
// Preerase consumes the same fixed budget, rather than creating a new one.
fresh();clock_step=0;begin_advance=900000000;install();
assert(begun==1&&!read_calls&&!written&&!ended&&monotonic==900000000&&timeout_count==2);
assert_failed_safe("OTA deadline e=0x6 off=0/2048 rc=0 errno=0",1,1,1,1);
// Only 500ms remains after erase; the first read cannot obtain a fresh 20s.
fresh();clock_step=0;begin_advance=899500000;const int stalled[]={-ESP_ERR_HTTP_EAGAIN,2048};script_reads(stalled,2);install();
assert(read_calls==1&&timeout_count==3&&timeout_values[0]==20000&&timeout_values[1]==20000&&timeout_values[2]==500&&!written);
assert_failed_safe("OTA deadline e=0x6 off=0/2048 rc=-28679 errno=11",1,1,1,1);
}
static void test_setup_deadlines(void){
// Neither open nor headers gets to reset the deadline or begin erasing late.
fresh();clock_step=0;open_advance=900000000;install();
assert(opened==1&&!headers_fetched&&!begun&&!read_calls&&timeout_count==1);
assert_failed_safe("OTA deadline e=0x6 off=0/2048 rc=0 errno=0",1,1,0,1);
fresh();clock_step=0;header_advance=900000000;install();
assert(opened==1&&headers_fetched==1&&!begun&&!read_calls&&timeout_count==2);
assert_failed_safe("OTA deadline e=0x6 off=0/2048 rc=0 errno=0",1,1,0,1);
fresh();clock_step=0;open_advance=899500000;header_advance=500000;install();
assert(timeout_count==2&&timeout_values[0]==20000&&timeout_values[1]==500&&!begun&&!read_calls);
assert_failed_safe("OTA deadline e=0x6 off=0/2048 rc=0 errno=0",1,1,0,1);
// SHA finalization and SDK end are inside the budget even after all writes.
fresh();clock_step=0;sha_finish_advance=900000000;install();
assert(written==1&&write_offset==image_size&&!ended);
assert_failed_safe("OTA deadline e=0x6 off=2048/2048",1,1,1,1);
fresh();clock_step=0;end_advance=900000000;install();assert(ended==1&&written==1);
assert_failed_safe("OTA deadline e=0x6 off=2048/2048",1,1,0,1);
}
static void test_buffer_fallbacks(void){
for(unsigned failures=0;failures<2;failures++){
fresh();clock_step=0;heap_fail_first=failures;image_size=sizeof(image_bytes);header_size=image_size;
unsigned char digest[32];char hex[65];SHA256(image_bytes,image_size,digest);hash_hex(digest,hex);set_number("size",image_size);set_string("sha256",hex);
install();size_t chunk=failures?4096:8192;unsigned count=(image_size+chunk-1)/chunk;
assert(restarts==1&&boot_partition==1&&begun==1&&begin_size==image_size&&read_calls==(int)count&&written==(int)count);
assert(allocated_size==chunk&&alloc_sizes[0]==8192&&(!failures||alloc_sizes[1]==4096));
for(unsigned i=0;i<alloc_calls;i++)assert(alloc_caps[i]==(MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
for(unsigned i=0;i<count;i++){size_t expected=i+1<count?chunk:image_size-i*chunk;assert(read_wanted[i]==expected&&write_sizes[i]==expected);}
assert(read_offset==image_size&&write_offset==image_size&&!memcmp(flash[1],image_bytes,image_size));
char expected[20];snprintf(expected,sizeof(expected),"buf=%u",(unsigned)chunk);assert(strstr(state.message,expected));
assert_resources_clean(failures+1,1,0,1);
// Verify the large image through the real post-boot flash SHA path too.
reboot_to(1);device_ota_boot_ready();assert(ready&&state.phase==3&&device_ota_version_code()==11);device_ota_poll();assert(reported_success==1);
}
fresh();clock_step=0;heap_fail_first=2;install();
assert(!downloaded&&!opened&&!headers_fetched&&!begun&&!read_calls&&!written);
assert(alloc_sizes[0]==8192&&alloc_sizes[1]==4096);assert_failed_safe("OTA failed:",2,0,0,0);
}
static void test_failure_cleanup(void){
fresh();clock_step=0;http_init_error=1;install();assert(downloaded==1&&!opened&&!begun);assert_failed_safe("OTA failed:",1,1,0,0);
fresh();clock_step=0;open_error=ESP_FAIL;install();assert(opened==1&&!headers_fetched&&!begun);assert_failed_safe("OTA open",1,1,0,1);
for(int bad_header=0;bad_header<2;bad_header++){
fresh();clock_step=0;if(bad_header)header_size--;else http_status=302;install();
assert(headers_fetched==1&&!begun&&!read_calls);assert_failed_safe("OTA headers",1,1,0,1);
}
for(int sha_stage=1;sha_stage<=3;sha_stage++){
fresh();clock_step=0;sha_error_stage=sha_stage;install();assert(begun==1&&!ended);
assert_failed_safe(sha_stage==1?"OTA sha.init":sha_stage==2?"OTA sha.upd":"OTA sha.end",1,1,1,1);
}
fresh();clock_step=0;image_bytes[0]^=1;install();assert(!ended);assert_failed_safe("OTA sha.end e=0x7",1,1,1,1);
fresh();clock_step=0;end_error=1;install();assert(ended==1);assert_failed_safe("OTA end",1,1,0,1);
// Successful download metrics must never conceal later journal or boot failure.
fresh();clock_step=0;fail_save_phase=2;install();assert(ended==1);assert_failed_safe("OTA journal failed:",1,1,0,1);
fresh();clock_step=0;boot_error=1;install();assert(ended==1&&boot_sets==1&&state.phase==4&&!restarts&&boot_partition==0);
assert(strstr(state.message,"OTA boot selection failed:")&&!strstr(state.message,"OTA ms="));assert_resources_clean(1,1,0,1);
}
static void test_success_metrics(void){
fresh();clock_step=0;begin_advance=7000;open_advance=11000;header_advance=13000;end_advance=19000;write_advance=5000;sha_update_advance=3000;
const int reads[]={1024,1024};script_reads(reads,2);read_advances[0]=17000;read_advances[1]=23000;install();
const char *expected="OTA ms=106 erase=7 read=40 write=10 sha=6 buf=8192";
assert(restarts==1&&state.phase==2&&monotonic==106000&&!strcmp(state.message,expected));assert_resources_clean(1,1,0,1);
reboot_to(1);assert(!strcmp(state.message,expected));device_ota_boot_ready();assert(state.phase==3&&!strcmp(state.message,expected));
device_ota_poll();assert(reported_success==1&&!strcmp(last_report_message,expected)&&state.phase==0);
reboot_to(1);device_ota_boot_ready();assert(device_ota_version_code()==11&&!strcmp(state.message,expected));
}
static void test_power_and_boot(void){
fresh();original_record();install();reboot_to(0);device_ota_boot_ready();assert(state.phase==4&&ready&&device_ota_version_code()==10);device_ota_poll();assert(reported_failed==1);
fresh();install();flash[1][0]^=1;reboot_to(1);boot_ready_allow_rollback();assert(rollbacks==1&&boot_partition==0&&!ready);
fresh();install();reboot_to(1);assert(timer_callback&&timer_armed);allow_restart=true;if(!setjmp(restart_jump))timer_callback(NULL);allow_restart=false;assert(restarts==2&&!ready);
fresh();fail_save_phase=2;install();reboot_to(0);fail_save_phase=0;device_ota_boot_ready();assert(ready&&state.phase==4);
}
static void test_metadata_failure(void){
fresh();original_record();install();reboot_to(1);mark_error=1;device_ota_boot_ready();assert(!ready&&timer_armed);mark_error=0;
reboot_to(0);device_ota_boot_ready();assert(ready&&state.phase==4);assert(device_ota_version_code()==10);assert(!strcmp(device_ota_version(),"old-10"));
// Once both slots have been used, failing the next candidate still restores
// the exact version metadata for the previous, already updated slot.
fresh();original_record();install();reboot_to(1);device_ota_boot_ready();device_ota_poll();
task="40000000-0000-4000-8000-000000000002";set_string("task_id",task);set_string("version","new-12");set_number("version_code",12);
install();image_states[0]=ESP_OTA_IMG_PENDING_VERIFY;reboot_to(0);mark_error=1;device_ota_boot_ready();assert(!ready);mark_error=0;
reboot_to(1);device_ota_boot_ready();assert(ready&&device_ota_version_code()==11&&!strcmp(device_ota_version(),"new-11"));
fresh();original_record();install();reboot_to(1);fail_installed=1;device_ota_boot_ready();assert(!ready&&timer_armed);fail_installed=0;reboot_to(0);device_ota_boot_ready();assert(device_ota_version_code()==10);
}
static void test_timer_failure(void){
for(int i=0;i<2;i++){fresh();install();if(i)timer_start_error=1;else timer_create_error=1;
allow_restart=true;if(!setjmp(restart_jump))reboot_to(1);allow_restart=false;assert(rollbacks==1&&boot_partition==0&&!ready);}
}
static void test_numeric_validation(void){
for(int i=0;i<3;i++){fresh();set_number("version_code",i==0?11.5:i==1?1e100:INFINITY);install();assert(!begun&&!restarts);}
}
static void test_corrupt_records(void){
fresh();original_record();fail_readback=1;reboot_to(0);device_ota_boot_ready();assert(!ready&&!device_ota_supported());device_ota_request(task);device_ota_poll();assert(!begun);
fresh();install();for(int i=0;i<record_count;i++)if(!strcmp(records[i].key,"ota"))records[i].size=3;
reboot_to(1);device_ota_boot_ready();assert(!ready&&timer_armed);assert(!marked);
}
static void test_receipt_and_manifest_recovery(void){
fresh();manifest_error=1;install();assert(manifest_requests==1&&!begun&&!maintenance);
manifest_error=0;allow_restart=true;if(!setjmp(restart_jump))device_ota_poll();allow_restart=false;
assert(manifest_requests==2&&restarts==1&&boot_partition==1);
fresh();manifest_error=1;install();device_ota_poll();device_ota_poll();device_ota_poll();
assert(manifest_requests==3&&!begun&&!maintenance);
fresh();manifest_error=1;platform_error="OTA_TASK_UNAVAILABLE";install();device_ota_poll();assert(manifest_requests==1);
fresh();install();reboot_to(1);device_ota_boot_ready();
// Force the terminal receipt's phase=0 commit to fail and retain the journal.
fail_receipt_commit=true;device_ota_poll();assert(state.phase==3);
fail_receipt_commit=false;device_ota_poll();assert(state.phase==0);
fresh();install();reboot_to(1);device_ota_boot_ready();
report_error=1;platform_error="OTA_FINISHED";device_ota_poll();assert(state.phase==0);
fresh();install();reboot_to(1);device_ota_boot_ready();report_error=1;
device_ota_poll();assert(state.phase==3);report_error=0;device_ota_poll();assert(state.phase==0);
}
static void test_internal_finalize(void){
// The first internal operation finalizes an active OTA handle. Allocation
// failure must abort it, whereas an SDK end failure has consumed it already.
fresh();clock_step=0;sem_fail_on=1;install();
assert(ended==0&&aborted==1&&task_attempts==0&&state.phase==4&&boot_partition==0&&!restarts);
assert_resources_clean(1,1,1,1);assert_worker_cleanup();
fresh();clock_step=0;task_fail_on=1;install();
assert(ended==0&&aborted==1&&sem_created==1&&sem_deleted==1&&task_created==0&&state.phase==4&&boot_partition==0&&!restarts);
assert_resources_clean(1,1,1,1);assert_worker_cleanup();
fresh();clock_step=0;end_error=1;install();
assert(ended==1&&aborted==0&&boot_sets==0&&task_created==1&&state.phase==4&&boot_partition==0&&!restarts);
assert_resources_clean(1,1,0,1);assert_worker_cleanup();
// The second operation selects the candidate. It must never abort an ended
// SDK handle or restart after either allocation or boot-selection failure.
fresh();clock_step=0;sem_fail_on=2;install();
assert(ended==1&&aborted==0&&boot_sets==0&&task_created==1&&state.phase==4&&boot_partition==0&&!restarts);
assert(strstr(state.message,"OTA boot selection failed:"));assert_worker_cleanup();
fresh();clock_step=0;task_fail_on=2;install();
assert(ended==1&&aborted==0&&boot_sets==0&&task_created==1&&sem_created==2&&state.phase==4&&boot_partition==0&&!restarts);
assert_worker_cleanup();
fresh();clock_step=0;boot_error=1;install();
assert(ended==1&&aborted==0&&boot_sets==1&&task_created==2&&state.phase==4&&boot_partition==0&&!restarts);
assert_worker_cleanup();
fresh();clock_step=0;install();
assert(ended==1&&boot_sets==1&&restarts==1&&boot_partition==1&&state.phase==2&&task_created==2);
assert_resources_clean(1,1,0,1);assert_worker_cleanup();
// A global deadline reached while releasing TLS prevents verification.
fresh();clock_step=0;cleanup_advance=900000000;install();
assert(ended==0&&aborted==1&&task_attempts==0&&state.phase==4&&strstr(state.message,"OTA deadline"));
assert_resources_clean(1,1,1,1);assert_worker_cleanup();
// If verification consumed the handle before its deadline expires, the
// caller records timeout and retains the old slot without aborting it again.
fresh();clock_step=0;end_advance=900000000;install();
assert(ended==1&&aborted==0&&boot_sets==0&&state.phase==4&&boot_partition==0&&strstr(state.message,"OTA deadline"));
assert_worker_cleanup();
// Completion distinguishes a worker allocation error from a completed SDK
// operation error; callers use this to maintain the handle's ownership.
fresh();bool completed=true;sem_fail_on=1;
assert(ota_internal_flash_call(OTA_FLASH_BOOT,0,&parts[0],&completed)==ESP_ERR_NO_MEM&&!completed);assert_worker_cleanup();
fresh();completed=true;task_fail_on=1;
assert(ota_internal_flash_call(OTA_FLASH_BOOT,0,&parts[0],&completed)==ESP_ERR_NO_MEM&&!completed);assert_worker_cleanup();
fresh();completed=false;boot_error=1;
assert(ota_internal_flash_call(OTA_FLASH_BOOT,0,&parts[0],&completed)==ESP_FAIL&&completed);assert_worker_cleanup();
}
static void test_internal_boot_and_rollback(void){
// First serial-installed image establishes its running slot as fallback.
fresh();clock_step=0;image_states[0]=-1;reboot_to(0);device_ota_boot_ready();
assert(ready&&boot_sets==1&&marked==1&&boot_partition==0&&task_created==1);assert_worker_cleanup();
fresh();image_states[0]=-1;reboot_to(0);task_fail_on=1;device_ota_boot_ready();
assert(!ready&&boot_sets==0&&marked==0&&boot_partition==0);assert_worker_cleanup();
fresh();image_states[0]=-1;reboot_to(0);boot_error=1;device_ota_boot_ready();
assert(!ready&&boot_sets==1&&marked==0&&boot_partition==0);assert_worker_cleanup();
// Corrupt candidate executes rollback internally; reset happens in parent
// only after both worker and semaphore have been deleted.
fresh();install();flash[1][0]^=1;reboot_to(1);boot_ready_allow_rollback();
assert(rollbacks==1&&boot_partition==0&&restarts==2&&!ready&&task_created==3);assert_worker_cleanup();
fresh();install();flash[1][0]^=1;reboot_to(1);rollback_error=1;device_ota_boot_ready();
assert(rollbacks==1&&boot_partition==1&&restarts==1&&!ready&&timer_armed&&state.phase==4);assert_worker_cleanup();
for(int fail=0;fail<2;fail++){
fresh();install();flash[1][0]^=1;reboot_to(1);
if(fail)task_fail_on=task_attempts+1;else sem_fail_on=sem_attempts+1;
device_ota_boot_ready();assert(rollbacks==0&&boot_partition==1&&restarts==1&&!ready&&timer_armed);assert_worker_cleanup();
}
// Failed health-timer setup must use the same safe worker, then restart even
// if explicit rollback fails: the bootloader owns pending-image rollback.
for(int fail=0;fail<2;fail++){
fresh();install();timer_create_error=1;rollback_error=fail;allow_restart=true;
if(!setjmp(restart_jump))reboot_to(1);allow_restart=false;
assert(rollbacks==1&&restarts==2&&!ready&&boot_partition==(fail?1:0));assert_worker_cleanup();
}
fresh();bool completed=false;rollback_error=1;
assert(ota_internal_flash_call(OTA_FLASH_ROLLBACK,0,NULL,&completed)==ESP_FAIL&&completed);
assert(rollbacks==1&&!restarts&&boot_partition==0);assert_worker_cleanup();
}
int main(int argc,char **argv){
const char *pick=argc>1?argv[1]:"all";
if(!strcmp(pick,"all")||!strcmp(pick,"normal"))test_normal();
if(!strcmp(pick,"all")||!strcmp(pick,"rejections"))test_rejections();
if(!strcmp(pick,"all")||!strcmp(pick,"download"))test_download_failures();
if(!strcmp(pick,"all")||!strcmp(pick,"read-recovery"))test_read_recovery();
if(!strcmp(pick,"all")||!strcmp(pick,"read-stall"))test_read_stall_limit();
if(!strcmp(pick,"all")||!strcmp(pick,"read-eof"))test_read_eof_and_error();
if(!strcmp(pick,"all")||!strcmp(pick,"write-offset"))test_write_failure_offset();
if(!strcmp(pick,"all")||!strcmp(pick,"deadline"))test_read_deadline();
if(!strcmp(pick,"all")||!strcmp(pick,"long-download"))test_long_download();
if(!strcmp(pick,"all")||!strcmp(pick,"preerase"))test_known_size_preerase();
if(!strcmp(pick,"all")||!strcmp(pick,"target-guard"))test_target_guard();
if(!strcmp(pick,"all")||!strcmp(pick,"erase-deadline"))test_begin_error_and_deadline();
if(!strcmp(pick,"all")||!strcmp(pick,"setup-deadline"))test_setup_deadlines();
if(!strcmp(pick,"all")||!strcmp(pick,"buffer"))test_buffer_fallbacks();
if(!strcmp(pick,"all")||!strcmp(pick,"cleanup"))test_failure_cleanup();
if(!strcmp(pick,"all")||!strcmp(pick,"metrics"))test_success_metrics();
if(!strcmp(pick,"all")||!strcmp(pick,"power"))test_power_and_boot();
if(!strcmp(pick,"all")||!strcmp(pick,"metadata"))test_metadata_failure();
if(!strcmp(pick,"all")||!strcmp(pick,"timer"))test_timer_failure();
if(!strcmp(pick,"all")||!strcmp(pick,"numeric"))test_numeric_validation();
if(!strcmp(pick,"all")||!strcmp(pick,"corrupt"))test_corrupt_records();
if(!strcmp(pick,"all")||!strcmp(pick,"recovery"))test_receipt_and_manifest_recovery();
if(!strcmp(pick,"all")||!strcmp(pick,"internal-finalize"))test_internal_finalize();
if(!strcmp(pick,"all")||!strcmp(pick,"internal-boot-rollback"))test_internal_boot_and_rollback();
assert_worker_cleanup();assert(!heap_live&&!client_live&&!ota_handle_live&&!sha_live);cJSON_Delete(manifest_json);printf("PASS production OTA: %s\n",pick);return 0;
}
@@ -0,0 +1,7 @@
// Compile the complete production OTA state machine with real SHA-256 and
// injected ESP partition, HTTP, NVS, timer and restart failures.
const fs=require('fs'),path=require('path');
const root=path.resolve(__dirname,'..'),json=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const source=fs.readFileSync(path.join(root,'main/device_ota.c'),'utf8').replace(/^#include[^\n]*\n/gm,'');
const harness=fs.readFileSync(path.join(__dirname,'device-ota-host.c'),'utf8');
process.stdout.write(fs.readFileSync(path.join(json,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(json,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+'\n'+harness.replace('/* PRODUCTION_OTA */',source));
@@ -0,0 +1,56 @@
// Execute the production power transition code with failing hardware/NVS doubles.
const fs=require('fs'), path=require('path');
const root=path.resolve(__dirname,'..');
const json=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const source=fs.readFileSync(path.join(root,'main/device_power.c'),'utf8').replace(/^#include[^\n]*\n/gm,'');
process.stdout.write(fs.readFileSync(path.join(json,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(json,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+`
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
typedef int esp_err_t;
typedef int nvs_handle_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_INVALID_ARG 1
#define ESP_ERR_INVALID_STATE 2
#define ESP_ERR_NVS_NOT_FOUND 3
#define NVS_READONLY 0
#define NVS_READWRITE 1
typedef struct {void *display_handle;} volc_hal_context_t;
static volc_hal_context_t context={(void *)1};
static bool held,busy,standby,paused;
static int brightness=80,stored=-1,staged=-1,commits,storage_error,audio_error,display_error;
static bool call_client_enter_maintenance(void){if(held||busy)return false;held=true;return true;}
static void call_client_leave_maintenance(void){held=false;}
static int call_client_set_standby(bool value){if(busy)return ESP_ERR_INVALID_STATE;standby=value;return 0;}
static bool call_client_standby(void){return standby;}
static int audio_recorder_set_standby(bool value){if(audio_error)return -1;paused=value;return 0;}
static bool audio_recorder_is_standby(void){return paused;}
static uint32_t audio_recorder_microphone_reads(void){return 42;}
static volc_hal_context_t *volc_get_global_hal_context(void){return &context;}
static int volc_hal_display_get_brightness(void *display){return brightness;}
static int volc_hal_display_set_brightness(void *display,int value){if(display_error)return -1;brightness=value;return 0;}
static int nvs_open(const char *name,int mode,int *n){*n=1;return 0;}
static int nvs_get_u8(int n,const char *key,uint8_t *value){if(stored<0)return ESP_ERR_NVS_NOT_FOUND;*value=stored;return 0;}
static int nvs_set_u8(int n,const char *key,uint8_t value){staged=value;return 0;}
static int nvs_commit(int n){if(storage_error)return -1;stored=staged;commits++;return 0;}
static void nvs_close(int n){}
`+source+`
static void expect_mode(const char *mode){cJSON *j=device_power_report();assert(!strcmp(cJSON_GetObjectItem(j,"mode")->valuestring,mode));cJSON_Delete(j);}
int main(void){
assert(device_power_boot()==0);assert(!standby&&!paused);expect_mode("active");
busy=true;assert(device_power_apply("standby",false)!=0);assert(!standby&&!paused&&brightness==80&&commits==0);busy=false;
assert(device_power_apply("off",false)!=0);
assert(device_power_apply("standby",false)==0);assert(standby&&paused&&brightness==0&&stored==1&&!held);expect_mode("standby");
int previous=commits;assert(device_power_apply("standby",false)==0);assert(commits==previous);
held=true;assert(device_power_apply("active",true)==0);assert(held&&!standby&&!paused&&brightness==80&&stored==0);held=false;
display_error=1;assert(device_power_apply("standby",false)!=0);expect_mode("transition");assert(stored==0&&!held);display_error=0;
assert(device_power_apply("active",false)==0);
audio_error=1;assert(device_power_apply("standby",false)!=0);assert(stored==0&&!held);audio_error=0;
storage_error=1;assert(device_power_apply("standby",false)!=0);expect_mode("standby");assert(stored==0&&!held);storage_error=0;
assert(device_power_apply("standby",false)==0);assert(stored==1);
standby=paused=false;brightness=80;assert(device_power_boot()==0);expect_mode("standby");
assert(device_power_apply("active",false)==0);expect_mode("active");
puts("PASS power transitions: busy exclusion, real state, failure paths, persistent standby, default wake and no repeated flash writes");
}
`);
@@ -0,0 +1,63 @@
// Production durable command receipts with NVS and lost-response injection.
const fs=require('fs'),path=require('path');
const source=fs.readFileSync(path.join(__dirname,'../main/device_control.c'),'utf8');
const json=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const receipts=source.slice(source.indexOf('static esp_err_t save_result('),source.indexOf('static esp_err_t apply_config('));
process.stdout.write(fs.readFileSync(path.join(json,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(json,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+`
#include <assert.h>
#include <stdbool.h>
typedef int esp_err_t;
typedef int nvs_handle_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_NO_MEM -2
#define ESP_ERR_NVS_NOT_FOUND 1
#define NVS_READONLY 0
#define NVS_READWRITE 1
static bool namespace_exists;
static char durable[4096],staged[4096];
static int open_error,size_error,read_error,commit_error,write_error,posts,network_error;
static const char *boot_id="boot-A",*error_code="";
static const char *id="40000000-0000-4000-8000-000000000001";
static int nvs_open(const char *ns,int mode,nvs_handle_t *n){
assert(!strcmp(ns,"control"));if(open_error)return ESP_FAIL;
if(!namespace_exists&&mode==NVS_READONLY)return ESP_ERR_NVS_NOT_FOUND;
namespace_exists=true;strcpy(staged,durable);*n=1;return ESP_OK;
}
static int nvs_set_str(int n,const char *key,const char *value){assert(!strcmp(key,"result"));if(write_error)return ESP_FAIL;assert(strlen(value)<sizeof(staged));strcpy(staged,value);return ESP_OK;}
static int nvs_erase_key(int n,const char *key){assert(!strcmp(key,"result"));if(write_error)return ESP_FAIL;staged[0]=0;return ESP_OK;}
static int nvs_commit(int n){if(commit_error)return ESP_FAIL;strcpy(durable,staged);return ESP_OK;}
static int nvs_get_str(int n,const char *key,char *out,size_t *size){
assert(!strcmp(key,"result"));if(!durable[0])return ESP_ERR_NVS_NOT_FOUND;
if(!out){if(size_error)return ESP_FAIL;*size=strlen(durable)+1;return ESP_OK;}
if(read_error)return ESP_FAIL;assert(*size>=strlen(durable)+1);strcpy(out,durable);*size=strlen(durable)+1;return ESP_OK;
}
static void nvs_close(int n){}
static const char *ble_cloud_boot_id(void){return boot_id;}
static const char *platform_last_error_code(void){return error_code;}
static const char *text(const cJSON *j,const char *k){const cJSON *v=cJSON_GetObjectItemCaseSensitive(j,k);return cJSON_IsString(v)?v->valuestring:"";}
static int post(const char *path,cJSON *j,cJSON **out){
assert(!strcmp(path,"/api/v1/device/control/ack"));assert(!strcmp(text(j,"command_id"),id));
assert(!cJSON_GetObjectItemCaseSensitive(j,"_reboot_from"));posts++;return network_error?ESP_FAIL:ESP_OK;
}
`+receipts+`
static void reset(void){namespace_exists=false;durable[0]=staged[0]=0;open_error=size_error=read_error=commit_error=write_error=posts=network_error=0;boot_id="boot-A";error_code="";}
static void pending(void){assert(ack(id,"SUCCEEDED",NULL,true)==ESP_OK);assert(durable[0]&&posts==0);}
int main(int argc,char **argv){
reset();assert(flush_result());
// Unknown storage state must block command fetching, never mean no receipt.
if(argc>1&&!strcmp(argv[1],"open-error")){open_error=1;assert(!flush_result());puts("PASS NVS read failure blocks commands");return 0;}
reset();pending();assert(!flush_result()&&posts==0);
boot_id="boot-B";network_error=1;assert(!flush_result()&&durable[0]&&posts==1);
// A lost ACK response and a failed local erase both retain the same receipt.
network_error=0;commit_error=1;assert(!flush_result()&&durable[0]&&posts==2);
commit_error=0;assert(flush_result()&&!durable[0]&&posts==3);assert(flush_result()&&posts==3);
for(int fault=0;fault<3;fault++){reset();pending();boot_id="boot-B";if(fault==0)open_error=1;if(fault==1)size_error=1;if(fault==2)read_error=1;assert(!flush_result()&&posts==0&&durable[0]);}
reset();namespace_exists=true;strcpy(durable,"not JSON");assert(!flush_result()&&posts==0);
reset();write_error=1;assert(ack(id,"SUCCEEDED",NULL,true)!=ESP_OK&&posts==0&&!durable[0]);
reset();commit_error=1;assert(ack(id,"SUCCEEDED",NULL,true)!=ESP_OK&&posts==0&&!durable[0]);
reset();pending();boot_id="boot-B";network_error=1;error_code="DEVICE_COMMAND_STATE_CONFLICT";assert(flush_result()&&!durable[0]);
reset();network_error=1;assert(ack(id,"SUCCEEDED",NULL,false)==ESP_OK&&durable[0]);network_error=0;assert(flush_result()&&!durable[0]);
puts("PASS production receipts: boot guard, lost responses, durable retries and NVS failures");return 0;
}
`);
@@ -0,0 +1,75 @@
// Execute the actual reset implementation with cloud/NVS fault injection.
const fs=require('fs'),path=require('path');
const root=path.resolve(__dirname,'..'),json=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const source=fs.readFileSync(path.join(root,'main/device_reset.c'),'utf8').replace(/^#include[^\n]*\n/gm,'');
process.stdout.write(fs.readFileSync(path.join(json,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(json,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+`
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
typedef int esp_err_t;
typedef int nvs_handle_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_NVS_NOT_FOUND 1
#define ESP_ERR_INVALID_STATE 2
#define ESP_ERR_INVALID_ARG 3
#define ESP_ERR_INVALID_RESPONSE 4
#define ESP_ERR_NO_MEM 5
#define ESP_ERR_INVALID_SIZE 6
#define NVS_READONLY 0
#define NVS_READWRITE 1
static const char *spaces[]={"factory_reset","voice","platform","control","ble_setup","wifi"};
static unsigned char durable[1024],staged[1024];static size_t durable_len,staged_len;
static int erased,posts,deny_state,deny_reset,bad_response,fail_phase,fail_clear,fail_save_once;
static char first_body[512];
static void mbedtls_platform_zeroize(void *p,size_t n){memset(p,0,n);}
static bool ble_cloud_configured(void){return true;}
static int ble_cloud_sync_time(void){return 0;}
static const char *device_ota_version(void){return "1.7.0-controls.test";}
static int nvs_open(const char *space,int mode,int *n){for(int i=0;i<6;i++)if(!strcmp(space,spaces[i])){*n=i;return 0;}assert(!"forbidden namespace");return -1;}
static void nvs_close(int n){}
static int nvs_set_blob(int n,const char *key,const void *v,size_t size){assert(n==0&&!strcmp(key,"record")&&size<=sizeof(staged));memcpy(staged,v,size);staged_len=size;return 0;}
static int nvs_get_blob(int n,const char *key,void *v,size_t *size){assert(n==0&&!strcmp(key,"record"));if(!durable_len)return ESP_ERR_NVS_NOT_FOUND;assert(*size>=durable_len);memcpy(v,durable,durable_len);*size=durable_len;return 0;}
static int nvs_commit(int n){
if(n==0){uint32_t phase=0;memcpy(&phase,staged+4,4);if(fail_save_once&&phase==(unsigned)fail_phase){fail_save_once=0;return -1;}memcpy(durable,staged,staged_len);durable_len=staged_len;}
return 0;
}
static int nvs_erase_all(int n){assert(n==1||n==4||n==5);if(fail_clear==n)return -1;erased|=1<<n;return 0;}
static int nvs_erase_key(int n,const char *key){assert(n==2||n==3);assert(strcmp(key,"device_id")&&strcmp(key,"device_secret")&&strcmp(key,"ota")&&strcmp(key,"installed"));if(fail_clear==n)return -1;erased|=1<<n;return 0;}
static int platform_request(const char *method,const char *path,const char *body,const char *id,char **raw){
assert(!strcmp(method,"POST"));*raw=NULL;
if(!strcmp(path,"/api/v1/device/factory-reset/state")){if(deny_state)return ESP_ERR_INVALID_RESPONSE;*raw=strdup("{\\"ok\\":true,\\"data\\":{\\"reset_counter\\":0}}");return 0;}
assert(!strcmp(path,"/api/v1/device/factory-reset"));posts++;
if(first_body[0])assert(!strcmp(first_body,body));else strcpy(first_body,body);
if(deny_reset)return ESP_FAIL;
cJSON *request=cJSON_Parse(body);assert(request);
const char *rid=cJSON_GetObjectItem(request,"reset_id")->valuestring;
char response[1024];snprintf(response,sizeof(response),"{\\"ok\\":true,\\"data\\":{\\"reset_id\\":\\"%s\\",\\"reset_counter\\":%d,\\"activation_state\\":\\"CLAIMABLE\\",\\"license_action\\":\\"RETAINED\\",\\"local_actions\\":[\\"clear_volc_credential\\",\\"clear_user_context\\",\\"clear_session_state\\",\\"clear_network_credentials\\",\\"reboot_to_claimable\\"]}}",rid,bad_response?2:1);
*raw=strdup(response);cJSON_Delete(request);return 0;
}
`+source+`
static const char *rid="40000000-0000-4000-8000-000000000001";
static void fresh(void){memset(durable,0,sizeof(durable));memset(staged,0,sizeof(staged));durable_len=staged_len=0;erased=posts=deny_state=deny_reset=bad_response=fail_phase=fail_clear=fail_save_once=0;first_body[0]=0;}
static int phase(void){reset_record_t r;assert(load(&r)==0);return r.phase;}
int main(void){
fresh();assert(device_reset_boot_cleanup()==0&&erased==0);
deny_state=1;assert(device_reset_from_setup(rid)!=0&&erased==0&&posts==0&&phase()==0);
fresh();deny_reset=1;assert(device_reset_from_setup(rid)!=0&&erased==0&&phase()==1);assert(device_reset_boot_cleanup()==0&&erased==0);
deny_reset=0;assert(device_reset_from_setup(rid)==0&&phase()==3&&erased==62&&posts==2);
assert(device_reset_from_setup(rid)==0&&posts==2); // Lost BLE acknowledgement.
fresh();bad_response=1;assert(device_reset_from_setup(rid)!=0&&erased==0&&phase()==1);
fresh();fail_phase=1;fail_save_once=1;assert(device_reset_from_setup(rid)!=0&&erased==0&&posts==0);
fresh();fail_phase=2;fail_save_once=1;assert(device_reset_from_setup(rid)!=0&&erased==0&&phase()==1);
assert(device_reset_from_setup(rid)==0&&phase()==3&&posts==2);
fresh();fail_clear=5;assert(device_reset_from_setup(rid)!=0&&phase()==2&&erased==30); // Cut before Wi-Fi cleanup.
fail_clear=0;assert(device_reset_boot_cleanup()==0&&phase()==3&&erased==62&&posts==1);
fresh();fail_phase=3;fail_save_once=1;assert(device_reset_from_setup(rid)!=0&&phase()==2&&erased==62);
assert(device_reset_boot_cleanup()==0&&phase()==3&&posts==1);
fresh();durable_len=5;assert(device_reset_boot_cleanup()!=0&&erased==0);
puts("PASS production reset: authorization, exact replay, durable approval, interrupted cleanup, preserved identity/OTA");
}
`);
@@ -0,0 +1,48 @@
// Exercise the production reboot command with persisted-receipt and MQTT spies.
const fs=require('fs'),path=require('path');
const source=fs.readFileSync(path.join(__dirname,'../main/device_control.c'),'utf8');
const json=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const command=source.slice(source.indexOf('static void command('),source.indexOf('void device_control_prepare_restart('));
const shutdown=source.slice(source.indexOf('void device_control_prepare_restart('),source.indexOf('void device_control_heartbeat('));
process.stdout.write(fs.readFileSync(path.join(json,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(json,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+`
#include <assert.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <setjmp.h>
#define ESP_OK 0
#define ESP_LOGW(...) ((void)0)
#define pdMS_TO_TICKS(x) (x)
typedef int esp_err_t;
static void *mqtt;
static atomic_bool connected,stopping;
static int busy,ack_failure,persist_failure,stop_failure,acknowledged,persisted,stopped,restarted,left;
static jmp_buf restart_jump;
static bool call_client_enter_maintenance(void){return !busy;}
static void call_client_leave_maintenance(void){left++;}
static const char *text(const cJSON *j,const char *k){const cJSON *v=cJSON_GetObjectItemCaseSensitive(j,k);return cJSON_IsString(v)?v->valuestring:"";}
static int ack(const char *id,const char *status,cJSON *result,bool reboot){
if(!strcmp(status,"ACKNOWLEDGED")){acknowledged++;return ack_failure;}
if(reboot){assert(!strcmp(status,"SUCCEEDED"));if(persist_failure)return -1;persisted++;return 0;}
assert(strcmp(status,"SUCCEEDED"));return 0;
}
static int esp_mqtt_client_stop(void *client){assert(persisted);assert(!atomic_load(&connected)&&atomic_load(&stopping));stopped++;return stop_failure;}
static void vTaskDelay(int ticks){assert(ticks==100);}
static void esp_restart(void){assert(persisted&&atomic_load(&stopping));if(mqtt)assert(stopped==1);restarted++;longjmp(restart_jump,1);}
static cJSON *diagnostics(void){return NULL;}
static int apply_config(const cJSON *j,bool held){return -1;}
`+shutdown+command+`
static void run(void){
cJSON *j=cJSON_Parse("{\\"id\\":\\"40000000-0000-4000-8000-000000000001\\",\\"type\\":\\"reboot\\"}");
if(!setjmp(restart_jump))command(j,NULL);cJSON_Delete(j);
}
static void reset(void){mqtt=(void*)1;connected=true;stopping=false;busy=ack_failure=persist_failure=stop_failure=acknowledged=persisted=stopped=restarted=left=0;}
int main(void){
reset();run();assert(acknowledged==1&&persisted==1&&stopped==1&&restarted==1&&!left);
reset();busy=1;run();assert(!acknowledged&&!stopped&&!restarted);
reset();ack_failure=-1;run();assert(!persisted&&!stopped&&!restarted&&left==1);
reset();persist_failure=-1;run();assert(!persisted&&!stopped&&!restarted&&left==1);
reset();mqtt=NULL;run();assert(restarted==1&&!stopped);
reset();stop_failure=-1;run();assert(restarted==1&&stopped==1);
puts("PASS production reboot: persist before disconnect, busy/ACK/storage guards, MQTT stop failure");return 0;
}
`);
@@ -0,0 +1,169 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
typedef int esp_err_t;
#define ESP_OK 0
#define ESP_FAIL 1
#define ESP_ERR_NO_MEM 2
#define BIT0 1
#define BIT1 2
#define BIT2 4
#define pdFALSE 0
#define pdMS_TO_TICKS(x) (x)
#define MALLOC_CAP_INTERNAL 1
#define ESP_EVENT_ANY_ID (-1)
#define WIFI_EVENT 1
#define IP_EVENT 2
#define WIFI_EVENT_STA_START 1
#define WIFI_EVENT_STA_DISCONNECTED 2
#define WIFI_EVENT_STA_STOP 3
#define IP_EVENT_STA_GOT_IP 1
#define WIFI_MODE_STA 1
#define WIFI_STORAGE_RAM 1
#define WIFI_IF_STA 0
#define WIFI_ALL_CHANNEL_SCAN 1
#define WIFI_CONNECT_AP_BY_SIGNAL 1
#define WIFI_AUTH_OPEN 0
#define WIFI_PS_NONE 0
#define NVS_READONLY 0
#define NVS_READWRITE 1
#define ESP_ERROR_CHECK(x) assert((x)==ESP_OK)
#define bzero(p,n) memset(p,0,n)
typedef unsigned EventBits_t;
typedef EventBits_t *EventGroupHandle_t;
typedef int esp_event_base_t;
typedef int esp_event_handler_instance_t;
typedef int esp_netif_t;
typedef int nvs_handle_t;
typedef struct { EventGroupHandle_t event_group; char ssid[33],password[64]; int reconnect_count; char ip_address[16]; } network_t;
typedef struct { struct { int ip; } ip_info; } ip_event_got_ip_t;
typedef struct { int8_t rssi; uint8_t primary,bssid[6]; } wifi_ap_record_t;
typedef struct { int static_rx_buf_num,static_tx_buf_num,dynamic_rx_buf_num,cache_tx_buf_num,rx_ba_win; } wifi_init_config_t;
#define WIFI_INIT_CONFIG_DEFAULT() ((wifi_init_config_t){0})
typedef struct { struct { char ssid[32],password[64]; int scan_method,sort_method; struct {int authmode;} threshold; bool bssid_set; uint8_t bssid[6],channel; } sta; } wifi_config_t;
typedef struct { uint8_t *ssid; } wifi_scan_config_t;
typedef struct { bool active; void (*callback)(void *); void *arg; } mock_timer_t;
typedef mock_timer_t *esp_timer_handle_t;
typedef struct { void (*callback)(void *); void *arg; const char *name; } esp_timer_create_args_t;
static EventBits_t event_bits;
static mock_timer_t timer;
static wifi_config_t current_config;
static bool initial_connected,ble_configured,identity_configured,timer_create_failure,commit_failure;
static char saved_ssid[33],saved_password[64];
static uint8_t saved_setup_requested;
static int connect_error,connect_calls,ap_calls,stop_calls,deinit_calls,netif_destroyed,timer_starts;
void handler_event_wifi_st(void *,esp_event_base_t,int32_t,void *);
void handler_event_ip_st(void *,esp_event_base_t,int32_t,void *);
static void mock_log(const char *tag,const char *format,...) {(void)tag;(void)format;}
#define ESP_LOGI(...) mock_log(__VA_ARGS__)
#define ESP_LOGW(...) mock_log(__VA_ARGS__)
#define ESP_LOGE(...) mock_log(__VA_ARGS__)
static const char *esp_err_to_name(int err) {(void)err;return "mock error";}
static unsigned heap_caps_get_free_size(int cap) {(void)cap;return 64000;}
static unsigned heap_caps_get_largest_free_block(int cap) {(void)cap;return 32000;}
static EventGroupHandle_t xEventGroupCreate(void) {event_bits=0;return &event_bits;}
static EventBits_t xEventGroupGetBits(EventGroupHandle_t group) {return *group;}
static EventBits_t xEventGroupSetBits(EventGroupHandle_t group,EventBits_t bits) {return *group|=bits;}
static EventBits_t xEventGroupClearBits(EventGroupHandle_t group,EventBits_t bits) {EventBits_t old=*group;*group&=~bits;return old;}
static EventBits_t xEventGroupWaitBits(EventGroupHandle_t group,EventBits_t bits,int clear,int all,int ticks) {
(void)clear;(void)all;(void)ticks;
if(bits==(BIT0|BIT1)) {
if(initial_connected) {ip_event_got_ip_t e={{1}};handler_event_ip_st(NULL,IP_EVENT,IP_EVENT_STA_GOT_IP,&e);}
else for(int i=0;i<6;i++) handler_event_wifi_st(NULL,WIFI_EVENT,WIFI_EVENT_STA_DISCONNECTED,NULL);
}
return *group;
}
static esp_err_t esp_timer_create(const esp_timer_create_args_t *args,esp_timer_handle_t *out) {
if(timer_create_failure)return ESP_ERR_NO_MEM;
timer.callback=args->callback;timer.arg=args->arg;*out=&timer;return ESP_OK;
}
static esp_err_t esp_timer_start_once(esp_timer_handle_t handle,uint64_t micros) {assert(handle&&!handle->active&&micros==10000000);handle->active=true;timer_starts++;return ESP_OK;}
static esp_err_t esp_timer_stop(esp_timer_handle_t handle) {handle->active=false;return ESP_OK;}
static bool esp_timer_is_active(esp_timer_handle_t handle) {return handle->active;}
static esp_err_t nvs_open(const char *name,int mode,nvs_handle_t *handle) {assert(!strcmp(name,"wifi"));(void)mode;*handle=1;return ESP_OK;}
static esp_err_t nvs_get_u8(nvs_handle_t handle,const char *key,uint8_t *value) {(void)handle;assert(!strcmp(key,"setup_requested"));*value=saved_setup_requested;return ESP_OK;}
static esp_err_t nvs_get_str(nvs_handle_t handle,const char *key,char *value,size_t *len) {(void)handle;const char *s=!strcmp(key,"ssid")?saved_ssid:saved_password;assert(strlen(s)<*len);strcpy(value,s);return ESP_OK;}
static esp_err_t nvs_set_u8(nvs_handle_t handle,const char *key,uint8_t value) {(void)handle;assert(!strcmp(key,"setup_requested"));saved_setup_requested=value;return ESP_OK;}
static esp_err_t nvs_commit(nvs_handle_t handle) {(void)handle;return commit_failure?ESP_FAIL:ESP_OK;}
static void nvs_close(nvs_handle_t handle) {(void)handle;}
static bool platform_identity_is_configured(void) {return identity_configured;}
static bool ble_cloud_configured(void) {return ble_configured;}
static void configure_ap(void) {ap_calls++;}
static esp_err_t esp_netif_init(void) {return ESP_OK;}
static esp_err_t esp_event_handler_instance_register(esp_event_base_t base,int id,void (*cb)(void *,esp_event_base_t,int32_t,void *),void *arg,esp_event_handler_instance_t *instance) {(void)base;(void)id;(void)cb;(void)arg;*instance=1;return ESP_OK;}
static esp_err_t esp_event_handler_instance_unregister(esp_event_base_t base,int id,esp_event_handler_instance_t instance) {(void)base;(void)id;(void)instance;return ESP_OK;}
static esp_netif_t *esp_netif_create_default_wifi_sta(void) {static int station;return &station;}
static void esp_netif_destroy_default_wifi(esp_netif_t *station) {assert(station);netif_destroyed++;}
static esp_err_t esp_wifi_init(wifi_init_config_t *cfg) {assert(cfg->static_rx_buf_num==6&&cfg->static_tx_buf_num==6);return ESP_OK;}
static esp_err_t esp_wifi_set_mode(int mode) {(void)mode;return ESP_OK;}
static esp_err_t esp_wifi_set_storage(int mode) {(void)mode;return ESP_OK;}
static esp_err_t esp_wifi_set_ps(int mode) {(void)mode;return ESP_OK;}
static esp_err_t esp_wifi_set_config(int iface,const wifi_config_t *cfg) {(void)iface;current_config=*cfg;return ESP_OK;}
static esp_err_t esp_wifi_get_config(int iface,wifi_config_t *cfg) {(void)iface;*cfg=current_config;return ESP_OK;}
static esp_err_t esp_wifi_start(void) {handler_event_wifi_st(NULL,WIFI_EVENT,WIFI_EVENT_STA_START,NULL);return ESP_OK;}
static esp_err_t esp_wifi_stop(void) {stop_calls++;return ESP_OK;}
static esp_err_t esp_wifi_deinit(void) {deinit_calls++;return ESP_OK;}
static esp_err_t esp_wifi_connect(void) {connect_calls++;return connect_error;}
static esp_err_t esp_wifi_sta_get_ap_info(wifi_ap_record_t *record) {memset(record,0,sizeof(*record));record->rssi=-42;record->primary=6;return ESP_OK;}
static esp_err_t esp_wifi_scan_start(wifi_scan_config_t *scan,bool block) {(void)scan;(void)block;return ESP_FAIL;}
static esp_err_t esp_wifi_scan_get_ap_records(uint16_t *count,wifi_ap_record_t *records) {(void)records;*count=0;return ESP_OK;}
static void esp_ip4addr_ntoa(const int *address,char *out,size_t len) {(void)address;snprintf(out,len,"192.168.1.2");}
/* PRODUCTION_SOURCE */
static void reset(void) {
memset(&network,0,sizeof(network));memset(&timer,0,sizeof(timer));memset(&current_config,0,sizeof(current_config));
network_error=ESP_OK;reconnect_timer=NULL;reconnect_enabled=false;
event_bits=0;initial_connected=true;ble_configured=true;identity_configured=true;
timer_create_failure=commit_failure=false;saved_setup_requested=0;
strcpy(saved_ssid,"saved-network");strcpy(saved_password,"saved-password");
connect_error=connect_calls=ap_calls=stop_calls=deinit_calls=netif_destroyed=timer_starts=0;
}
static void disconnected(void) {handler_event_wifi_st(NULL,WIFI_EVENT,WIFI_EVENT_STA_DISCONNECTED,NULL);}
static void fire_timer(void) {assert(timer.active);timer.active=false;timer.callback(timer.arg);}
int main(void) {
reset();assert(configure_network());assert(is_connected()&&!ap_calls&&!timer.active);
current_config.sta.bssid_set=true;current_config.sta.channel=6;
int base=connect_calls;
for(int i=0;i<5;i++)disconnected();
assert(connect_calls==base+5&&!is_connected()&&!timer.active);
assert(!current_config.sta.bssid_set&&current_config.sta.channel==0);
disconnected();assert(timer.active&&(event_bits&BIT1)&&!network.ip_address[0]);
for(int i=0;i<20;i++) {fire_timer();disconnected();assert(timer.active);}
assert(connect_calls==base+25); // Recover even after more than 5 attempts.
ip_event_got_ip_t ip={{1}};handler_event_ip_st(NULL,IP_EVENT,IP_EVENT_STA_GOT_IP,&ip);
assert(is_connected()&&!(event_bits&BIT1)&&!timer.active&&network.reconnect_count==0);
base=connect_calls;timer.callback(NULL);assert(connect_calls==base); // Late timer after IP cannot reconnect.
for(int i=0;i<6;i++)disconnected();assert(timer.active);
handler_event_wifi_st(NULL,WIFI_EVENT,WIFI_EVENT_STA_STOP,NULL);
base=connect_calls;disconnected();timer.callback(NULL);
assert(!timer.active&&!reconnect_enabled&&!is_connected()&&connect_calls==base);
reset();assert(configure_network());connect_error=ESP_FAIL;disconnected();assert(timer.active);
fire_timer();assert(timer.active);connect_error=ESP_OK;fire_timer();assert(!timer.active);
reset();initial_connected=false;
assert(!configure_network()&&network_last_error()==ESP_OK);
assert(saved_setup_requested==1&&!ap_calls&&stop_calls==1&&deinit_calls==1&&netif_destroyed==1);
assert(!timer.active&&!reconnect_enabled&&!strcmp(saved_ssid,"saved-network")&&!strcmp(saved_password,"saved-password"));
base=connect_calls;timer.callback(NULL);assert(connect_calls==base); // No driver call after teardown.
reset();initial_connected=false;ble_configured=false;
assert(!configure_network()&&ap_calls==1&&saved_setup_requested==0);
reset();initial_connected=false;commit_failure=true;
assert(!configure_network()&&network_last_error()==ESP_FAIL&&!ap_calls);
reset();saved_ssid[0]=0;
assert(!configure_network()&&saved_setup_requested==1&&!ap_calls&&!stop_calls);
reset();timer_create_failure=true;
assert(!configure_network()&&network_last_error()==ESP_ERR_NO_MEM&&!connect_calls&&!ap_calls);
puts("PASS production network: prolonged disconnect, BSSID fallback, delayed retry, IP recovery, stop guards, BLE boot fallback, credentials preserved, commit failure, legacy AP");
return 0;
}
@@ -0,0 +1,5 @@
// Compile the production network driver against SDK spies; no firmware logic is copied.
const fs = require('node:fs'), path = require('node:path');
const source = fs.readFileSync(path.join(__dirname, '../main/network.c'), 'utf8');
const harness = fs.readFileSync(path.join(__dirname, 'network-reconnect-host.c'), 'utf8');
process.stdout.write(harness.replace('/* PRODUCTION_SOURCE */', source.replace(/^#include[^\n]*$/gm, '')));
@@ -0,0 +1,19 @@
// Contract G02 requires source-level protection even with verbose logging enabled.
const {test}=require('node:test');
const assert=require('node:assert/strict');
const fs=require('node:fs');
const path=require('node:path');
const root=path.resolve(__dirname,'../components/volc_conv_ai/src');
function sources(dir){return fs.readdirSync(dir,{withFileTypes:true}).flatMap(x=>x.isDirectory()?sources(path.join(dir,x.name)):x.name.endsWith('.c')?[path.join(dir,x.name)]:[]);}
test('vendor SDK does not log credential, transcript, config, or HTTP payload buffers',()=>{
const unsafe=[];
for(const file of sources(root)){
const code=fs.readFileSync(file,'utf8').replace(/^\s*\/\/[^\n]*/gm,'');
for(const match of code.matchAll(/\bLOG[VDEIWF]\s*\(([^;]+)\);/g)){
// Match arguments separately: diagnostic words in a constant message are safe.
const args=match[1].replace(/"(?:\\.|[^"\\])*"/g,'');
if(/(?<!\.)\b(?:secret|params|param_str|post_data|json_str|json|buffer|token|response|data|extencontent|error_reason|message|decoded_payload|output)\b|->p_token\b/.test(args)&&/%(?:\.\*|\d*)s/.test(match[1]))unsafe.push(path.relative(root,file)+': '+match[0]);
}
}
assert.deepEqual(unsafe,[],'private buffers must not be passed to SDK log macros');
});
@@ -0,0 +1,44 @@
// Exercise the actual downlink decoder loop with a decoder that consumes one
// encoded frame per invocation, including coalesced frames and invalid progress.
const fs=require('node:fs'), path=require('node:path');
const src=fs.readFileSync(path.join(__dirname,'../components/volc_conv_ai/src/transports/high_quality/src/volc_rtc.c'),'utf8');
const fn=src.slice(src.indexOf('static int __rtc_decode_opus('),src.indexOf('\nstatic int __rtc_rx_decoder_reset('));
if(!fn.startsWith('static int __rtc_decode_opus(')||!fn.trimEnd().endsWith('}'))throw new Error('decoder function extraction failed');
process.stdout.write(`
#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#define ESP_AUDIO_ERR_OK 0
#define VOLC_AUDIO_DATA_TYPE_PCM 2
#define LOGE(...) ((void)0)
typedef struct {void *opus_decoder;uint8_t decoded_pcm[3840];unsigned rx_errors;} rtc_impl_t;
typedef struct {struct {struct {int data_type;} audio;} info;} volc_data_info_t;
typedef struct {uint8_t *buffer;uint32_t len,consumed;} esp_audio_dec_in_raw_t;
typedef struct {uint8_t *buffer;uint32_t len,decoded_size;} esp_audio_dec_out_frame_t;
typedef struct {int unused;} esp_audio_dec_info_t;
static int mode,calls,sends;static uint8_t output[16384];static unsigned output_size;
static int __rtc_rx_current(rtc_impl_t *rtc,unsigned generation){(void)rtc;return generation==1;}
static int esp_opus_dec_decode(void *decoder,esp_audio_dec_in_raw_t *in,esp_audio_dec_out_frame_t *out,esp_audio_dec_info_t *info){
calls++;
if(mode==1)return -1;
if(mode==2){in->consumed=0;return 0;}
if(mode==3){in->consumed=in->len+1;return 0;}
if(mode==4){in->consumed=1;out->decoded_size=out->len+1;return 0;}
in->consumed=1;out->decoded_size=640;memset(out->buffer,in->buffer[0],640);return 0;
}
static void __send_data_2_user(rtc_impl_t *rtc,void *data,unsigned size,volc_data_info_t *info){
assert(info->info.audio.data_type==VOLC_AUDIO_DATA_TYPE_PCM);assert(output_size+size<=sizeof(output));memcpy(output+output_size,data,size);output_size+=size;sends++;
}
${fn}
int main(void){
for(unsigned n=1;n<=12;n++){rtc_impl_t rtc={0};volc_data_info_t info={0};uint8_t packet[12];for(unsigned i=0;i<n;i++)packet[i]=i;
mode=calls=sends=output_size=0;assert(__rtc_decode_opus(&rtc,packet,n,&info,1)==640*(int)n);assert(calls==(int)n && sends==(int)n && rtc.rx_errors==0);
for(unsigned i=0;i<n;i++)for(unsigned j=0;j<640;j++)assert(output[i*640+j]==i);
}
for(mode=1;mode<=4;mode++){rtc_impl_t rtc={0};volc_data_info_t info={0};uint8_t packet[3]={1,2,3};calls=sends=output_size=0;
assert(__rtc_decode_opus(&rtc,packet,3,&info,1)==0);assert(calls==1 && sends==0 && rtc.rx_errors==1);}
puts("RTC downlink decoder frame consumption and bounds: PASS");
}
`);
@@ -0,0 +1,42 @@
// Compile actual PCM packetizer and runtime JSON with ASan/UBSan on a C host.
const fs=require('node:fs'),path=require('node:path');
const root=path.resolve(__dirname,'..');
const read=p=>fs.readFileSync(path.join(root,p),'utf8');
const src=read('components/volc_conv_ai/src/transports/high_quality/src/volc_rtc.c');
const fn=src.slice(src.indexOf('static int __rtc_send_pcm('),src.indexOf('\nint volc_rtc_send('));
const conv=read('components/application/service/conv_service/src/volc_conv.c');
const config=conv.slice(conv.indexOf('#define CONV_AI_CONFIG_FORMAT'),conv.indexOf('#define CONV_SERVICE_TIMEOUT'));
const jsonDir=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
process.stdout.write(fs.readFileSync(path.join(jsonDir,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(jsonDir,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+`
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#define ESP_PLATFORM 1
#define ESP_AUDIO_ERR_OK 0
#define AUDIO_DATA_TYPE_PCM 5
#define AUDIO_DATA_TYPE_OPUS 1
typedef struct {int data_type;} audio_frame_info_t;
typedef struct {uint8_t *buffer;uint32_t len;} esp_audio_enc_in_frame_t;
typedef struct {uint8_t *buffer;uint32_t len,encoded_bytes;} esp_audio_enc_out_frame_t;
typedef struct {void *rtc; const char *p_channel_name; uint8_t pcm_frame[640]; size_t pcm_used;void *opus_encoder;uint8_t *opus_packet;int opus_packet_size;} rtc_impl_t;
static uint8_t received[64000]; static size_t used; static int fail;
static int esp_opus_enc_process(void *h,esp_audio_enc_in_frame_t *in,esp_audio_enc_out_frame_t *out){assert(in->len==640 && out->len==640);memcpy(out->buffer,in->buffer,640);out->encoded_bytes=640;return 0;}
static int byte_rtc_send_audio_data(void *r,const char *ch,const void *p,size_t n,audio_frame_info_t *info) {
assert(n==640 && (info->data_type==AUDIO_DATA_TYPE_PCM || info->data_type==AUDIO_DATA_TYPE_OPUS)); if(fail)return -7;
assert(used+n<=sizeof(received)); memcpy(received+used,p,n); used+=n; return 0;
}
`+fn+'\n'+config+`
int main(void) {
uint8_t input[64000]; for(size_t i=0;i<sizeof(input);i++)input[i]=(uint8_t)i;
for(int encoded=0;encoded<2;encoded++)for(size_t chunk=1;chunk<=4097;chunk+=137) {
uint8_t packet[640];rtc_impl_t rtc={.opus_encoder=encoded?(void*)1:NULL,.opus_packet=packet,.opus_packet_size=sizeof(packet)}; used=0;
for(size_t pos=0;pos<sizeof(input);) {size_t n=sizeof(input)-pos;if(n>chunk)n=chunk;assert(__rtc_send_pcm(&rtc,input+pos,n)==0);pos+=n;}
assert(rtc.pcm_used==0 && used==sizeof(input));assert(!memcmp(input,received,sizeof(input)));
}
rtc_impl_t rtc={0};fail=1;assert(__rtc_send_pcm(&rtc,input,1920)==-7);assert(rtc.pcm_used==0);fail=0;
char buf[1024];int n=snprintf(buf,sizeof(buf),CONV_AI_CONFIG_FORMAT,"instance","product","secret","device");assert(n>0 && (size_t)n<sizeof(buf));
cJSON *cfg=cJSON_Parse(buf);assert(cfg);cJSON *params=cJSON_GetObjectItem(cJSON_GetObjectItem(cfg,"rtc"),"params");assert(cJSON_IsArray(params));
int opus=0;cJSON *p; cJSON_ArrayForEach(p,params) {cJSON *j=cJSON_Parse(cJSON_GetStringValue(p));assert(j);cJSON *a=cJSON_GetObjectItem(cJSON_GetObjectItem(cJSON_GetObjectItem(j,"audio"),"codec"),"opus");if(a){opus++;assert(cJSON_GetNumberValue(cJSON_GetObjectItem(a,"s_sample_rate"))==48000);assert(cJSON_GetNumberValue(cJSON_GetObjectItem(a,"s_samples_per_frame"))==960);assert(cJSON_GetNumberValue(cJSON_GetObjectItem(a,"r_sample_rate"))==48000);assert(cJSON_GetNumberValue(cJSON_GetObjectItem(a,"r_samples_per_frame"))==960);}cJSON_Delete(j);}
assert(opus==1);cJSON_Delete(cfg);puts("RTC PCM packetizer and runtime JSON: PASS");return 0;
}
`);
@@ -0,0 +1,96 @@
// Exercise the production touch debounce and serialized conversation manager.
const fs = require('node:fs'), path = require('node:path');
const root = path.resolve(__dirname, '..');
const touch = fs.readFileSync(path.join(root, 'main/conv_ai_embedded_kit.c'), 'utf8');
const manager = fs.readFileSync(path.join(root, 'components/application/service/conv_service/src/volc_conv_service.c'), 'utf8');
function extract(text, first, next) {
const a = text.indexOf(first), b = text.indexOf(next, a);
if (a < 0 || b < 0) throw new Error('Missing production section: ' + first);
return text.slice(a, b);
}
process.stdout.write(String.raw`
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
typedef uint32_t TickType_t;
typedef int aios_ret_t;
#define ESP_PLATFORM 1
typedef void *TaskHandle_t;
typedef struct {int id;} aios_event_t;
typedef struct {void *conv_thread_id;} volc_conv_service_manager_t;
typedef struct {void *display_handle;} volc_hal_context_t;
typedef void *volc_hal_display_t;
typedef struct {char name[32];int stack_size,priority,stack_in_ext;} volc_osal_thread_param_t;
enum {VOLC_SERVICE_AI_CONVERSATION_START=100,VOLC_SERVICE_AI_CONVERSATION_INTERRUPT,
VOLC_SERVICE_AI_CONVERSATION_QUIT,VOLC_SERVICE_AI_CONVERSATION_OVER,VOLC_SERVICE_AI_CONVERSATION_TOGGLE};
enum {AIOS_Ret_NotHandled,AIOS_Ret_Handled};
#define VOLC_DISPLAY_OBJ_STATUS 0
#define VOLC_DISPLAY_TEXT 0
#define pdMS_TO_TICKS(x) (x)
#define ESP_LOGI(...) ((void)0)
static volc_hal_context_t hal;
static bool create_fail,publish_fail,hal_missing;
static bool standby,wake_accept=true;
static unsigned wakes;
static bool call_client_standby(void){return standby;}
static bool device_control_request_screen_wake(void){if(!wake_accept)return false;wakes++;return true;}
static unsigned starts,stops,prepares,resets,events,reaps;
static int task_data;
static void *handle=&task_data;
static volc_conv_service_manager_t state;
static volc_hal_context_t *volc_get_global_hal_context(void){return hal_missing?NULL:&hal;}
static void volc_hal_display_set_content(void *h,int o,int t,const char *c){(void)h;(void)o;(void)t;(void)c;}
static void conv_ai_service_prepare_task(void){prepares++;}
static void conv_ai_service_task(void *p){(void)p;}
static void conv_ai_service_task_stop(void){stops++;}
static void conv_ai_service_diagnostic_reset(void){resets++;}
static void volc_osal_thread_destroy(void *h){assert(h==&handle);}
static void vTaskDeleteWithCaps(TaskHandle_t h){assert(h==&task_data);reaps++;}
static int volc_osal_thread_create(void **h,volc_osal_thread_param_t *p,void (*f)(void*),void *arg){
(void)p;(void)f;(void)arg;starts++;if(create_fail)return -1;*h=&handle;return 0;
}
` + extract(manager, 'static aios_ret_t __state_conversation(', '\nstatic aios_ret_t __state_init(') + String.raw`
static int aios_event_pub(int id,void *a,void *b){
assert(id==VOLC_SERVICE_AI_CONVERSATION_TOGGLE&&!a&&!b);events++;
if(publish_fail)return -1;
aios_event_t e={id};assert(__state_conversation(&state,&e)==AIOS_Ret_Handled);return 0;
}
` + extract(touch, '/* SCREEN_CALL_TAP_BEGIN:', '/* SCREEN_CALL_TAP_END */') + String.raw`
int main(void){
screen_call_tap(0);assert(starts==1&&prepares==1&&state.conv_thread_id);
screen_call_tap(100);screen_call_tap(299);assert(events==1&&!stops);
// A second deliberate tap stops even within the former five-second guard.
screen_call_tap(300);assert(stops==1&&starts==1&&state.conv_thread_id);
// Connecting/stopping retain admission; rapid repeated taps cannot restart.
screen_call_tap(600);screen_call_tap(900);assert(stops==3&&starts==1);
aios_event_t over={VOLC_SERVICE_AI_CONVERSATION_OVER};
__state_conversation(&state,&over);assert(!state.conv_thread_id);
screen_call_tap(1200);assert(starts==2&&state.conv_thread_id);
aios_event_t quit={VOLC_SERVICE_AI_CONVERSATION_QUIT};
__state_conversation(&state,&quit);assert(stops==4&&state.conv_thread_id);
__state_conversation(&state,&over);
create_fail=true;screen_call_tap(1500);assert(starts==3&&resets==1&&!state.conv_thread_id);
create_fail=false;screen_call_tap(1800);assert(starts==4&&state.conv_thread_id);
__state_conversation(&state,&over);
// Failed event delivery does not consume the debounce window.
publish_fail=true;screen_call_tap(2100);assert(starts==4);
publish_fail=false;screen_call_tap(2101);assert(starts==5&&state.conv_thread_id);
// Unsigned tick rollover preserves the short debounce.
screen_call_tap(UINT32_MAX-100);assert(stops==5);
screen_call_tap(100);assert(stops==5);
screen_call_tap(199);assert(stops==6&&starts==5);
assert(reaps==3);
// A sleeping screen consumes the tap as wake, never as call toggle.
__state_conversation(&state,&over);
unsigned old_events=events,old_starts=starts;
standby=true;screen_call_tap(500);assert(wakes==1&&events==old_events&&starts==old_starts);
standby=false;screen_call_tap(501);assert(events==old_events);
screen_call_tap(800);assert(events==old_events+1&&starts==old_starts+1);
__state_conversation(&state,&over);
standby=true;wake_accept=false;screen_call_tap(1100);assert(wakes==1);
wake_accept=true;screen_call_tap(1101);assert(wakes==2&&starts==old_starts+1);
puts("PASS screen tap: idle start, second tap hangup at 300ms, connecting/closing admission, repeated stop, OVER reopen, create failure, delivery retry, tick rollover");
return 0;
}
`);
@@ -0,0 +1,59 @@
// Exercise the real durable wake state machine with failed hardware/NVS/replies.
const fs=require('node:fs'), path=require('node:path');
const root=path.resolve(__dirname,'..');
const json=path.join(process.env.IDF_PATH||'E:/Codex/toolchains/esp-idf-v5.5.1','components/json/cJSON');
const source=fs.readFileSync(path.join(root,'main/device_screen_wake.c'),'utf8').replace(/^#include[^\n]*\n/gm,'');
process.stdout.write(fs.readFileSync(path.join(json,'cJSON.h'),'utf8')+'\n'+fs.readFileSync(path.join(json,'cJSON.c'),'utf8').replace('#include "cJSON.h"','')+String.raw`
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdatomic.h>
typedef int esp_err_t,nvs_handle_t;
#define ESP_OK 0
#define ESP_FAIL -1
#define ESP_ERR_NVS_NOT_FOUND 1
#define ESP_ERR_INVALID_STATE 2
#define ESP_ERR_INVALID_RESPONSE 3
#define NVS_READONLY 0
#define NVS_READWRITE 1
static bool standby,storage_fail,power_fail,present,staged_present;
static unsigned calls,commits,nonce;
static unsigned char stored[256],staged[256];
static size_t stored_size,staged_size;
static bool call_client_standby(void){return standby;}
static int device_power_apply(const char *mode,bool held){assert(!strcmp(mode,"active")&&!held);calls++;if(power_fail)return -1;standby=false;return 0;}
static void esp_fill_random(void *out,size_t size){uint32_t *p=out;for(size_t i=0;i<size/4;i++)p[i]=++nonce;}
static int nvs_open(const char *name,int mode,int *n){assert(!strcmp(name,"control"));*n=1;return 0;}
static int nvs_get_blob(int n,const char *key,void *out,size_t *size){if(!present)return ESP_ERR_NVS_NOT_FOUND;assert(*size>=stored_size);memcpy(out,stored,stored_size);*size=stored_size;return 0;}
static int nvs_set_blob(int n,const char *key,const void *data,size_t size){assert(size<sizeof(staged));memcpy(staged,data,size);staged_size=size;staged_present=true;return 0;}
static int nvs_erase_key(int n,const char *key){staged_present=false;return present?0:ESP_ERR_NVS_NOT_FOUND;}
static int nvs_commit(int n){if(storage_fail)return -1;memcpy(stored,staged,staged_size);stored_size=staged_size;present=staged_present;commits++;return 0;}
static void nvs_close(int n){}
`+source+String.raw`
static cJSON *report(void){cJSON *r=cJSON_CreateObject();device_screen_wake_add_report(r);return r;}
static cJSON *receipt(const char *result){cJSON *r=report(),*w=cJSON_GetObjectItem(r,"screen_wake");assert(w);cJSON_AddStringToObject(w,"result",result);return r;}
int main(void){
assert(device_screen_wake_init()==0);
device_screen_wake_request();assert(device_screen_wake_poll(4)==0&&calls==0&&!present);
standby=true;device_screen_wake_request();assert(device_screen_wake_poll(0)==0&&calls==0&&!present);
assert(device_screen_wake_poll(4)==0&&!standby&&present&&calls==1&&commits==1);
char id[37];strcpy(id,pending.id);assert(id[14]=='4'&&id[19]=='8');
device_screen_wake_request();assert(device_screen_wake_poll(4)==0&&!strcmp(id,pending.id)&&commits==1);
cJSON *r=cJSON_CreateObject();assert(device_screen_wake_ack(r)==ESP_ERR_INVALID_RESPONSE);cJSON_Delete(r);
r=receipt("applied");storage_fail=true;assert(device_screen_wake_ack(r)!=0&&present);
storage_fail=false;assert(device_screen_wake_ack(r)==0&&!present);cJSON_Delete(r);
// Failed persistence does not activate the microphone or display.
standby=true;storage_fail=true;device_screen_wake_request();unsigned before=calls;
assert(device_screen_wake_poll(5)!=0&&calls==before&&standby);
storage_fail=false;power_fail=true;assert(device_screen_wake_poll(5)!=0&&present&&standby);
r=report();assert(!cJSON_GetObjectItem(r,"screen_wake"));cJSON_Delete(r);
power_fail=false;assert(device_screen_wake_poll(5)==0&&!standby);strcpy(id,pending.id);
// An offline wake survives reboot, and its original revision/ID is retained.
standby=true;assert(device_screen_wake_init()==0);assert(device_screen_wake_poll(0)==0&&!standby&&!strcmp(id,pending.id)&&pending.revision==5);
r=receipt("superseded");assert(device_screen_wake_ack(r)==0&&!present);cJSON_Delete(r);
assert(device_screen_wake_poll(8)==0&&!present);
((wake_event_t*)stored)->version=2;present=true;stored_size=sizeof(wake_event_t);
assert(device_screen_wake_init()==ESP_ERR_INVALID_STATE);
puts("PASS screen wake: wake-only tap, startup intent, durable offline/reboot intent, duplicate tap, failed persistence/hardware/ACK, revision-fenced receipts");
}
`);
@@ -0,0 +1,80 @@
import importlib.util
import json
from pathlib import Path
import sys
import tempfile
import unittest
TOOLS = Path(__file__).resolve().parents[1] / "tools"
sys.path.insert(0, str(TOOLS))
import device_prepare as prepare
class PrepareTests(unittest.TestCase):
def setUp(self):
self.directory = tempfile.TemporaryDirectory()
self.addCleanup(self.directory.cleanup)
self.root = Path(self.directory.name)
self.credential = self.root / "credential.json"
self.value = {"factory_id": "KP01-0123456789AB", "model": "ECHOEAR-S3",
"origin": "https://example.com", "key_version": 1,
"device_key_hex": "01" * 32}
self.save()
def save(self):
self.credential.write_text(json.dumps(self.value), encoding="utf-8-sig")
def test_accepts_actual_web_download(self):
self.assertEqual(prepare.read_credential(self.credential)["device_id"], self.value["factory_id"])
def test_legacy_id(self):
self.value["device_id"] = self.value.pop("factory_id")
self.save()
self.assertEqual(prepare.read_credential(self.credential)["device_id"], self.value["device_id"])
def test_rejects_conflicting_identity(self):
self.value["device_id"] = "KP01-AAAAAAAAAAAA"
self.save()
with self.assertRaises(ValueError):
prepare.read_credential(self.credential)
def test_rejects_bad_configuration(self):
for field, value in [("model", "different-board"), ("key_version", True),
("key_version", 0), ("device_key_hex", "bad"),
("origin", "http://example.com"),
("origin", "https://user:password@example.com"),
("origin", "https://example.com/path"),
("origin", "https://example.com:invalid")]:
original = self.value[field]
self.value[field] = value
self.save()
with self.subTest(field=field, value=value), self.assertRaises(ValueError):
prepare.read_credential(self.credential)
self.value[field] = original
def test_mac_independent_of_factory_id(self):
self.assertEqual(prepare.normalize_mac("B4-3A-45-16-34-A8"), "b4:3a:45:16:34:a8")
@unittest.skipUnless(importlib.util.find_spec("esp_idf_nvs_partition_gen"), "NVS generator unavailable")
def test_real_nvs_generation_and_no_overwrite(self):
output = self.root / "private"
manifest = prepare.prepare(self.credential, output, "B4:3A:45:16:34:A8")
image = (output / "ble_factory.bin").read_bytes()
self.assertEqual(len(image), 0x6000)
self.assertIn(bytes.fromhex(self.value["device_key_hex"]), image)
self.assertEqual(manifest["expected_mac"], "b4:3a:45:16:34:a8")
self.assertNotIn("device_key_hex", json.dumps(manifest))
self.assertNotIn(self.value["device_key_hex"], (output / "setup-label.json").read_text())
if (TOOLS.parent / "build" / "flasher_args.json").is_file():
import device_flash
plan = device_flash.build_plan(TOOLS.parent / "build", output,
identity_only=True, reset_runtime=True,
expect_mac="B4:3A:45:16:34:A8")
self.assertEqual({item.name for item in plan.images}, {"reset-runtime", "ble_factory"})
self.assertEqual(next(item.data for item in plan.images if item.name == "ble_factory"), image)
with self.assertRaises(FileExistsError):
prepare.prepare(self.credential, output, "B4:3A:45:16:34:A8")
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,210 @@
"""Offline tests for the GUI bridge; serial/device I/O is always mocked."""
import io
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import types
import unittest
from unittest.mock import patch
TOOLS = Path(__file__).resolve().parents[1] / "tools"
sys.path.insert(0, str(TOOLS))
import flash_app_bridge as bridge
from test_device_flash import MAC, SimulatedEsptool, make_build
class BridgeTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix=".bridge-test-", dir=Path(__file__).parent)
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name).resolve()
self.build = self.root / "build"
make_build(self.build)
self.key = "ab" * 32
self.password = "private-setup-password"
self.credential = self.root / "credential.json"
self.info = {"factory_id": "KP01-123456789ABC", "model": "ECHOEAR-S3", "key_version": 1,
"origin": "https://example.com", "device_key_hex": self.key}
self.credential.write_text(json.dumps(self.info), encoding="utf-8")
self.request = {"credential_path": str(self.credential), "build_dir": str(self.build),
"mode": "identity", "expected_mac": MAC, "port": "COM6",
"output_root": str(self.root / "output")}
self.events = []
for owner, name, replacement in [
(bridge.device_prepare, "private_directory", lambda path: path.mkdir(parents=True, mode=0o700)),
(bridge.device_flash, "private_directory", lambda path: path.mkdir(parents=True, mode=0o700)),
(bridge.device_prepare, "prepare", self.fake_prepare),
]:
item = patch.object(owner, name, side_effect=replacement)
item.start()
self.addCleanup(item.stop)
def fake_prepare(self, credential, output, expected_mac):
info = bridge.device_prepare.read_credential(credential)
output.mkdir()
data = b"\xab" * bridge.device_flash.FACTORY_SIZE
(output / "ble_factory.bin").write_bytes(data)
(output / "factory.csv").write_text("device_key," + self.key)
(output / "setup-label.json").write_text(json.dumps({"setup_password": self.password}))
manifest = {"schema_version": 1, "chip": "esp32s3", "flash_size": bridge.device_flash.FLASH_SIZE,
"device_id": info["device_id"], "model": info["model"], "expected_mac": expected_mac,
"image": {"file": "ble_factory.bin", "offset": bridge.device_flash.FACTORY_OFFSET,
"size": len(data), "sha256": bridge.device_flash.sha256(data)}}
(output / "manifest.json").write_text(json.dumps(manifest))
return manifest
def preview(self, **overrides):
bridge.preview({**self.request, **overrides}, self.events.append)
return self.events[-1]
def assert_blocked(self, request, code):
with patch.object(bridge.device_flash, "execute_plan") as execute:
with self.assertRaises(bridge.BridgeError) as raised:
bridge.flash(request, self.events.append)
self.assertEqual(raised.exception.code, code)
execute.assert_not_called()
def test_preview_is_offline_and_secret_free(self):
with patch.object(bridge.device_flash, "execute_plan") as execute, patch.object(bridge, "hidden_run") as run:
result = self.preview()
execute.assert_not_called()
run.assert_not_called()
self.assertEqual(result["event"], "preview")
summary = result["summary"]
self.assertEqual(summary["mode"], "identity")
self.assertIsNone(summary["firmware_version"])
self.assertEqual(summary["reference_firmware_version"], "1.7.0-controls.28")
self.assertEqual({image["name"] for image in summary["regions"]}, {"ble_factory", "reset-runtime"})
text = json.dumps(self.events)
self.assertNotIn(self.key, text)
self.assertNotIn(self.password, text)
session = bridge.load_session(Path(result["session_file"]))
self.assertEqual(session["port"], "COM6")
self.assertEqual(session["expected_mac"], MAC)
self.assertNotIn(self.key, json.dumps(session))
def test_full_preview_has_complete_regions_and_version(self):
result = self.preview(mode="full")
self.assertEqual(result["summary"]["firmware_version"], "1.7.0-controls.28")
self.assertEqual(len(result["summary"]["regions"]), 8)
def test_bad_build_refuses_to_create_factory(self):
(self.build / "app.bin").write_bytes(b"not firmware")
with self.assertRaises(bridge.BridgeError) as raised:
self.preview()
self.assertEqual(raised.exception.code, "invalid_build")
self.assertFalse((self.root / "output").exists())
def test_bad_credential_refuses_to_create_factory(self):
self.info["device_key_hex"] = "invalid key"
self.credential.write_text(json.dumps(self.info))
with self.assertRaises(bridge.BridgeError) as raised:
self.preview()
self.assertEqual(raised.exception.code, "invalid_credential")
self.assertFalse((self.root / "output").exists())
def test_flash_requires_literal_true_confirmation(self):
session = self.preview()["session_file"]
for value in (None, False, 1, "true"):
with self.subTest(value=value):
self.assert_blocked({"session_file": session, "execute": value}, "confirmation_required")
def test_port_and_mac_bound_to_preview(self):
session = self.preview()["session_file"]
for override in ({"port": "COM7"}, {"expected_mac": "11:22:33:44:55:66"}):
with self.subTest(override=override):
self.assert_blocked({"session_file": session, "execute": True, **override}, "port_mismatch")
def test_frozen_inputs_changes_block_before_hardware(self):
for filename in ("credential.json", "build/app.bin", "build/flasher_args.json", "factory/ble_factory.bin", "factory/setup-label.json"):
with self.subTest(filename=filename):
session_path = Path(self.preview()["session_file"])
path = session_path.parent / filename if filename.startswith("factory/") else self.root / filename
original = path.read_bytes()
try:
path.write_bytes(original + b"changed")
self.assert_blocked({"session_file": str(session_path), "execute": True}, "stale_preview")
finally:
path.write_bytes(original)
def test_expiry_blocks_before_hardware(self):
session = self.preview()["session_file"]
with patch.object(bridge.time, "time", return_value=bridge.time.time() + bridge.SESSION_TTL + 1):
self.assert_blocked({"session_file": session, "execute": True}, "stale_preview")
def test_modified_session_cannot_change_target(self):
session = Path(self.preview()["session_file"])
value = json.loads(session.read_text())
value["port"] = "COM42"
session.write_text(json.dumps(value))
self.assert_blocked({"session_file": str(session), "execute": True}, "stale_preview")
def test_cannot_supply_arbitrary_command_or_runner(self):
session = self.preview()["session_file"]
for field in ("command", "python", "baud", "factory_dir", "backup_dir"):
self.assert_blocked({"session_file": session, "execute": True, field: "evil"}, "invalid_request")
def test_success_runs_existing_backup_write_verify_and_consumes_session(self):
session = self.preview()["session_file"]
runner = SimulatedEsptool()
with patch.object(bridge, "hidden_run", side_effect=runner):
bridge.flash({"session_file": session, "execute": True}, self.events.append)
self.assertEqual(runner.commands, ["version", "read_mac", "flash_id", "get_security_info", "read_flash", "write_flash", "verify_flash"])
self.assertEqual(self.events[-1]["event"], "complete")
self.assertTrue((Path(session).parent / "backup" / "before-16mb.bin").is_file())
self.assertIn("backup", [item.get("stage") for item in self.events])
self.assertIn("writing", [item.get("stage") for item in self.events])
self.assertIn("verifying", [item.get("stage") for item in self.events])
self.assert_blocked({"session_file": session, "execute": True}, "stale_preview")
def test_device_mac_mismatch_never_writes(self):
session = self.preview()["session_file"]
runner = SimulatedEsptool(mac="11:22:33:44:55:66")
with patch.object(bridge, "hidden_run", side_effect=runner):
with self.assertRaises(bridge.BridgeError) as raised:
bridge.flash({"session_file": session, "execute": True}, self.events.append)
self.assertEqual(raised.exception.code, "flash_failed")
self.assertNotIn("write_flash", runner.commands)
def test_backup_failure_never_writes(self):
session = self.preview()["session_file"]
runner = SimulatedEsptool(short_backup=True)
with patch.object(bridge, "hidden_run", side_effect=runner):
with self.assertRaises(bridge.BridgeError):
bridge.flash({"session_file": session, "execute": True}, self.events.append)
self.assertNotIn("write_flash", runner.commands)
def test_main_outputs_json_lines_and_redacts_exception(self):
session = self.preview()["session_file"]
request_file = self.root / "request.json"
request_file.write_text(json.dumps({"session_file": session, "execute": True}))
output = io.StringIO()
with patch.object(bridge.device_flash, "execute_plan", side_effect=RuntimeError(self.key + self.password)):
code = bridge.main(["flash", "--request", str(request_file)], output=output)
self.assertEqual(code, 2)
rows = [json.loads(line) for line in output.getvalue().splitlines()]
self.assertEqual(rows[-1]["code"], "flash_failed")
self.assertNotIn(self.key, output.getvalue())
self.assertNotIn(self.password, output.getvalue())
def test_ports_enumeration_does_not_open_serial(self):
import serial
from serial.tools import list_ports
port = types.SimpleNamespace(device="COM6", description="USB", hwid="USB VID:PID", serial_number="SERIAL")
with patch.object(serial, "Serial") as open_serial, patch.object(list_ports, "comports", return_value=[port]):
bridge.enumerate_ports({}, self.events.append)
open_serial.assert_not_called()
self.assertEqual(self.events[-1]["ports"][0]["serial_number"], "SERIAL")
def test_windows_child_processes_are_hidden(self):
if bridge.os.name != "nt":
self.skipTest("Windows-only subprocess flag")
with patch.object(bridge.subprocess, "run") as run:
bridge.hidden_run(["example"], capture_output=True)
self.assertTrue(run.call_args.kwargs["creationflags"] & subprocess.CREATE_NO_WINDOW)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,105 @@
// Actual command recognizer, message/report parsing and stop-readiness boundary.
const fs = require('node:fs'), path = require('node:path');
const root = path.resolve(__dirname, '..');
const read = p => fs.readFileSync(path.join(root, p), 'utf8');
const src = read('components/application/service/conv_service/src/volc_conv.c');
const json = path.join('E:/Codex/toolchains/esp-idf-v5.5.1/components/json/cJSON');
function section(start, end) {
const a = src.indexOf(start), b = src.indexOf(end, a + start.length);
if (a < 0 || b < 0) throw new Error('Missing production section ' + start);
return src.slice(a, b);
}
process.stdout.write(read('components/application/service/conv_service/inc/call_voice_end.h').replace('#pragma once', '') + '\n' +
read('components/application/service/conv_service/src/call_voice_end.c').replace('#include "call_voice_end.h"', '') + '\n' +
fs.readFileSync(path.join(json, 'cJSON.h'), 'utf8') + '\n' +
fs.readFileSync(path.join(json, 'cJSON.c'), 'utf8').replace('#include "cJSON.h"', '') + String.raw`
#include <assert.h>
#include <stdatomic.h>
#include <stdio.h>
typedef void *volc_engine_t;
typedef void *volc_hal_player_t;
typedef struct {int unused;} volc_message_info_t;
typedef struct {void *player_handle[1];} volc_hal_context_t;
static volc_hal_context_t hal={{(void*)1}};
static struct {void *engine;int wait_time;} conv_service;
static bool is_stop,stop_requested,session_started_reported,rtc=true,player_quiet=true;
static call_voice_end_t voice_end;
static atomic_uint voice_finish_serial,last_voice_received_ms;
static atomic_bool local_speech_active;
static uint64_t now_ms;
static cJSON *queue[8];static unsigned head,tail,turns,round_id;
static void *platform_report_queue=(void*)1;
#define pdTRUE 1
#define VOLC_HAL_PLAYER_AUDIO 0
#define VOLC_FUNCTION_CALL_EXEC 1
#define VOLC_FUNCTION_CALL_TRIGGER 2
#define LOGI(...) ((void)0)
#define LOGE(...) ((void)0)
static uint64_t volc_osal_get_time_ms(void){return now_ms;}
static bool platform_runtime_rtc(void){return rtc;}
static volc_hal_context_t *volc_get_global_hal_context(void){return &hal;}
static bool volc_hal_player_voice_quiet(void *p,unsigned ms){assert(p&&(ms==600));return player_quiet;}
static void volc_hal_player_play_data(void *p,const void *d,size_t n){(void)p;(void)d;(void)n;}
static void volc_hal_player_finish_segment(void *p){(void)p;}
static void call_idle_activity(void){}
static void platform_conversation_observe(const char *type){(void)type;}
static void __on_subtitle_message_received(const cJSON *j){(void)j;}
static const char *volc_current_user_id(void *e,bool remote){(void)e;return remote?"bot":"user";}
static int platform_conversation_turn(const char *s,const char *id,const char *text){assert(s&&id&&text);turns++;return 0;}
static int platform_conversation_started(const char *ref){assert(ref);return 0;}
static void aios_event_pub(int id,cJSON *j,void *u){(void)id;(void)u;cJSON_Delete(j);}
static int xQueueSend(void *q,cJSON **p,unsigned ticks){(void)q;(void)ticks;if(tail-head==8)return 0;queue[tail++%8]=*p;return pdTRUE;}
static int xQueueReceive(void *q,cJSON **p,unsigned ticks){(void)q;(void)ticks;if(head==tail)return 0;*p=queue[head++%8];return pdTRUE;}
` + section('/* Called only by the conversation task,', '\nvoid conv_ai_service_init()') + String.raw`
static void subtitle(const char *user,const char *text,bool definite,bool paragraph){
cJSON *j=cJSON_CreateObject(),*list=cJSON_AddArrayToObject(j,"data"),*line=cJSON_CreateObject();
cJSON_AddStringToObject(j,"type","subtitle");cJSON_AddItemToArray(list,line);
cJSON_AddStringToObject(line,"userId",user);cJSON_AddStringToObject(line,"text",text);
cJSON_AddNumberToObject(line,"sequence",tail);cJSON_AddBoolToObject(line,"definite",definite);cJSON_AddBoolToObject(line,"paragraph",paragraph);
cJSON_AddNumberToObject(line,"roundId",++round_id);
char *s=cJSON_PrintUnformatted(j);cJSON_Delete(j);char b[1024]="subv0000";
assert(strlen(s)+8<sizeof(b));strcpy(b+8,s);free(s);
__on_volc_message_data(NULL,b,strlen(b),NULL,NULL);
assert(!voice_end.pending); // receive callback never stops or mutates the FSM
process_platform_reports();
}
static void reset(void){call_voice_end_reset(&voice_end);memset(&voice_user_subtitle,0,sizeof(voice_user_subtitle));now_ms=1000;atomic_store(&voice_finish_serial,0);atomic_store(&last_voice_received_ms,0);atomic_store(&local_speech_active,false);is_stop=stop_requested=false;player_quiet=true;}
int main(void){
const char *yes[]={"拜拜吧","拜拜","再见了","就聊到这把","就聊到这吧","今天就聊到这里吧","好了,今天我们就先聊到这儿吧。谢谢你,拜拜!","那就先不聊了","请挂断电话","结束这次聊天吧","我先走了","我要休息了","下次再聊"};
const char *no[]={"不要拜拜","先别挂断","不要结束聊天","他说拜拜","她跟我说再见","你会说拜拜吗","拜拜是什么意思","就聊到这里吗?","\"拜拜\"","‘拜拜’","拜拜?","昨天就聊到这吧","好的拜拜之后继续聊","我还不想结束聊天","不聊了才怪","拜拜,不对,我还想聊","谢谢","今天的天气很好",NULL};
for(size_t i=0;i<sizeof(yes)/sizeof(*yes);i++)assert(call_voice_end_command(yes[i]));
for(size_t i=0;i<sizeof(no)/sizeof(*no);i++)assert(!call_voice_end_command(no[i]));
char huge[2048];memset(huge,'x',sizeof(huge));huge[sizeof(huge)-1]=0;assert(!call_voice_end_command(huge));
reset();subtitle("bot","拜拜吧",true,true);assert(!voice_end.pending);
subtitle("other","拜拜吧",true,true);assert(!voice_end.pending);
subtitle("user","拜拜吧",false,true);assert(!voice_end.pending);
const char *interim="{\"type\":\"subtitle\",\"data\":[{\"userId\":\"user\",\"sequence\":3,\"roundId\":2,\"definite\":false,\"paragraph\":false,\"text\":\"拜拜\"}]}";
for(int i=0;i<100;i++)__on_volc_message_data(NULL,interim,strlen(interim),NULL,NULL);
assert(head==tail&&!voice_end.pending); // partial floods cannot consume control slots
subtitle("user","拜拜吧",true,false);assert(!voice_end.pending);
subtitle("user","拜拜,不对,我还想聊",true,true);assert(!voice_end.pending);
cJSON *part=cJSON_CreateObject();cJSON_AddNumberToObject(part,"roundId",++round_id);cJSON_AddNumberToObject(part,"sequence",1);cJSON_AddBoolToObject(part,"definite",true);cJSON_AddBoolToObject(part,"paragraph",false);cJSON_AddStringToObject(part,"text","你跟我说一句,");
process_voice_end_rtc_user(part);assert(!voice_end.pending);
cJSON_SetNumberValue(cJSON_GetObjectItem(part,"sequence"),2);cJSON_SetBoolValue(cJSON_GetObjectItem(part,"paragraph"),true);cJSON_SetValuestring(cJSON_GetObjectItem(part,"text"),"再见");
process_voice_end_rtc_user(part);assert(!voice_end.pending);cJSON_Delete(part);
subtitle("user","就聊到这把",true,true);assert(voice_end.pending&&!voice_end_ready());
uint32_t deadline=voice_end.requested_ms;now_ms=2000;
call_voice_end_user(&voice_end,"就聊到这把",now_ms,0);assert(voice_end.requested_ms==deadline);
atomic_store(&voice_finish_serial,1);now_ms=2600;player_quiet=false;assert(!voice_end_ready());
player_quiet=true;atomic_store(&last_voice_received_ms,2500);assert(!voice_end_ready());
now_ms=3800;assert(voice_end_ready());atomic_store(&local_speech_active,true);assert(!voice_end_ready());
process_voice_end_transcript("subject","等一下,我还想问个问题",true);assert(!voice_end.pending);
reset();subtitle("user","拜拜",true,true);now_ms=12999;assert(!voice_end_ready());now_ms=13000;assert(voice_end_ready());
reset();subtitle("user","拜拜",true,true);is_stop=true;process_voice_end_transcript("subject","再见",true); // teardown cannot arm another close
call_voice_end_reset(&voice_end);process_voice_end_transcript("subject","拜拜",true);assert(!voice_end.pending);
reset();now_ms=UINT32_MAX-500;call_voice_end_user(&voice_end,"拜拜",now_ms,0);now_ms+=12000;assert(call_voice_end_due(&voice_end,(uint32_t)now_ms,0,false,false));
reset();rtc=false;
const char *msg="{\"type\":\"conversation.item.input_audio_transcription.completed\",\"event_id\":\"e1\",\"transcript\":\"拜拜吧\"}";
__on_volc_message_data(NULL,msg,strlen(msg),NULL,NULL);assert(!voice_end.pending);process_platform_reports();assert(voice_end.pending);
const char *done="{\"type\":\"response.audio.done\"}";
__on_volc_message_data(NULL,done,strlen(done),NULL,NULL);assert(atomic_load(&voice_finish_serial)==1);
now_ms+=2000;assert(voice_end_ready());
puts("PASS voice end: actual RTC/WS parsing, complete user-only commands, negation/quotes/questions, playback wait, cancel, duplicate deadline, 12s fallback, clock wrap, teardown fencing");
return 0;
}
`);
@@ -0,0 +1,69 @@
// Execute the actual PCM worker and quiet check with synthetic RTOS/codec I/O.
const fs=require('node:fs'),path=require('node:path');
const src=fs.readFileSync(path.join(__dirname,'../components/application/platform/src/espressif/volc_hal_player.c'),'utf8');
const section=(a,b)=>{const x=src.indexOf(a),y=src.indexOf(b,x+a.length);if(x<0||y<0)throw new Error(a);return src.slice(x,y);};
process.stdout.write(String.raw`
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdatomic.h>
#include <string.h>
typedef void *volc_hal_player_t;
typedef struct {uint8_t data[65536];size_t used;} stream_t;
typedef struct {
atomic_bool is_started,write_in_progress;
atomic_uint last_voice_write_ms;
stream_t *pcm_stream;void *worker_stopped;
bool discard_pending,segment_complete;
bool (*authorization_gate)(void);
} volc_player_impl_t;
#define LOGI(...) do { if (0) printf(__VA_ARGS__); } while (0)
#define LOGE(...) do { if (0) printf(__VA_ARGS__); } while (0)
#define ESP_OK 0
#define PCM_PREBUFFER_BYTES 24000
#define PCM_PREBUFFER_WAIT_MS 1500
#define PCM_REPRIME_IDLE_MS 600
#define pdMS_TO_TICKS(x) (x)
static uint64_t now_ms,limit_ms;
static unsigned written,stops;
static bool feed_fail;
static volc_player_impl_t *running;
static uint64_t volc_osal_get_time_ms(void){return now_ms;}
static size_t xStreamBufferBytesAvailable(stream_t *s){return s->used;}
static size_t xStreamBufferReceive(stream_t *s,void *out,size_t n,unsigned wait){assert(wait==0);if(n>s->used)n=s->used;memcpy(out,s->data,n);memmove(s->data,s->data+n,s->used-n);s->used-=n;return n;}
static int stop_feeder_once(volc_player_impl_t *p){(void)p;stops++;return 0;}
static bool volc_hal_player_voice_quiet(volc_hal_player_t,unsigned);
static int audio_feeder_feed_data_timeout(const void *data,size_t count,unsigned timeout){
assert(data&&count&&timeout==50);
/* A dequeued last chunk is still in flight and cannot permit hangup. */
assert(atomic_load(&running->write_in_progress));assert(!volc_hal_player_voice_quiet(running,600));
now_ms+=count/32;written+=count;return feed_fail?-1:0;
}
static void vTaskDelay(unsigned ms){now_ms+=ms;if(now_ms>=limit_ms)atomic_store(&running->is_started,false);}
static void xSemaphoreGive(void *s){(void)s;}
static void vTaskDelete(void *t){assert(!t);}
`+section('static void pcm_playback_task(', '\nvolc_hal_player_t volc_hal_player_create(')+
section('bool volc_hal_player_voice_quiet(', '\nvoid volc_hal_player_set_authorization_gate(')+String.raw`
static bool deny(void){return false;}
int main(void){
stream_t stream={0};volc_player_impl_t player={.pcm_stream=&stream,.segment_complete=true};
running=&player;now_ms=1000;atomic_store(&player.is_started,true);atomic_store(&player.last_voice_write_ms,1000);
assert(!volc_hal_player_voice_quiet(&player,600));
for(size_t i=0;i<4096;i+=2){int16_t s=(i&2)?-500:500;memcpy(stream.data+i,&s,2);}stream.used=4096;
now_ms=3000;assert(!volc_hal_player_voice_quiet(&player,600));limit_ms=3400;
pcm_playback_task(&player);assert(written==4096&&stream.used==0);
uint32_t last=atomic_load(&player.last_voice_write_ms);assert(last>=3128&&last<3400);
atomic_store(&player.is_started,true);now_ms=last+599;assert(!volc_hal_player_voice_quiet(&player,600));now_ms++;assert(volc_hal_player_voice_quiet(&player,600));
memset(stream.data,0,640);stream.used=640;written=0;limit_ms=now_ms+200;
pcm_playback_task(&player);assert(written==640&&atomic_load(&player.last_voice_write_ms)==last);
atomic_store(&player.is_started,true);now_ms+=1000;assert(volc_hal_player_voice_quiet(&player,600));
stream.used=640;player.authorization_gate=deny;limit_ms=now_ms+100;pcm_playback_task(&player);assert(stops==1&&stream.used==0);
atomic_store(&player.is_started,true);player.authorization_gate=NULL;player.discard_pending=true;stream.used=640;limit_ms=now_ms+100;written=0;
pcm_playback_task(&player);assert(!player.discard_pending&&stream.used==0&&written==0);
atomic_store(&player.is_started,true);atomic_store(&player.last_voice_write_ms,UINT32_MAX-500u);now_ms=(uint64_t)UINT32_MAX+100;assert(volc_hal_player_voice_quiet(&player,600));
assert(!volc_hal_player_voice_quiet(NULL,600));
puts("PASS voice playback: actual PCM worker, queued/in-flight audio, audible tail grace, continuous silent PCM, authorization/discard, monotonic clock wrap");
}
`);