#include #include #include #include #include #include #include #include #include #include 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)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_index0);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;i1?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; }