64 lines
4.4 KiB
JavaScript
64 lines
4.4 KiB
JavaScript
// 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;
|
||
|
|
}
|
||
|
|
`);
|