test: probe existing SYS clock access and AT time queries from APP
This commit is contained in:
+19
-2
@@ -2,6 +2,17 @@
|
|||||||
|
|
||||||
当前 `test/time-fix` 在 PDP 首次激活后自动启动 NITZ 测试,替换上一版自动 NTP 测试。只改 APP;本次没有修改、编译或烧录内核,不依赖之前添加的内核诊断接口。NTP 测试源文件保留但不参与当前 APP 构建。此模块不访问时间服务器域名或 UDP 123,使用设备已有的运营商 NITZ 查询接口。
|
当前 `test/time-fix` 在 PDP 首次激活后自动启动 NITZ 测试,替换上一版自动 NTP 测试。只改 APP;本次没有修改、编译或烧录内核,不依赖之前添加的内核诊断接口。NTP 测试源文件保留但不参与当前 APP 构建。此模块不访问时间服务器域名或 UDP 123,使用设备已有的运营商 NITZ 查询接口。
|
||||||
|
|
||||||
|
## 新增 SYS 读取探测
|
||||||
|
|
||||||
|
当前版本在 NITZ 测试之前运行 `[APP-SYS-PROBE]`,只使用当前源码声明过的函数签名和 APP 已有 AT 接口,不读取旧 AXF,不修改内核,不调用 `talkingq_time_diag_snapshot_v1`。原有 TIME-DIAG 日志是否可用,不影响本探测。
|
||||||
|
|
||||||
|
- 动态查找 `PMIC_RTC_GetTime_Count` 和 `GetTimeZone`。如果原内核本来就导出这两个名字,调用 `SYS_OFFSET=0`,减去网络时区分钟数,间隔 3 秒读取两次,检查合理范围和推进量。找不到就输出 `direct_sys=unavailable`,不添加内核导出、不尝试固定地址。
|
||||||
|
- 输出 `time(NULL)`、`ql_rtc_get_time_ex()` 作为对照,不把 `get_time_ex` 的名称当成读 SYS 的证据。
|
||||||
|
- 通过 `ql_atcmd_send_sync` 连续两轮读取 `AT+QLTS=0`、`AT+QLTS=1`、`AT+QLTS=2`、`AT+CCLK?`。两轮间隔 3 秒,每个 AT 请求超时参数为 2 秒。记录返回码、运行计数和原始响应;本型号不支持时允许返回 ERROR。两个 QLTS 候选模式是否提供有效当前时间,需根据原始日志对照确认。
|
||||||
|
- 本探测没有 RTC 设置调用。`direct_sys=candidate` 仅表示现有 SYS 函数可读、时间范围和推进正常,仍需与实际 UTC 核对;AT 返回成功也不等于已确认来源。
|
||||||
|
|
||||||
|
刷入后提供从 `[APP-SYS-PROBE] begin` 到 `finished` 的完整输出。若 QLTS 返回正确且推进的当前时间,可进一步实现 APP 校时;若所有接口缺失/返回错误或只返回 APP 旧时间,该路线仍未得到可用依据。后续 NITZ 测试保持原有写入条件,快照冻结时不写 RTC。
|
||||||
|
|
||||||
## 编译和测试
|
## 编译和测试
|
||||||
|
|
||||||
在现有 Windows 环境执行 `build.bat app`,按现有流程更新 APP 镜像。保持原内核、原 RTC/NVM 状态。核对 APP 构建成功及镜像容量后启动设备,保留从开机到测试结束、再到一次实际 WSS 连接尝试的完整串口日志。
|
在现有 Windows 环境执行 `build.bat app`,按现有流程更新 APP 镜像。保持原内核、原 RTC/NVM 状态。核对 APP 构建成功及镜像容量后启动设备,保留从开机到测试结束、再到一次实际 WSS 连接尝试的完整串口日志。
|
||||||
@@ -10,7 +21,11 @@
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
[TIME-DIAG] nitz_enabled=1 app_nitz_test=once_after_pdp ntp_test=off
|
[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
|
[APP-SYS-PROBE] begin version=1 read_only=1 kernel_diag_used=0
|
||||||
|
[APP-SYS-PROBE] exports sys_count=... network_zone=...
|
||||||
|
...
|
||||||
|
[APP-SYS-PROBE] finished rtc_not_written=1
|
||||||
|
[APP-NITZ-TEST] begin version=2 source=operator_nitz ntp=off
|
||||||
```
|
```
|
||||||
|
|
||||||
本机只进行了主机模拟测试和 ARM 目标语法检查,尚未完成 Windows 完整固件构建或实机测试。
|
本机只进行了主机模拟测试和 ARM 目标语法检查,尚未完成 Windows 完整固件构建或实机测试。
|
||||||
@@ -18,7 +33,7 @@
|
|||||||
## 实际流程
|
## 实际流程
|
||||||
|
|
||||||
1. 检查原内核是否导出公开 SDK 接口 `ql_nw_get_nitz_time_info`、`ql_localtime_r`、`ql_rtc_set_time`、`ql_rtc_get_time`。
|
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 时间戳使用。
|
2. 查询运营商 NITZ,输出完整原始字符串、返回值、`abs_time`、`leap_sec` 和查询耗时。支持 SDK 文档格式 `YY/MM/DD HH:MM:SS +32 00` 、紧凑格式 `YY/MM/DD,HH:MM:SS+32`,以及实机已返回的无正号、一位夏令时格式 `YY/MM/DD HH:MM:SS 32 0`。严格检查日期、闰年、时区及 UTC 范围(2024 年起,至 signed 32 位秒数上限)。不把语义未明确的 `abs_time` 当 Unix 时间戳使用。
|
||||||
3. 间隔 3 秒再次查询。两次 UTC 必须向前推进,并与系统运行计数的增量相差不超过 1 秒,时区相同;单次查询超过 2 秒不采用。冻结的 NITZ 快照不写入 APP。最多尝试 3 组,组间等待 5 秒。
|
3. 间隔 3 秒再次查询。两次 UTC 必须向前推进,并与系统运行计数的增量相差不超过 1 秒,时区相同;单次查询超过 2 秒不采用。冻结的 NITZ 快照不写入 APP。最多尝试 3 组,组间等待 5 秒。
|
||||||
4. 使用 NITZ 的 UT 日期字段生成 UTC 秒数。时区字段以 15 分钟为单位,只记录,不再次从 UT 扣除。这个解释依据仓库旧 SDK 镜像的 NITZ 转换逻辑:UT 日期转换后加时区生成 SYS 本地日历。原始值和解释会保留在日志中,供实机核对。
|
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 文件。
|
5. 通过 SDK `ql_localtime_r` 按现有 PMIC 时区把 UTC 转成 APP RTC 日历,再调用 `ql_rtc_set_time`。不改时区、不操作 `app_ever` 或内部跟随标记、不直接写 NVM 文件。
|
||||||
@@ -53,3 +68,5 @@ SDK 可能只提供 NITZ 最近一次通知的快照;如出现冻结拒绝,
|
|||||||
## 主机回归
|
## 主机回归
|
||||||
|
|
||||||
`python3 tools/test_app_nitz_test.py` 编译实际 C 模块,模拟 API 缺失、查询失败、空串、无终止符、无效日期、冻结时间、UTC 跨天、时区转换、运行计数回绕、signed 32 位边界、首次查询失败恢复、写入失败、读回偏差及任务创建失败。启用 ASan/UBSan;不发送设备或公网数据。
|
`python3 tools/test_app_nitz_test.py` 编译实际 C 模块,模拟 API 缺失、查询失败、空串、无终止符、无效日期、冻结时间、UTC 跨天、时区转换、运行计数回绕、signed 32 位边界、首次查询失败恢复、写入失败、读回偏差及任务创建失败。启用 ASan/UBSan;不发送设备或公网数据。
|
||||||
|
|
||||||
|
`python3 tools/test_app_sys_time_probe.py` 对实际 SYS/AT 探测模块验证 10 种场景:接口缺失、动态读取、无效/冻结时间、AT 错误、无终止符响应、tick 回绕等。测试不链接 RTC setter 或新增诊断接口;主机测试不证明本型号支持 QLTS。
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ SRC_FILES+= \
|
|||||||
mqtt_connect_diag.c \
|
mqtt_connect_diag.c \
|
||||||
time_diag.c \
|
time_diag.c \
|
||||||
app_nitz_test.c \
|
app_nitz_test.c \
|
||||||
|
app_sys_time_probe.c \
|
||||||
vad_det.c \
|
vad_det.c \
|
||||||
sleep_wake.c \
|
sleep_wake.c \
|
||||||
battery.c \
|
battery.c \
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "ql_nw.h"
|
#include "ql_nw.h"
|
||||||
#include "ql_rtc.h"
|
#include "ql_rtc.h"
|
||||||
#include "app_nitz_test.h"
|
#include "app_nitz_test.h"
|
||||||
|
#include "app_sys_time_probe.h"
|
||||||
|
|
||||||
/* SDK wrapper uses the same PMIC timezone as time(); no libc TZ assumptions. */
|
/* SDK wrapper uses the same PMIC timezone as time(); no libc TZ assumptions. */
|
||||||
extern struct tm *ql_localtime_r(const time_t *, struct tm *);
|
extern struct tm *ql_localtime_r(const time_t *, struct tm *);
|
||||||
@@ -52,13 +53,14 @@ static int two_digits(const char *s)
|
|||||||
return (s[0] - '0') * 10 + s[1] - '0';
|
return (s[0] - '0') * 10 + s[1] - '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Accept SDK documented "YY/MM/DD HH:MM:SS +32 00" and compact
|
/* Accept actual "YY/MM/DD HH:MM:SS 32 0", documented signed format, and compact
|
||||||
* "YY/MM/DD,HH:MM:SS+32". NITZ UT is UTC; zone is metadata, not subtracted.
|
* "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. */
|
* abs_time/leap_sec have insufficient documented semantics to use as epoch. */
|
||||||
static int parse_nitz(const char *s, time_t *epoch, int *zone)
|
static int parse_nitz(const char *s, time_t *epoch, int *zone)
|
||||||
{
|
{
|
||||||
size_t n = strlen(s), pos = 17;
|
size_t n = strlen(s), pos = 17;
|
||||||
int yy, mo, dd, hh, mm, ss, q, dst;
|
int yy, mo, dd, hh, mm, ss, q, dst, negative = 0;
|
||||||
|
size_t start;
|
||||||
long long value;
|
long long value;
|
||||||
if (n < 20 || s[2] != '/' || s[5] != '/' ||
|
if (n < 20 || s[2] != '/' || s[5] != '/' ||
|
||||||
(s[8] != ' ' && s[8] != ',') || s[11] != ':' || s[14] != ':') return -1;
|
(s[8] != ' ' && s[8] != ',') || s[11] != ':' || s[14] != ':') return -1;
|
||||||
@@ -66,16 +68,23 @@ static int parse_nitz(const char *s, time_t *epoch, int *zone)
|
|||||||
hh = two_digits(s + 9); mm = two_digits(s + 12); ss = two_digits(s + 15);
|
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 ||
|
if (yy < 0 || calendar_epoch(2000 + yy, mo, dd, hh, mm, ss, &value) != 0 ||
|
||||||
value < MIN_EPOCH || value > MAX_EPOCH) return -2;
|
value < MIN_EPOCH || value > MAX_EPOCH) return -2;
|
||||||
|
/* Actual SDK output is also "26/10/26 08:00:10 32 0". */
|
||||||
if (s[pos] == ' ') ++pos;
|
if (s[pos] == ' ') ++pos;
|
||||||
if (n < pos + 3 || (s[pos] != '+' && s[pos] != '-')) return -3;
|
else if (s[pos] != '+' && s[pos] != '-') return -3;
|
||||||
q = two_digits(s + pos + 1);
|
if (s[pos] == '+' || s[pos] == '-') negative = s[pos++] == '-';
|
||||||
if (q < 0 || q > 56 || (s[pos] == '-' && q > 48)) return -3;
|
start = pos;
|
||||||
*zone = s[pos] == '-' ? -q : q;
|
q = 0;
|
||||||
pos += 3;
|
while (pos < n && s[pos] >= '0' && s[pos] <= '9' && pos - start < 2)
|
||||||
|
q = q * 10 + s[pos++] - '0';
|
||||||
|
if (pos == start || q > 56 || (negative && q > 48)) return -3;
|
||||||
|
*zone = negative ? -q : q;
|
||||||
if (pos < n) {
|
if (pos < n) {
|
||||||
if (n != pos + 3 || s[pos] != ' ') return -4;
|
if (s[pos++] != ' ') return -4;
|
||||||
dst = two_digits(s + pos + 1);
|
start = pos;
|
||||||
if (dst < 0 || dst > 2) return -4;
|
dst = 0;
|
||||||
|
while (pos < n && s[pos] >= '0' && s[pos] <= '9' && pos - start < 2)
|
||||||
|
dst = dst * 10 + s[pos++] - '0';
|
||||||
|
if (pos == start || pos != n || dst > 2) return -4;
|
||||||
}
|
}
|
||||||
*epoch = (time_t)value;
|
*epoch = (time_t)value;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -177,7 +186,8 @@ static void nitz_test_worker(void *arg)
|
|||||||
long long advance, elapsed, drift;
|
long long advance, elapsed, drift;
|
||||||
unsigned int span;
|
unsigned int span;
|
||||||
(void)arg;
|
(void)arg;
|
||||||
printf("[APP-NITZ-TEST] begin version=1 source=operator_nitz ntp=off\r\n");
|
app_sys_time_probe_run();
|
||||||
|
printf("[APP-NITZ-TEST] begin version=2 source=operator_nitz ntp=off\r\n");
|
||||||
if (!m_get_api_ptr || !m_get_api_ptr("ql_nw_get_nitz_time_info") ||
|
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_localtime_r") || !m_get_api_ptr("ql_rtc_set_time") ||
|
||||||
!m_get_api_ptr("ql_rtc_get_time")) {
|
!m_get_api_ptr("ql_rtc_get_time")) {
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "ql_func.h"
|
||||||
|
#include "ql_rtos.h"
|
||||||
|
#include "ql_rtc.h"
|
||||||
|
#include "ql_atcmd.h"
|
||||||
|
#include "app_sys_time_probe.h"
|
||||||
|
|
||||||
|
/* Exact signatures from the existing kernel pmic_rtc.h/duster.h.
|
||||||
|
* Lookup only: never add exports, use fixed addresses, or use our diagnostic API.
|
||||||
|
* SYS_OFFSET is 0 in pmic_rtc.h. Missing exports are an expected result. */
|
||||||
|
typedef unsigned int (*sys_count_fn)(int);
|
||||||
|
typedef int (*zone_minutes_fn)(void);
|
||||||
|
|
||||||
|
static void probe_sys_exports(void)
|
||||||
|
{
|
||||||
|
sys_count_fn get_count;
|
||||||
|
zone_minutes_fn get_zone;
|
||||||
|
unsigned int raw[2], ticks[2];
|
||||||
|
int zone[2], i;
|
||||||
|
long long utc[2], advance, elapsed, drift;
|
||||||
|
|
||||||
|
get_count = (sys_count_fn)(intptr_t)m_get_api_ptr("PMIC_RTC_GetTime_Count");
|
||||||
|
get_zone = (zone_minutes_fn)(intptr_t)m_get_api_ptr("GetTimeZone");
|
||||||
|
printf("[APP-SYS-PROBE] exports sys_count=%d network_zone=%d\r\n",
|
||||||
|
get_count != NULL, get_zone != NULL);
|
||||||
|
if (!get_count || !get_zone) {
|
||||||
|
printf("[APP-SYS-PROBE] direct_sys=unavailable no_kernel_change=1\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (i = 0; i < 2; ++i) {
|
||||||
|
raw[i] = get_count(0);
|
||||||
|
zone[i] = get_zone();
|
||||||
|
ticks[i] = ql_rtos_get_systicks();
|
||||||
|
utc[i] = (long long)raw[i] - (long long)zone[i] * 60;
|
||||||
|
printf("[APP-SYS-PROBE] direct index=%d raw=%lu zone_minutes=%d "
|
||||||
|
"utc=%lld ticks=%lu\r\n", i + 1, (unsigned long)raw[i],
|
||||||
|
zone[i], utc[i], (unsigned long)ticks[i]);
|
||||||
|
if (i == 0) ql_rtos_task_sleep_ms(3000);
|
||||||
|
}
|
||||||
|
elapsed = (ticks[1] - ticks[0]) / 200U;
|
||||||
|
advance = utc[1] - utc[0];
|
||||||
|
drift = advance - elapsed;
|
||||||
|
if (utc[0] >= 1704067200LL && utc[1] <= 2147483647LL &&
|
||||||
|
zone[0] >= -720 && zone[0] <= 840 && zone[0] == zone[1] &&
|
||||||
|
elapsed >= 2 && elapsed <= 10 && advance > 0 && drift >= -1 && drift <= 1) {
|
||||||
|
printf("[APP-SYS-PROBE] direct_sys=candidate advancing=1 "
|
||||||
|
"advance_s=%lld elapsed_s=%lld external_utc_check_required=1\r\n",
|
||||||
|
advance, elapsed);
|
||||||
|
} else {
|
||||||
|
printf("[APP-SYS-PROBE] direct_sys=rejected advance_s=%lld "
|
||||||
|
"elapsed_s=%lld drift_s=%lld\r\n", advance, elapsed, drift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void probe_app_rtc(int round)
|
||||||
|
{
|
||||||
|
ql_rtc_time_t calendar;
|
||||||
|
int rc;
|
||||||
|
printf("[APP-SYS-PROBE] app round=%d utc=%ld ticks=%lu\r\n",
|
||||||
|
round, (long)time(NULL), (unsigned long)ql_rtos_get_systicks());
|
||||||
|
if (!m_get_api_ptr("ql_rtc_get_time_ex")) {
|
||||||
|
printf("[APP-SYS-PROBE] rtc_ex=unavailable\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memset(&calendar, 0, sizeof(calendar));
|
||||||
|
rc = ql_rtc_get_time_ex(&calendar);
|
||||||
|
printf("[APP-SYS-PROBE] rtc_ex round=%d rc=%d calendar="
|
||||||
|
"%04d-%02d-%02dT%02d:%02d:%02d source_not_assumed=1\r\n",
|
||||||
|
round, rc, calendar.tm_year, calendar.tm_mon, calendar.tm_mday,
|
||||||
|
calendar.tm_hour, calendar.tm_min, calendar.tm_sec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void probe_at(int round, char *command, const char *label)
|
||||||
|
{
|
||||||
|
char response[256];
|
||||||
|
unsigned int began = ql_rtos_get_systicks(), ended;
|
||||||
|
int rc, terminated;
|
||||||
|
size_t i;
|
||||||
|
memset(response, 0, sizeof(response));
|
||||||
|
rc = ql_atcmd_send_sync(command, response, sizeof(response), NULL, 2);
|
||||||
|
ended = ql_rtos_get_systicks();
|
||||||
|
terminated = memchr(response, '\0', sizeof(response)) != NULL;
|
||||||
|
response[sizeof(response) - 1] = '\0';
|
||||||
|
for (i = 0; response[i]; ++i)
|
||||||
|
if ((unsigned char)response[i] < 32 || response[i] == '"') response[i] = ' ';
|
||||||
|
printf("[APP-SYS-PROBE] at round=%d query=%s rc=%d duration_ms=%lu "
|
||||||
|
"ticks=%lu terminated=%d raw=\"%s\"\r\n", round, label, rc,
|
||||||
|
(unsigned long)(ended - began) * 5UL, (unsigned long)ended,
|
||||||
|
terminated, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_sys_time_probe_run(void)
|
||||||
|
{
|
||||||
|
int round;
|
||||||
|
printf("[APP-SYS-PROBE] begin version=1 read_only=1 kernel_diag_used=0\r\n");
|
||||||
|
if (!m_get_api_ptr) {
|
||||||
|
printf("[APP-SYS-PROBE] FAIL stage=api_lookup_unavailable\r\n");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
probe_sys_exports();
|
||||||
|
if (!m_get_api_ptr("ql_atcmd_send_sync")) {
|
||||||
|
printf("[APP-SYS-PROBE] at=unavailable\r\n");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
for (round = 1; round <= 2; ++round) {
|
||||||
|
probe_app_rtc(round);
|
||||||
|
/* QLTS support/semantics on this model must be established on-device.
|
||||||
|
* Mode 0 may be a snapshot; mode 1/2 on supported models advances.
|
||||||
|
* CCLK is comparison only; it may expose the same broken APP RTC. */
|
||||||
|
probe_at(round, "AT+QLTS=0\r\n", "QLTS_0");
|
||||||
|
probe_at(round, "AT+QLTS=1\r\n", "QLTS_1");
|
||||||
|
probe_at(round, "AT+QLTS=2\r\n", "QLTS_2");
|
||||||
|
probe_at(round, "AT+CCLK?\r\n", "CCLK");
|
||||||
|
if (round == 1) ql_rtos_task_sleep_ms(3000);
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
printf("[APP-SYS-PROBE] finished rtc_not_written=1\r\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef APP_SYS_TIME_PROBE_H
|
||||||
|
#define APP_SYS_TIME_PROBE_H
|
||||||
|
|
||||||
|
/* Run in the existing time-test worker; read only, no new kernel dependency. */
|
||||||
|
void app_sys_time_probe_run(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -32,7 +32,8 @@ int ql_rtc_get_time(ql_rtc_time_t *);
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "APP_SOURCE"
|
#include "APP_SOURCE"
|
||||||
static int mode, creates, queries, sets;
|
static int mode, creates, queries, sets, probes;
|
||||||
|
void app_sys_time_probe_run(void) {++probes;}
|
||||||
static unsigned int ticks=100;
|
static unsigned int ticks=100;
|
||||||
static time_t app_epoch=100;
|
static time_t app_epoch=100;
|
||||||
static int zone_minutes=480;
|
static int zone_minutes=480;
|
||||||
@@ -53,7 +54,10 @@ int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *info) {
|
|||||||
if (mode==3) return 0;
|
if (mode==3) return 0;
|
||||||
if (mode==4) {memset(info->nitz_time,'X',32); 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==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==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==16 && queries==1) return 1;
|
||||||
if (mode==17) ticks+=600; /* slow query must be rejected */
|
if (mode==17) ticks+=600; /* slow query must be rejected */
|
||||||
if (mode==14) value=1793059198; /* near midnight UTC */
|
if (mode==14) value=1793059198; /* near midnight UTC */
|
||||||
@@ -63,7 +67,7 @@ int ql_nw_get_nitz_time_info(QL_NW_NITZ_TIME_INFO_T *info) {
|
|||||||
/* Compute relative elapsed correctly in wrap scenario. */
|
/* Compute relative elapsed correctly in wrap scenario. */
|
||||||
if (mode==13) value=1793019600+(unsigned int)(ticks-(UINT32_MAX-199U))/200U;
|
if (mode==13) value=1793019600+(unsigned int)(ticks-(UINT32_MAX-199U))/200U;
|
||||||
utc=gmtime(&value); assert(utc);
|
utc=gmtime(&value); assert(utc);
|
||||||
snprintf(info->nitz_time,32,"%02d/%02d/%02d %02d:%02d:%02d +32 00",
|
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);
|
utc->tm_year%100,utc->tm_mon+1,utc->tm_mday,utc->tm_hour,utc->tm_min,utc->tm_sec);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -100,6 +104,11 @@ static void check_parser(void) {
|
|||||||
assert(parse_nitz("26/10/26,13:00:00-22",&epoch,&zone)==0 &&
|
assert(parse_nitz("26/10/26,13:00:00-22",&epoch,&zone)==0 &&
|
||||||
epoch==1793019600 && zone==-22);
|
epoch==1793019600 && zone==-22);
|
||||||
assert(parse_nitz("26/10/26 13:00:00 +56 02",&epoch,&zone)==0);
|
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("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("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:07 +00 00",&epoch,&zone)==0 && epoch==2147483647);
|
||||||
@@ -110,7 +119,7 @@ static void check_parser(void) {
|
|||||||
"26/10/26 24:00:00 +00 00", "26/10/26 13:60: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: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 -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 junk",
|
||||||
"26/10/26 13:00:00 +32 00junk", "x6/10/26 13:00:00 +32 00"};
|
"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)
|
for (unsigned int i=0;i<sizeof(bad)/sizeof(bad[0]);++i)
|
||||||
assert(parse_nitz(bad[i],&epoch,&zone)!=0);
|
assert(parse_nitz(bad[i],&epoch,&zone)!=0);
|
||||||
@@ -124,10 +133,11 @@ int main(int argc,char **argv) {
|
|||||||
if (mode==13) ticks=UINT32_MAX-199U;
|
if (mode==13) ticks=UINT32_MAX-199U;
|
||||||
app_nitz_test_start(); app_nitz_test_start();
|
app_nitz_test_start(); app_nitz_test_start();
|
||||||
assert(creates==(mode==19 ? 2 : 1));
|
assert(creates==(mode==19 ? 2 : 1));
|
||||||
if ((mode>=1 && mode<=6) || mode==15 || mode==17 || mode==18 || mode==19 || mode==7)
|
if ((mode>=1 && mode<=6) || mode==15 || mode==17 || mode==18 || mode==19 || mode==7 || mode==22)
|
||||||
assert(sets==0);
|
assert(sets==0);
|
||||||
else assert(sets==1);
|
else assert(sets==1);
|
||||||
assert(queries<=6);
|
assert(queries<=6);
|
||||||
|
assert(probes==(mode==19 ? 0 : 1));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
'''.replace('APP_SOURCE', str(APP / 'app_nitz_test.c'))
|
'''.replace('APP_SOURCE', str(APP / 'app_nitz_test.c'))
|
||||||
@@ -147,10 +157,10 @@ int main(int argc,char **argv) {
|
|||||||
12: 'PASS source=nitz', 13: 'PASS source=nitz', 14: 'PASS source=nitz',
|
12: 'PASS source=nitz', 13: 'PASS source=nitz', 14: 'PASS source=nitz',
|
||||||
15: 'FAIL stage=no_fresh_nitz', 16: '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',
|
17: 'FAIL stage=no_fresh_nitz', 18: 'cached_or_inconsistent_nitz=1',
|
||||||
19: 'FAIL stage=create_task', 20: 'FAIL stage=app_clock_verification'}
|
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():
|
for mode, expected in cases.items():
|
||||||
log = subprocess.check_output([str(work / 'test'), str(mode)], text=True)
|
log = subprocess.check_output([str(work / 'test'), str(mode)], text=True)
|
||||||
assert expected in log, log
|
assert expected in log, log
|
||||||
if mode not in (0, 12, 13, 14, 16):
|
if mode not in (0, 12, 13, 14, 16, 21):
|
||||||
assert '[APP-NITZ-TEST] PASS ' not in log, log
|
assert '[APP-NITZ-TEST] PASS ' not in log, log
|
||||||
print('PASS: Gregorian/parser validation and 21 API/freshness/RTC scenarios (ASan/UBSan)')
|
print('PASS: Gregorian/parser validation and 23 API/freshness/RTC scenarios (ASan/UBSan)')
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
"""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 <stdint.h>\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 <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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')
|
||||||
Reference in New Issue
Block a user