test: verify operator NITZ to APP RTC without kernel changes
This commit is contained in:
@@ -0,0 +1,55 @@
|
|||||||
|
# 纯 APP NITZ 校时实机测试
|
||||||
|
|
||||||
|
当前 `test/time-fix` 在 PDP 首次激活后自动启动 NITZ 测试,替换上一版自动 NTP 测试。只改 APP;本次没有修改、编译或烧录内核,不依赖之前添加的内核诊断接口。NTP 测试源文件保留但不参与当前 APP 构建。此模块不访问时间服务器域名或 UDP 123,使用设备已有的运营商 NITZ 查询接口。
|
||||||
|
|
||||||
|
## 编译和测试
|
||||||
|
|
||||||
|
在现有 Windows 环境执行 `build.bat app`,按现有流程更新 APP 镜像。保持原内核、原 RTC/NVM 状态。核对 APP 构建成功及镜像容量后启动设备,保留从开机到测试结束、再到一次实际 WSS 连接尝试的完整串口日志。
|
||||||
|
|
||||||
|
应看到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[TIME-DIAG] nitz_enabled=1 app_nitz_test=once_after_pdp ntp_test=off
|
||||||
|
[APP-NITZ-TEST] begin version=1 source=operator_nitz ntp=off
|
||||||
|
```
|
||||||
|
|
||||||
|
本机只进行了主机模拟测试和 ARM 目标语法检查,尚未完成 Windows 完整固件构建或实机测试。
|
||||||
|
|
||||||
|
## 实际流程
|
||||||
|
|
||||||
|
1. 检查原内核是否导出公开 SDK 接口 `ql_nw_get_nitz_time_info`、`ql_localtime_r`、`ql_rtc_set_time`、`ql_rtc_get_time`。
|
||||||
|
2. 查询运营商 NITZ,输出完整原始字符串、返回值、`abs_time`、`leap_sec` 和查询耗时。支持 SDK 文档格式 `YY/MM/DD HH:MM:SS +32 00` 和紧凑格式 `YY/MM/DD,HH:MM:SS+32`。严格检查日期、闰年、时区及 UTC 范围(2024 年起,至 signed 32 位秒数上限)。不把语义未明确的 `abs_time` 当 Unix 时间戳使用。
|
||||||
|
3. 间隔 3 秒再次查询。两次 UTC 必须向前推进,并与系统运行计数的增量相差不超过 1 秒,时区相同;单次查询超过 2 秒不采用。冻结的 NITZ 快照不写入 APP。最多尝试 3 组,组间等待 5 秒。
|
||||||
|
4. 使用 NITZ 的 UT 日期字段生成 UTC 秒数。时区字段以 15 分钟为单位,只记录,不再次从 UT 扣除。这个解释依据仓库旧 SDK 镜像的 NITZ 转换逻辑:UT 日期转换后加时区生成 SYS 本地日历。原始值和解释会保留在日志中,供实机核对。
|
||||||
|
5. 通过 SDK `ql_localtime_r` 按现有 PMIC 时区把 UTC 转成 APP RTC 日历,再调用 `ql_rtc_set_time`。不改时区、不操作 `app_ever` 或内部跟随标记、不直接写 NVM 文件。
|
||||||
|
6. 读回 `ql_rtc_get_time` 日历和 `time(NULL)` 的 UTC 返回值,两者都必须与目标相差不超过 3 秒,并且 SDK 返回成功,才输出 `PASS`。设置后无论成功失败,都结束本次测试,避免反复写 RTC。
|
||||||
|
|
||||||
|
测试任务独立运行,不等待它就可以继续 MQTT 流程。每次启动完成一次测试;仅任务创建失败时允许下一次 PDP 激活重试。此版本没有周期重校、WSS 启动等待或主动重连逻辑,不是完整发布方案。
|
||||||
|
|
||||||
|
## 日志判断
|
||||||
|
|
||||||
|
以下是格式示例,不是设备实测结果:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[APP-NITZ-TEST] sample attempt=1 index=1 rc=0 raw="26/10/26 06:00:00 +32 00" abs_time=0 leap_sec=0 query_ms=...
|
||||||
|
[APP-NITZ-TEST] parsed utc=... zone_quarters=32 zone_minutes=480
|
||||||
|
[APP-NITZ-TEST] freshness advance_s=3 elapsed_s=3 drift_s=0
|
||||||
|
[APP-NITZ-TEST] set_begin app_before=... expected_utc=... pmic_zone_minutes=480 app_calendar=2026-10-26T14:00:03
|
||||||
|
[APP-NITZ-TEST] readback set_rc=0 rtc_rc=0 app_after=... expected_utc=... delta_s=0 calendar=...
|
||||||
|
[APP-NITZ-TEST] PASS source=nitz app_utc=verified wss_requires_separate_handshake_check=1
|
||||||
|
[APP-NITZ-TEST] finished one_test_per_boot=1
|
||||||
|
```
|
||||||
|
|
||||||
|
- `sdk_api_unavailable`:原内核缺少所需公开接口,本次不写 RTC。
|
||||||
|
- `stage=parse`:时间为空、格式不支持或日期/范围无效。保留 `sample` 原文以便分析。
|
||||||
|
- `stage=freshness`:两次时间冻结、跳变或与运行计时不符,不采用。若三组都失败,会输出 `no_fresh_nitz rtc_not_written=1`。
|
||||||
|
- `stage=local_calendar`:SDK 时区转换未产生可用日历,不写 RTC。
|
||||||
|
- `stage=app_clock_verification`:设置返回失败,或者读回 UTC/日历不符。需要分析是否有时区或 APP RTC 写入问题。
|
||||||
|
|
||||||
|
`PASS` 只证明 NITZ 候选值推进正常、APP 写入和读回相符,无法单独证明运营商绝对时间准确或当前固件的 NITZ UT 语义与旧 SDK 完全一致。实机仍需把原始 NITZ、APP UTC 和实际北京时间对照,尤其检查整 8 小时偏差。它也不等于 WSS 握手或 OTA 下载成功;这两项继续看后续实际连接日志,TLS 校验保持开启。
|
||||||
|
|
||||||
|
SDK 可能只提供 NITZ 最近一次通知的快照;如出现冻结拒绝,不应去掉新鲜度检查直接写入,应据日志评估带接收时刻的 NITZ 事件或已有业务服务器授时路径。本测试不注册新网络回调,不抢占现有网络事件处理。
|
||||||
|
|
||||||
|
## 主机回归
|
||||||
|
|
||||||
|
`python3 tools/test_app_nitz_test.py` 编译实际 C 模块,模拟 API 缺失、查询失败、空串、无终止符、无效日期、冻结时间、UTC 跨天、时区转换、运行计数回绕、signed 32 位边界、首次查询失败恢复、写入失败、读回偏差及任务创建失败。启用 ASan/UBSan;不发送设备或公网数据。
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# 纯 APP 的设备侧 NTP 测试
|
# 纯 APP 的设备侧 NTP 测试
|
||||||
|
|
||||||
|
当前分支已切换为自动 NITZ 测试(见 `docs/app_nitz_test.md`);下文描述前一版 NTP 测试,当前不会自动启动,也不参与 APP 构建。
|
||||||
|
|
||||||
本次测试只新增 APP 代码,不需要编译、修改或更新内核。测试使用设备自己的 PDP 1 和 SIM 网络,不使用电脑网络来判断白名单是否放行。
|
本次测试只新增 APP 代码,不需要编译、修改或更新内核。测试使用设备自己的 PDP 1 和 SIM 网络,不使用电脑网络来判断白名单是否放行。
|
||||||
|
|
||||||
## 编译与运行
|
## 编译与运行
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ SRC_FILES+= \
|
|||||||
mqtt.c \
|
mqtt.c \
|
||||||
mqtt_connect_diag.c \
|
mqtt_connect_diag.c \
|
||||||
time_diag.c \
|
time_diag.c \
|
||||||
app_ntp_test.c \
|
app_nitz_test.c \
|
||||||
vad_det.c \
|
vad_det.c \
|
||||||
sleep_wake.c \
|
sleep_wake.c \
|
||||||
battery.c \
|
battery.c \
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "ql_rtos.h"
|
||||||
|
#include "ql_func.h"
|
||||||
|
#include "ql_nw.h"
|
||||||
|
#include "ql_rtc.h"
|
||||||
|
#include "app_nitz_test.h"
|
||||||
|
|
||||||
|
/* SDK wrapper uses the same PMIC timezone as time(); no libc TZ assumptions. */
|
||||||
|
extern struct tm *ql_localtime_r(const time_t *, struct tm *);
|
||||||
|
#define MIN_EPOCH 1704067200LL
|
||||||
|
#define MAX_EPOCH 2147483647LL
|
||||||
|
#define ATTEMPTS 3
|
||||||
|
#define SAMPLE_MS 3000U
|
||||||
|
|
||||||
|
static ql_task_t test_task;
|
||||||
|
static int test_started;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
time_t utc;
|
||||||
|
unsigned int ticks;
|
||||||
|
int zone_quarters;
|
||||||
|
} nitz_sample;
|
||||||
|
|
||||||
|
static int leap(int year)
|
||||||
|
{
|
||||||
|
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Explicit Gregorian conversion: no mktime() or APP clock dependency. */
|
||||||
|
static int calendar_epoch(int year, int month, int day, int hour, int minute,
|
||||||
|
int second, long long *result)
|
||||||
|
{
|
||||||
|
static const int days[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
||||||
|
long long total = 0;
|
||||||
|
int y, m;
|
||||||
|
if (year < 1970 || year > 2038 || month < 1 || month > 12 ||
|
||||||
|
day < 1 || day > days[month - 1] + (month == 2 && leap(year)) ||
|
||||||
|
hour < 0 || hour > 23 || minute < 0 || minute > 59 ||
|
||||||
|
second < 0 || second > 59) return -1;
|
||||||
|
for (y = 1970; y < year; ++y) total += 365 + leap(y);
|
||||||
|
for (m = 1; m < month; ++m) total += days[m - 1] + (m == 2 && leap(year));
|
||||||
|
*result = ((total + day - 1) * 24 + hour) * 3600 + minute * 60 + second;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int two_digits(const char *s)
|
||||||
|
{
|
||||||
|
if (s[0] < '0' || s[0] > '9' || s[1] < '0' || s[1] > '9') return -1;
|
||||||
|
return (s[0] - '0') * 10 + s[1] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Accept SDK documented "YY/MM/DD HH:MM:SS +32 00" and compact
|
||||||
|
* "YY/MM/DD,HH:MM:SS+32". NITZ UT is UTC; zone is metadata, not subtracted.
|
||||||
|
* abs_time/leap_sec have insufficient documented semantics to use as epoch. */
|
||||||
|
static int parse_nitz(const char *s, time_t *epoch, int *zone)
|
||||||
|
{
|
||||||
|
size_t n = strlen(s), pos = 17;
|
||||||
|
int yy, mo, dd, hh, mm, ss, q, dst;
|
||||||
|
long long value;
|
||||||
|
if (n < 20 || s[2] != '/' || s[5] != '/' ||
|
||||||
|
(s[8] != ' ' && s[8] != ',') || s[11] != ':' || s[14] != ':') return -1;
|
||||||
|
yy = two_digits(s); mo = two_digits(s + 3); dd = two_digits(s + 6);
|
||||||
|
hh = two_digits(s + 9); mm = two_digits(s + 12); ss = two_digits(s + 15);
|
||||||
|
if (yy < 0 || calendar_epoch(2000 + yy, mo, dd, hh, mm, ss, &value) != 0 ||
|
||||||
|
value < MIN_EPOCH || value > MAX_EPOCH) return -2;
|
||||||
|
if (s[pos] == ' ') ++pos;
|
||||||
|
if (n < pos + 3 || (s[pos] != '+' && s[pos] != '-')) return -3;
|
||||||
|
q = two_digits(s + pos + 1);
|
||||||
|
if (q < 0 || q > 56 || (s[pos] == '-' && q > 48)) return -3;
|
||||||
|
*zone = s[pos] == '-' ? -q : q;
|
||||||
|
pos += 3;
|
||||||
|
if (pos < n) {
|
||||||
|
if (n != pos + 3 || s[pos] != ' ') return -4;
|
||||||
|
dst = two_digits(s + pos + 1);
|
||||||
|
if (dst < 0 || dst > 2) return -4;
|
||||||
|
}
|
||||||
|
*epoch = (time_t)value;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int get_sample(int attempt, int index, nitz_sample *sample)
|
||||||
|
{
|
||||||
|
QL_NW_NITZ_TIME_INFO_T info;
|
||||||
|
unsigned int began = ql_rtos_get_systicks();
|
||||||
|
int rc, parsed;
|
||||||
|
memset(&info, 0, sizeof(info));
|
||||||
|
rc = ql_nw_get_nitz_time_info(&info);
|
||||||
|
sample->ticks = ql_rtos_get_systicks();
|
||||||
|
printf("[APP-NITZ-TEST] sample attempt=%d index=%d rc=%d raw=\"%.*s\" "
|
||||||
|
"abs_time=%lu leap_sec=%u query_ms=%lu\r\n", attempt, index, rc,
|
||||||
|
(int)sizeof(info.nitz_time), info.nitz_time, info.abs_time,
|
||||||
|
(unsigned int)info.leap_sec,
|
||||||
|
(unsigned long)(sample->ticks - began) * 5UL);
|
||||||
|
if (rc != QL_NW_SUCCESS ||
|
||||||
|
memchr(info.nitz_time, '\0', sizeof(info.nitz_time)) == NULL) return -1;
|
||||||
|
parsed = parse_nitz(info.nitz_time, &sample->utc, &sample->zone_quarters);
|
||||||
|
if (parsed != 0) {
|
||||||
|
printf("[APP-NITZ-TEST] rejected stage=parse reason=%d\r\n", parsed);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
/* A long blocking query makes the timestamp's age ambiguous. */
|
||||||
|
if (sample->ticks - began > 400U) return -1;
|
||||||
|
printf("[APP-NITZ-TEST] parsed utc=%ld zone_quarters=%d zone_minutes=%d\r\n",
|
||||||
|
(long)sample->utc, sample->zone_quarters, sample->zone_quarters * 15);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int write_and_verify(const nitz_sample *reference)
|
||||||
|
{
|
||||||
|
struct tm local;
|
||||||
|
ql_rtc_time_t target, actual;
|
||||||
|
time_t epoch, before = time(NULL), after;
|
||||||
|
long long expected, local_epoch, delta, actual_local = 0;
|
||||||
|
int rc, read_rc, calendar_ok;
|
||||||
|
expected = (long long)reference->utc +
|
||||||
|
(ql_rtos_get_systicks() - reference->ticks) / 200U;
|
||||||
|
if (expected > MAX_EPOCH) {
|
||||||
|
printf("[APP-NITZ-TEST] FAIL stage=epoch_range\r\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
epoch = (time_t)expected;
|
||||||
|
memset(&local, 0, sizeof(local));
|
||||||
|
if (ql_localtime_r(&epoch, &local) == NULL ||
|
||||||
|
calendar_epoch(local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
|
||||||
|
local.tm_hour, local.tm_min, local.tm_sec, &local_epoch) != 0 ||
|
||||||
|
local_epoch < MIN_EPOCH || local_epoch > MAX_EPOCH ||
|
||||||
|
local_epoch - expected < -43200 || local_epoch - expected > 50400) {
|
||||||
|
printf("[APP-NITZ-TEST] FAIL stage=local_calendar\r\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memset(&target, 0, sizeof(target));
|
||||||
|
target.tm_year = local.tm_year + 1900;
|
||||||
|
target.tm_mon = local.tm_mon + 1;
|
||||||
|
target.tm_mday = local.tm_mday;
|
||||||
|
target.tm_hour = local.tm_hour;
|
||||||
|
target.tm_min = local.tm_min;
|
||||||
|
target.tm_sec = local.tm_sec;
|
||||||
|
target.tm_wday = local.tm_wday;
|
||||||
|
printf("[APP-NITZ-TEST] set_begin app_before=%ld expected_utc=%ld "
|
||||||
|
"pmic_zone_minutes=%ld app_calendar=%04d-%02d-%02dT%02d:%02d:%02d\r\n",
|
||||||
|
(long)before, (long)epoch, (long)((local_epoch - expected) / 60),
|
||||||
|
target.tm_year, target.tm_mon, target.tm_mday,
|
||||||
|
target.tm_hour, target.tm_min, target.tm_sec);
|
||||||
|
rc = ql_rtc_set_time(&target);
|
||||||
|
memset(&actual, 0, sizeof(actual));
|
||||||
|
read_rc = ql_rtc_get_time(&actual);
|
||||||
|
after = time(NULL);
|
||||||
|
expected = (long long)reference->utc +
|
||||||
|
(ql_rtos_get_systicks() - reference->ticks) / 200U;
|
||||||
|
delta = (long long)after - expected;
|
||||||
|
calendar_ok = calendar_epoch(actual.tm_year, actual.tm_mon, actual.tm_mday,
|
||||||
|
actual.tm_hour, actual.tm_min, actual.tm_sec,
|
||||||
|
&actual_local) == 0;
|
||||||
|
actual_local -= local_epoch + (expected - (long long)epoch);
|
||||||
|
printf("[APP-NITZ-TEST] readback set_rc=%d rtc_rc=%d app_after=%ld "
|
||||||
|
"expected_utc=%lld delta_s=%lld calendar=%04d-%02d-%02dT%02d:%02d:%02d\r\n",
|
||||||
|
rc, read_rc, (long)after, expected, delta, actual.tm_year, actual.tm_mon,
|
||||||
|
actual.tm_mday, actual.tm_hour, actual.tm_min, actual.tm_sec);
|
||||||
|
if (rc != 0 || read_rc != 0 || after < MIN_EPOCH ||
|
||||||
|
delta < -3 || delta > 3 || !calendar_ok ||
|
||||||
|
actual_local < -3 || actual_local > 3) {
|
||||||
|
printf("[APP-NITZ-TEST] FAIL stage=app_clock_verification\r\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
printf("[APP-NITZ-TEST] PASS source=nitz app_utc=verified "
|
||||||
|
"wss_requires_separate_handshake_check=1\r\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nitz_test_worker(void *arg)
|
||||||
|
{
|
||||||
|
nitz_sample first, second;
|
||||||
|
int attempt;
|
||||||
|
long long advance, elapsed, drift;
|
||||||
|
unsigned int span;
|
||||||
|
(void)arg;
|
||||||
|
printf("[APP-NITZ-TEST] begin version=1 source=operator_nitz ntp=off\r\n");
|
||||||
|
if (!m_get_api_ptr || !m_get_api_ptr("ql_nw_get_nitz_time_info") ||
|
||||||
|
!m_get_api_ptr("ql_localtime_r") || !m_get_api_ptr("ql_rtc_set_time") ||
|
||||||
|
!m_get_api_ptr("ql_rtc_get_time")) {
|
||||||
|
printf("[APP-NITZ-TEST] FAIL stage=sdk_api_unavailable\r\n");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
for (attempt = 1; attempt <= ATTEMPTS; ++attempt) {
|
||||||
|
if (get_sample(attempt, 1, &first) == 0) {
|
||||||
|
ql_rtos_task_sleep_ms(SAMPLE_MS);
|
||||||
|
if (get_sample(attempt, 2, &second) == 0) {
|
||||||
|
span = second.ticks - first.ticks; /* unsigned handles tick wrap */
|
||||||
|
elapsed = span / 200U;
|
||||||
|
advance = (long long)second.utc - first.utc;
|
||||||
|
drift = advance - elapsed;
|
||||||
|
printf("[APP-NITZ-TEST] freshness advance_s=%lld elapsed_s=%lld "
|
||||||
|
"drift_s=%lld\r\n", advance, elapsed, drift);
|
||||||
|
if (span >= 400U && span <= 2000U && advance > 0 &&
|
||||||
|
drift >= -1 && drift <= 1 &&
|
||||||
|
first.zone_quarters == second.zone_quarters) {
|
||||||
|
write_and_verify(&second);
|
||||||
|
goto done; /* Never repeat an uncertain RTC write. */
|
||||||
|
}
|
||||||
|
printf("[APP-NITZ-TEST] rejected stage=freshness "
|
||||||
|
"cached_or_inconsistent_nitz=1 rtc_not_written=1\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attempt < ATTEMPTS) ql_rtos_task_sleep_ms(5000);
|
||||||
|
}
|
||||||
|
printf("[APP-NITZ-TEST] FAIL stage=no_fresh_nitz rtc_not_written=1\r\n");
|
||||||
|
done:
|
||||||
|
printf("[APP-NITZ-TEST] finished one_test_per_boot=1\r\n");
|
||||||
|
ql_rtos_task_delete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_nitz_test_start(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
if (test_started) return; /* PDP management is the sole caller. */
|
||||||
|
test_started = 1;
|
||||||
|
rc = ql_rtos_task_create(&test_task, 6 * 1024, 110, "app_nitz_test",
|
||||||
|
nitz_test_worker, NULL);
|
||||||
|
if (rc != 0) {
|
||||||
|
test_started = 0;
|
||||||
|
printf("[APP-NITZ-TEST] FAIL stage=create_task rc=%d\r\n", rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef APP_NITZ_TEST_H
|
||||||
|
#define APP_NITZ_TEST_H
|
||||||
|
|
||||||
|
void app_nitz_test_start(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
#include "http_downfile.h"
|
#include "http_downfile.h"
|
||||||
#include "ota_post_update.h"
|
#include "ota_post_update.h"
|
||||||
#include "time_diag.h"
|
#include "time_diag.h"
|
||||||
#include "app_ntp_test.h"
|
#include "app_nitz_test.h"
|
||||||
|
|
||||||
//#define NTP_SERVER_DOMAIN "ntp.aliyun.com"
|
//#define NTP_SERVER_DOMAIN "ntp.aliyun.com"
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ void ql_datacall_demo_thread(void *arvg)
|
|||||||
if(ret == 0)
|
if(ret == 0)
|
||||||
{
|
{
|
||||||
talkingq_time_diag_log("pdp-active");
|
talkingq_time_diag_log("pdp-active");
|
||||||
app_ntp_test_start();
|
app_nitz_test_start();
|
||||||
ql_get_data_call_info(1, 0, &info);
|
ql_get_data_call_info(1, 0, &info);
|
||||||
printf("info.profile_idx: %d\r\n", info.profile_idx);
|
printf("info.profile_idx: %d\r\n", info.profile_idx);
|
||||||
printf("info.ip_version: %d\r\n", info.ip_version);
|
printf("info.ip_version: %d\r\n", info.ip_version);
|
||||||
@@ -201,7 +201,7 @@ int app_net_mgt_init()
|
|||||||
// ql_rtos_semaphore_create(&g_ql_ntp_sem,0);
|
// ql_rtos_semaphore_create(&g_ql_ntp_sem,0);
|
||||||
|
|
||||||
ql_rtc_set_nitz_mode(1);
|
ql_rtc_set_nitz_mode(1);
|
||||||
printf("[TIME-DIAG] nitz_enabled=1 app_ntp_test=once_after_pdp\r\n");
|
printf("[TIME-DIAG] nitz_enabled=1 app_nitz_test=once_after_pdp ntp_test=off\r\n");
|
||||||
talkingq_time_diag_log("nitz-enabled");
|
talkingq_time_diag_log("nitz-enabled");
|
||||||
|
|
||||||
ql_task_t appTaskRef = NULL;
|
ql_task_t appTaskRef = NULL;
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
"""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 <stddef.h>
|
||||||
|
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 <stdint.h>\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 <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "APP_SOURCE"
|
||||||
|
static int mode, creates, queries, sets;
|
||||||
|
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) {strcpy(info->nitz_time,"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,"%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("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 0", "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<sizeof(bad)/sizeof(bad[0]);++i)
|
||||||
|
assert(parse_nitz(bad[i],&epoch,&zone)!=0);
|
||||||
|
assert(calendar_epoch(2024,2,29,23,59,59,&value)==0);
|
||||||
|
long long next;
|
||||||
|
assert(calendar_epoch(2024,3,1,0,0,0,&next)==0 && next-value==1);
|
||||||
|
}
|
||||||
|
int main(int argc,char **argv) {
|
||||||
|
assert(argc==2); mode=atoi(argv[1]); check_parser();
|
||||||
|
if (mode==12) zone_minutes=-330;
|
||||||
|
if (mode==13) ticks=UINT32_MAX-199U;
|
||||||
|
app_nitz_test_start(); app_nitz_test_start();
|
||||||
|
assert(creates==(mode==19 ? 2 : 1));
|
||||||
|
if ((mode>=1 && mode<=6) || mode==15 || mode==17 || mode==18 || mode==19 || mode==7)
|
||||||
|
assert(sets==0);
|
||||||
|
else assert(sets==1);
|
||||||
|
assert(queries<=6);
|
||||||
|
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'}
|
||||||
|
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):
|
||||||
|
assert '[APP-NITZ-TEST] PASS ' not in log, log
|
||||||
|
print('PASS: Gregorian/parser validation and 21 API/freshness/RTC scenarios (ASan/UBSan)')
|
||||||
Reference in New Issue
Block a user