"""Exercise the actual read-only SYS/AT probe without a modem or kernel image.""" from pathlib import Path import shutil import subprocess import tempfile ROOT = Path(__file__).resolve().parents[1] APP = ROOT / 'ql-application/threadx/interface/custom_app' with tempfile.TemporaryDirectory(prefix='app-sys-probe-') as directory: work = Path(directory) headers = { 'ql_rtos.h': '''unsigned int ql_rtos_get_systicks(void); void ql_rtos_task_sleep_ms(unsigned int); ''', 'ql_func.h': '#include \nextern intptr_t (*m_get_api_ptr)(char *);\n', 'ql_rtc.h': '''typedef struct {int tm_sec,tm_min,tm_hour,tm_mday,tm_mon,tm_year,tm_wday;} ql_rtc_time_t; int ql_rtc_get_time_ex(ql_rtc_time_t *); ''', 'ql_atcmd.h': 'int ql_atcmd_send_sync(char *,char *,int,char *,int);\n', } for name, content in headers.items(): (work / name).write_text(content) harness = r''' #include #include #include "APP_SOURCE" static int mode, sends, reads, sys_reads; static unsigned int ticks=100, origin=100; unsigned int ql_rtos_get_systicks(void) {return ticks;} void ql_rtos_task_sleep_ms(unsigned int ms) {assert(ms==3000); ticks+=ms/5;} static unsigned int get_count(int offset) { assert(offset==0); ++sys_reads; if (mode==4) return 100; return 1793030410U+(mode==5 ? 0 : (ticks-origin)/200U); } static int get_zone(void) {return 480;} static intptr_t lookup(char *name) { assert(strstr(name,"diag")==NULL); if (!strcmp(name,"PMIC_RTC_GetTime_Count")) return mode==1 ? 0 : (intptr_t)get_count; if (!strcmp(name,"GetTimeZone")) return mode==1 || mode==2 ? 0 : (intptr_t)get_zone; if (!strcmp(name,"ql_rtc_get_time_ex")) return mode==7 ? 0 : 1; if (!strcmp(name,"ql_atcmd_send_sync")) return mode==8 ? 0 : 1; assert(0); return 0; } intptr_t (*m_get_api_ptr)(char *)=lookup; time_t time(time_t *out) {(void)out; return 100;} int ql_rtc_get_time_ex(ql_rtc_time_t *out) { ++reads; out->tm_year=1970; out->tm_mon=1; out->tm_mday=1; return 0; } int ql_atcmd_send_sync(char *cmd,char *resp,int len,char *filter,int timeout) { static const char *commands[]={"AT+QLTS=0\r\n","AT+QLTS=1\r\n","AT+QLTS=2\r\n","AT+CCLK?\r\n"}; assert(!strcmp(cmd,commands[sends%4])); assert(len==256 && filter==NULL && timeout==2); ++sends; ticks+=20; if (mode==1) {strcpy(resp,"\r\nERROR\r\n"); return 1;} if (mode==6) {memset(resp,'X',len); return 0;} strcpy(resp,"\r\n+QLTS: \"2026/10/26,08:00:10+32,0\"\r\nOK\r\n"); return 0; } int main(int argc,char **argv) { assert(argc==2); mode=atoi(argv[1]); if (mode==3) m_get_api_ptr=NULL; if (mode==9) ticks=origin=UINT32_MAX-199U; app_sys_time_probe_run(); assert(sends==(mode==3 || mode==8 ? 0 : 8)); assert(reads==(mode==3 || mode==7 || mode==8 ? 0 : 2)); assert(sys_reads==(mode==1 || mode==2 || mode==3 ? 0 : 2)); return 0; } '''.replace('APP_SOURCE', str(APP / 'app_sys_time_probe.c')) (work / 'test.c').write_text(harness) compiler = shutil.which('clang') or shutil.which('cc') if not compiler: raise SystemExit('A native C compiler is required') subprocess.run([compiler, '-std=c99', '-Wall', '-Wextra', '-Werror', '-fsanitize=address,undefined', '-I', str(work), '-I', str(APP / 'inc'), str(work / 'test.c'), '-o', str(work / 'test')], check=True) cases = {0: 'direct_sys=candidate', 1: 'direct_sys=unavailable', 2: 'direct_sys=unavailable', 3: 'FAIL stage=api_lookup_unavailable', 4: 'direct_sys=rejected', 5: 'direct_sys=rejected', 6: 'terminated=0', 7: 'rtc_ex=unavailable', 8: 'at=unavailable', 9: 'direct_sys=candidate'} for mode, expected in cases.items(): log = subprocess.check_output([str(work / 'test'), str(mode)], text=True) assert expected in log, log assert '[APP-SYS-PROBE] finished rtc_not_written=1' in log, log if mode == 1: assert 'query=QLTS_1 rc=1' in log, log if mode not in (0, 6, 7, 8, 9): assert 'direct_sys=candidate' not in log, log print('PASS: 10 SYS/AT probe scenarios (ASan/UBSan); no setter or diagnostic API linked')