"""Host behavioral tests of the actual APP module; no modem traffic is sent.""" 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-nitz-test-') as directory: work = Path(directory) headers = { 'ql_rtos.h': '''#include typedef void *ql_task_t; unsigned int ql_rtos_get_systicks(void); void ql_rtos_task_sleep_ms(unsigned int); int ql_rtos_task_create(ql_task_t *, unsigned int, unsigned char, char *, void (*)(void *), void *, ...); int ql_rtos_task_delete(ql_task_t); ''', 'ql_func.h': '#include \nextern intptr_t (*m_get_api_ptr)(char *);\n', 'ql_nw.h': '''enum { QL_NW_SUCCESS=0 }; typedef struct {char nitz_time[32]; unsigned long abs_time; unsigned char leap_sec;} QL_NW_NITZ_TIME_INFO_T; int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *); ''', '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_set_time(ql_rtc_time_t *); int ql_rtc_get_time(ql_rtc_time_t *); ''', } for name, content in headers.items(): (work / name).write_text(content) harness = r''' #include #include #include "APP_SOURCE" static int mode, creates, queries, sets, probes; void app_sys_time_probe_run(void) {++probes;} static unsigned int ticks=100; static time_t app_epoch=100; static int zone_minutes=480; static ql_rtc_time_t saved; unsigned int ql_rtos_get_systicks(void) {return ticks;} void ql_rtos_task_sleep_ms(unsigned int ms) {ticks+=ms/5;} static intptr_t lookup(char *name) { assert(!strcmp(name,"ql_nw_get_nitz_time_info") || !strcmp(name,"ql_localtime_r") || !strcmp(name,"ql_rtc_set_time") || !strcmp(name,"ql_rtc_get_time")); return mode==1 ? 0 : 1; } intptr_t (*m_get_api_ptr)(char *)=lookup; int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *info) { time_t value=1793019600; /* 2026-10-26 13:00:00 UTC */ struct tm *utc; ++queries; if (mode==2) return 1; if (mode==3) return 0; if (mode==4) {memset(info->nitz_time,'X',32); return 0;} if (mode==5) {strcpy(info->nitz_time,"26/02/30 13:00:00 +32 00"); return 0;} if (mode==6 || mode==22) { strcpy(info->nitz_time,mode==22 ? "26/10/26 08:00:10 32 0" : "26/10/26 13:00:00 +32 00"); return 0; } if (mode==16 && queries==1) return 1; if (mode==17) ticks+=600; /* slow query must be rejected */ if (mode==14) value=1793059198; /* near midnight UTC */ if (mode==15) value=2147483646; /* would advance past signed32 limit */ if (mode==18) value+=(queries%2==0 ? 60 : 0); /* discontinuity */ else value+=(ticks-100U)/200U; /* Compute relative elapsed correctly in wrap scenario. */ if (mode==13) value=1793019600+(unsigned int)(ticks-(UINT32_MAX-199U))/200U; utc=gmtime(&value); assert(utc); snprintf(info->nitz_time,32,mode==21 ? "%02d/%02d/%02d %02d:%02d:%02d 32 0" : "%02d/%02d/%02d %02d:%02d:%02d +32 00", utc->tm_year%100,utc->tm_mon+1,utc->tm_mday,utc->tm_hour,utc->tm_min,utc->tm_sec); return 0; } struct tm *ql_localtime_r(const time_t *epoch,struct tm *out) { time_t local=*epoch+zone_minutes*60; struct tm *value; if (mode==7) return NULL; value=gmtime(&local); assert(value); *out=*value; return out; } time_t time(time_t *out) {(void)out; return app_epoch;} int ql_rtc_set_time(ql_rtc_time_t *calendar) { long long value; ++sets; saved=*calendar; if (mode==8) return -1; assert(calendar_epoch(calendar->tm_year,calendar->tm_mon,calendar->tm_mday, calendar->tm_hour,calendar->tm_min,calendar->tm_sec,&value)==0); if (mode!=9) app_epoch=(time_t)(value-zone_minutes*60); if (mode==10) app_epoch+=28800; /* catches double timezone conversion */ return 0; } int ql_rtc_get_time(ql_rtc_time_t *out) {*out=saved; if (mode==20) out->tm_year=1970; return mode==11 ? -1 : 0;} int ql_rtos_task_create(ql_task_t *task,unsigned int size,unsigned char priority,char *name, void (*entry)(void *),void *arg,...) { assert(size>=4096 && priority==110 && !strcmp(name,"app_nitz_test")); ++creates; if (mode==19) return -1; *task=(void *)1; entry(arg); return 0; } int ql_rtos_task_delete(ql_task_t task) {assert(task==NULL); return 0;} static void check_parser(void) { time_t epoch; int zone; long long value; assert(parse_nitz("26/10/26 13:00:00 +32 00",&epoch,&zone)==0 && epoch==1793019600 && zone==32); assert(parse_nitz("26/10/26,13:00:00-22",&epoch,&zone)==0 && epoch==1793019600 && zone==-22); assert(parse_nitz("26/10/26 13:00:00 +56 02",&epoch,&zone)==0); assert(parse_nitz("26/10/26 08:00:10 32 0",&epoch,&zone)==0 && epoch==1793001610 && zone==32); assert(parse_nitz("26/10/26 13:00:00 -22 0",&epoch,&zone)==0 && zone==-22); assert(parse_nitz("26/10/26 13:00:00 0 0",&epoch,&zone)==0 && zone==0); assert(parse_nitz("26/10/26 13:00:00 0",&epoch,&zone)!=0); assert(parse_nitz("26/10/26 13:00:00 320 0",&epoch,&zone)!=0); assert(parse_nitz("24/02/29 23:59:59 +00 00",&epoch,&zone)==0); assert(parse_nitz("26/02/29 00:00:00 +00 00",&epoch,&zone)!=0); assert(parse_nitz("38/01/19 03:14:07 +00 00",&epoch,&zone)==0 && epoch==2147483647); assert(parse_nitz("38/01/19 03:14:08 +00 00",&epoch,&zone)!=0); const char *bad[]={"", "26/10/26 13:00:00", "23/12/31 23:59:59 +00 00", "26/00/26 13:00:00 +00 00", "26/13/26 13:00:00 +00 00", "26/10/00 13:00:00 +00 00", "26/04/31 13:00:00 +00 00", "26/10/26 24:00:00 +00 00", "26/10/26 13:60:00 +00 00", "26/10/26 13:00:60 +00 00", "26/10/26 13:00:00 +57 00", "26/10/26 13:00:00 -49 00", "26/10/26 13:00:00 +32 03", "26/10/26 13:00:00 +32 junk", "26/10/26 13:00:00 +32 00junk", "x6/10/26 13:00:00 +32 00"}; for (unsigned int i=0;i=1 && mode<=6) || mode==15 || mode==17 || mode==18 || mode==19 || mode==7 || mode==22) assert(sets==0); else assert(sets==1); assert(queries<=6); assert(probes==(mode==19 ? 0 : 1)); return 0; } '''.replace('APP_SOURCE', str(APP / 'app_nitz_test.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: 'PASS source=nitz', 1: 'FAIL stage=sdk_api_unavailable', 2: 'FAIL stage=no_fresh_nitz', 3: 'FAIL stage=no_fresh_nitz', 4: 'FAIL stage=no_fresh_nitz', 5: 'FAIL stage=no_fresh_nitz', 6: 'cached_or_inconsistent_nitz=1', 7: 'FAIL stage=local_calendar', 8: 'FAIL stage=app_clock_verification', 9: 'FAIL stage=app_clock_verification', 10: 'FAIL stage=app_clock_verification', 11: 'FAIL stage=app_clock_verification', 12: 'PASS source=nitz', 13: 'PASS source=nitz', 14: 'PASS source=nitz', 15: 'FAIL stage=no_fresh_nitz', 16: 'PASS source=nitz', 17: 'FAIL stage=no_fresh_nitz', 18: 'cached_or_inconsistent_nitz=1', 19: 'FAIL stage=create_task', 20: 'FAIL stage=app_clock_verification', 21: 'PASS source=nitz', 22: 'FAIL stage=no_fresh_nitz'} for mode, expected in cases.items(): log = subprocess.check_output([str(work / 'test'), str(mode)], text=True) assert expected in log, log if mode not in (0, 12, 13, 14, 16, 21): assert '[APP-NITZ-TEST] PASS ' not in log, log print('PASS: Gregorian/parser validation and 23 API/freshness/RTC scenarios (ASan/UBSan)')