Compare commits
5
Commits
main
...
test/time-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
884ab54255 | ||
|
|
f8df812f45 | ||
|
|
07525225cb | ||
|
|
03627a26fa | ||
|
|
c53a117368 |
@@ -0,0 +1,56 @@
|
||||
# 纯 APP:开机首次有效 NITZ 校时测试
|
||||
|
||||
当前 `test/time-fix` 在 PDP 首次激活后启动一次独立测试任务,尽早读取有效的运营商 NITZ 快照,写入 APP RTC 后读回验证。不再要求两次查询值每隔 3 秒推进;这符合实机已观察到的“最近一次授时快照”行为。
|
||||
|
||||
只改 APP,没有修改内核,也不依赖新增的 `talkingq_time_diag_snapshot_v1`。前置 SYS/AT 探测及 APP NTP 测试均已关闭,相关源文件保留但不参与当前 APP 构建。原有内核功能不因本 APP 测试而关闭。
|
||||
|
||||
## 编译与实测
|
||||
|
||||
在现有 Windows 环境执行 `build.bat app`,构建成功并核对 APP 镜像容量后,只更新 APP。保持原内核和故障现场 RTC/NVM 状态。
|
||||
|
||||
启动时应出现:
|
||||
|
||||
```text
|
||||
[TIME-DIAG] nitz_enabled=1 app_nitz_test=first_valid_after_pdp ntp_test=off sys_probe=off
|
||||
[APP-NITZ-TEST] begin version=3 source=operator_nitz ntp=off policy=first_valid_after_pdp sys_probe=off
|
||||
```
|
||||
|
||||
保留从启动到 `[APP-NITZ-TEST] finished` 的完整日志,随后按语音键触发一次实际 WSS 连接,并保留连接日志。以下是预期格式示例,不是本次实机结果:
|
||||
|
||||
```text
|
||||
[APP-NITZ-TEST] sample attempt=1 index=1 rc=0 raw="26/10/26 08:00:10 32 0" abs_time=... leap_sec=0 query_ms=5
|
||||
[APP-NITZ-TEST] parsed utc=1793001610 zone_quarters=32 zone_minutes=480
|
||||
[APP-NITZ-TEST] snapshot_selected attempt=1 utc=1793001610 age=unknown first_valid_after_pdp=1 max_rtc_writes=1
|
||||
[APP-NITZ-TEST] set_begin app_before=... expected_utc=1793001610 pmic_zone_minutes=480 app_calendar=2026-10-26T16:00:10
|
||||
[APP-NITZ-TEST] readback set_rc=0 rtc_rc=0 app_after=1793001610 expected_utc=1793001610 delta_s=0 calendar=2026-10-26T16:00:10
|
||||
[APP-NITZ-TEST] PASS source=nitz app_utc=verified wss_requires_separate_handshake_check=1
|
||||
[APP-NITZ-TEST] finished one_test_per_boot=1
|
||||
```
|
||||
|
||||
## 校时逻辑
|
||||
|
||||
1. 检查原内核是否提供既有公开 SDK 接口 `ql_nw_get_nitz_time_info`、`ql_localtime_r`、`ql_rtc_set_time`、`ql_rtc_get_time`,缺失则退出。
|
||||
2. 立即读取 NITZ。支持实机格式 `YY/MM/DD HH:MM:SS 32 0`、文档中的带符号格式和紧凑格式。严格检查终止符、日期、闰年、时区和 UTC 范围:2024 年起至 signed 32 位秒数上限。
|
||||
3. 查询失败、空值、无效格式或查询耗时超过 2 秒,不写入,间隔 5 秒重试,最多 3 次。首次有效后立即采用,不等待第二个快照,也不运行前置 AT/SYS 探测。
|
||||
4. NITZ UT 日期字段按 UTC 解析;`32` 表示 32 个 15 分钟,即 UTC+8。UTC 时间戳本身不加 8 小时。使用 SDK `ql_localtime_r` 按现有 PMIC 时区转换成 RTC 本地日历,再调用 `ql_rtc_set_time`。不直接改时区、跟随标记或 NVM 文件。
|
||||
5. 读回 RTC 日历和 `time(NULL)` 返回的 UTC 秒数,要求 SDK 返回成功、日期有效,且 UTC 和本地日历均与写入目标相差不超过 3 秒,才输出 `PASS`。
|
||||
6. 设置尝试后不再重试写入,即使读回失败;任务结束后本次启动不再校时。仅任务创建失败允许下一次 PDP 激活重试创建。
|
||||
|
||||
## 结果与边界
|
||||
|
||||
- `sdk_api_unavailable`:缺少所需公开 SDK 接口。
|
||||
- `stage=parse` / `stage=slow_query`:本次查询未采用。
|
||||
- `no_valid_nitz rtc_not_written=1`:三次均未得到可采用值。
|
||||
- `snapshot_selected`:已选择本次启动首次读到的有效快照。
|
||||
- `stage=local_calendar`:SDK 时区转换未产生可用 RTC 日历,不写入。
|
||||
- `app_clock_verification`:设置或读回验证失败。
|
||||
|
||||
本版本按用户要求验证“开机联网后尽早采用首次有效快照”。首次读取不等于已证明基站刚刚下发;接口没有提供本测试可确认的快照接收时刻,所以记录 `age=unknown`。只补偿本次查询返回后至写入的运行时间,不把设备开机时间或 PDP 激活时间冒充快照接收时间。
|
||||
|
||||
`PASS` 证明 APP 设置、时区换算和读回与所选快照一致,不独立证明快照绝对新鲜。需把原始 NITZ、APP 日历和实际北京时间对照,并继续看 WSS 实际握手。TLS 校验保持开启。本版没有增加 NITZ 更新事件处理、周期重校、WSS 启动等待或主动重连;先验证一次 APP 校时能否恢复当前故障设备。
|
||||
|
||||
## 本机验证
|
||||
|
||||
`python3 tools/test_app_nitz_test.py` 使用实际 C 源码验证解析、首次有效快照(含实机冻结快照)、失败后重试、最多一次写入、日期跨天、PMIC 时区转换、UTC 不重复偏移、tick 回绕、signed 32 位边界、设置失败及读回不符。启用 ASan/UBSan,不发送设备或公网数据。
|
||||
|
||||
同时对实际 SDK 头文件执行 ARM 目标语法检查。当前 macOS 环境不能运行仓库的 Windows 完整构建工具链,完整 APP 构建和实机验证待用户执行。SYS/AT 探测代码仍可通过 `tools/test_app_sys_time_probe.py` 单独回归,但不在本次开机流程中运行。
|
||||
@@ -0,0 +1,46 @@
|
||||
# 纯 APP 的设备侧 NTP 测试
|
||||
|
||||
当前分支已切换为自动 NITZ 测试(见 `docs/app_nitz_test.md`);下文描述前一版 NTP 测试,当前不会自动启动,也不参与 APP 构建。
|
||||
|
||||
本次测试只新增 APP 代码,不需要编译、修改或更新内核。测试使用设备自己的 PDP 1 和 SIM 网络,不使用电脑网络来判断白名单是否放行。
|
||||
|
||||
## 编译与运行
|
||||
|
||||
在现有 Windows 构建环境执行 `build.bat app`,按现有 APP OTA 流程生成、校验和更新 APP 镜像,核对分区容量。保留目标设备原内核和 RTC/NVM 状态。不要为了运行这个测试执行 `build.bat kernel` 或烧录全分区包;分支之前的内核诊断改动不是本测试的依赖。
|
||||
|
||||
每次启动后,PDP 首次成功激活会自动启动独立测试任务,不阻塞 MQTT 联网流程。每次启动只测试一次,不依赖串口命令。默认服务器为现有 GNSS 配置所用的 `ntp.aliyun.com`,端口为 UDP 123;可在编译时用 `APP_NTP_TEST_SERVER` 替换为白名单中的服务器。
|
||||
|
||||
测试会实际调用现有 SDK NTP 接口,可能校正 APP RTC。这是连通性与校时效果的测试版本,不包含正式修复所需的周期校时、退避重试和 WSS 等待策略。
|
||||
|
||||
## 判定流程
|
||||
|
||||
1. 在设备 PDP 上解析目标域名,输出解析到的 IP。`dns_ok` 不表示 NTP 可用。
|
||||
2. 向解析到的第一个 IPv4 地址发送 NTP 请求,接收窗口为 5 秒。使用连接的 UDP socket,并检查响应模式、版本、闰秒状态、层级、请求时间戳回显及时间范围。合法响应输出 `UDP_RESPONSE_VERIFIED` 和服务器 UTC 秒数。支持 2036 年 NTP era 翻转,范围限定在 2024 年至目标 signed 32 位时间上限。
|
||||
3. 检查原内核是否导出已有的 `ql_ntp_set_server`、`ql_ntp_set_cid`、`ql_ntp_sync_ex`。缺少接口时明确失败,不把动态转发接口静默返回当成成功。
|
||||
4. 通过公开 SDK 接口向同一域名发起 APP 校时,等待回调最多 25 秒。超时后不启动第二个请求;信号量保留到本次启动结束,以便迟到回调安全返回。
|
||||
5. 读取 `time(NULL)` 的返回值和 RTC 日历。要求回调成功、RTC 读取成功、APP UTC 有效,且 APP UTC 与第一次独立响应按运行计时外推后的时间相差不超过 6 秒,才输出 `PASS`。该容差涵盖首次响应最多 5 秒的传输窗口及秒级取整;不是精密授时指标。
|
||||
|
||||
网络超时只能说明这次没有取得响应,不能单凭超时确定是白名单拦截,也可能是服务器、DNS、路由或临时网络故障。该 NTP 测试不提供服务器身份认证。
|
||||
|
||||
## 日志示例
|
||||
|
||||
以下是格式示例,不是本次设备实测结果:
|
||||
|
||||
```text
|
||||
[APP-NTP-TEST] begin server=ntp.aliyun.com port=123 protocol=UDP pdp=1
|
||||
[APP-NTP-TEST] dns_ok ip=... response_not_yet_verified=1
|
||||
[APP-NTP-TEST] UDP_RESPONSE_VERIFIED ip=... epoch=... rtt_ms=...
|
||||
[APP-NTP-TEST] sdk_sync_begin app_before=...
|
||||
[APP-NTP-TEST] sdk_result callback=0 app_before=... app_after=... expected=... delta_s=0 rtc_rc=0
|
||||
[APP-NTP-TEST] PASS udp_response=verified app_utc=verified wss_still_requires_separate_handshake_check=1
|
||||
```
|
||||
|
||||
`FAIL stage=dns`、`receive_wait`、`sdk_api_unavailable`、`sdk_callback_timeout`、`app_clock_verification` 分别定位到解析、接收、内核接口兼容、SDK 回调和 APP 时间结果。
|
||||
|
||||
设备上出现 `PASS` 证明该次网络响应及 APP 校时读回通过,WSS 实际握手仍需另行核对。发布前还需验证原内核、故障现场 NVM、重启、断网恢复等场景。
|
||||
|
||||
## 本机检查与当前状态
|
||||
|
||||
`python3 tools/test_app_ntp_test.py` 编译实际测试模块,使用模拟网络和 SDK 验证响应校验、成功与失败路径、SDK 缺失、时区偏差、迟到回调及资源释放。这些模拟测试不向设备或公网发包。
|
||||
|
||||
当前 macOS 环境无法运行仓库的 Windows 完整固件构建工具链。设备串口已有程序占用,且当前 APP 没有 NTP 测试指令,因此尚未执行本测试固件的设备侧验证,不能宣称白名单已经放行。
|
||||
@@ -0,0 +1,31 @@
|
||||
# RTC / GNSS / WSS 诊断日志
|
||||
|
||||
所有新增输出以 `[TIME-DIAG]` 开头,仅采集状态,不主动设置时间、清除 NVM 或修改校时重试策略。
|
||||
|
||||
## 构建与采集
|
||||
|
||||
完整诊断需要在支持的 Windows 编译环境依次执行 `build.bat kernel`、`build.bat app`、`build.bat firmware`,并更新匹配的内核和 APP。普通 APP OTA 不代表内核已更新。保留设备原有 RTC/NVM 状态,以免丢失异常现场。
|
||||
|
||||
APP 兼容旧内核:缺少新增接口时打印 `kernel_diag=unavailable need_matching_diagnostic_kernel=1`,仍可输出 WSS 的 APP 时间,但无法取得内核偏移和跟随标志。
|
||||
|
||||
采集从上电至 WSS 首次失败、随后恢复的完整串口日志,并记录烧录包版本。启动日志包含 APP 版本、编译信息和固件版本;内核事件日志包含自身编译日期。重点比较异常设备与正常设备的下列阶段:
|
||||
|
||||
- `boot-before-nitz`:启动且尚未开启 NITZ。
|
||||
- `nitz-enabled`、`pdp-active`:开启运营商校时、数据连接成功。
|
||||
- `gnss_ntp_dns`、`gnss_ntp_trigger`、`ntp_request/result`、`ntp_rtc_written`:GNSS 发起的 NTP 路径。
|
||||
- `gnss-time-state`、`ntp-finished`:内核校时状态变化和 NTP 完成。
|
||||
- `wss-before-time-read`、`wss-precheck`、`wss-after-time-read`:WSS 读取 APP 时间前后。
|
||||
|
||||
## 字段
|
||||
|
||||
`counter` 是原始 RTC 计数,`sys_offset/app_offset` 是两套偏移;`sys_raw/app_raw` 是无符号 32 位相加后的原始秒数,不直接视为 UTC。`follows_sys` 是 APP 跟随 SYS 标志;`app_ever` 表示持久化的 APP RTC 设置标记;`storage` 是 SDK 的偏移存储枚举。
|
||||
|
||||
`net_tz_min` 单位为分钟,`rtc_tz_hour` 单位为小时;`source` 对应 SDK 的 NITZ=0、NTP=1、APP=2、UNKNOWN=3。`ntp_status` 对应原有状态 0/1/2(初始/成功/失败),日志忠实输出现有状态,不能仅凭这个标志判定实际校时成功。`ntp_type` 是 SDK 的校时类型状态,`ntp_ready` 表示任务和事件句柄已创建;`gnss_rtc` 是 GNSS 网络模块的校时状态,`cid` 为该模块记录的数据上下文。未编入的可选功能字段为 -1。
|
||||
|
||||
`time_null` 是 WSS 原来使用的 `time(NULL)` 样本,门槛仍为 `1704067200`;返回这个原始样本用于判断。`time_ptr_return/value` 用于对比 `time(&value)` 的返回值和指针写回,未写回时 value 保持哨兵值 1。`app_calendar` 来自额外的 `ql_rtc_get_time`,只在 `rtc_rc` 成功时有意义。
|
||||
|
||||
内核快照直接读计数、偏移和标记,避开可能开启 APP 跟随行为的 RTC 取时接口。WSS 额外读取发生在原来的首次 `time(NULL)` 之后。多字段采样并非原子快照,允许跨秒或并发校时造成的短暂差异,需结合连续日志判读。
|
||||
|
||||
## 本机检查
|
||||
|
||||
`python3 tools/test_time_diag.py` 使用本机 C 编译器及模拟 SDK,编译实际新增模块,验证可选接口缺失、ABI 不匹配、未初始化 RTC、SYS/APP 偏移差异、32 位溢出及 WSS 保留原始门槛样本。它不替代 Windows 目标固件编译、链接或实机复现。
|
||||
@@ -82,6 +82,8 @@ SRC_FILES+= \
|
||||
DP1312EA.c \
|
||||
mqtt.c \
|
||||
mqtt_connect_diag.c \
|
||||
time_diag.c \
|
||||
app_nitz_test.c \
|
||||
vad_det.c \
|
||||
sleep_wake.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
|
||||
|
||||
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 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.
|
||||
* 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, negative = 0;
|
||||
size_t start;
|
||||
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;
|
||||
/* Actual SDK output is also "26/10/26 08:00:10 32 0". */
|
||||
if (s[pos] == ' ') ++pos;
|
||||
else if (s[pos] != '+' && s[pos] != '-') return -3;
|
||||
if (s[pos] == '+' || s[pos] == '-') negative = s[pos++] == '-';
|
||||
start = pos;
|
||||
q = 0;
|
||||
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 (s[pos++] != ' ') return -4;
|
||||
start = pos;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
/* Bound query delay; this does not establish the age of a cached NITZ. */
|
||||
if (sample->ticks - began > 400U) {
|
||||
printf("[APP-NITZ-TEST] rejected stage=slow_query rtc_not_written=1\r\n");
|
||||
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 sample;
|
||||
int attempt;
|
||||
(void)arg;
|
||||
printf("[APP-NITZ-TEST] begin version=3 source=operator_nitz ntp=off "
|
||||
"policy=first_valid_after_pdp sys_probe=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;
|
||||
}
|
||||
/* Device experiment: use the first valid operator snapshot promptly after
|
||||
* PDP activation. A snapshot need not advance between API reads. Its actual
|
||||
* network reception time is unknown; readback alone cannot establish age. */
|
||||
for (attempt = 1; attempt <= ATTEMPTS; ++attempt) {
|
||||
if (get_sample(attempt, 1, &sample) == 0) {
|
||||
printf("[APP-NITZ-TEST] snapshot_selected attempt=%d utc=%ld "
|
||||
"age=unknown first_valid_after_pdp=1 max_rtc_writes=1\r\n",
|
||||
attempt, (long)sample.utc);
|
||||
write_and_verify(&sample);
|
||||
goto done; /* No repeated write, even on setter/readback failure. */
|
||||
}
|
||||
if (attempt < ATTEMPTS) ql_rtos_task_sleep_ms(5000);
|
||||
}
|
||||
printf("[APP-NITZ-TEST] FAIL stage=no_valid_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,229 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include "ql_rtos.h"
|
||||
#include "ql_func.h"
|
||||
#include "ql_ntp.h"
|
||||
#include "ql_rtc.h"
|
||||
#include "netdb.h"
|
||||
#include "sockets.h"
|
||||
#include "app_ntp_test.h"
|
||||
|
||||
/* Use the existing GNSS NTP provider first; do not assume it is whitelisted. */
|
||||
#ifndef APP_NTP_TEST_SERVER
|
||||
#define APP_NTP_TEST_SERVER "ntp.aliyun.com"
|
||||
#endif
|
||||
#define NTP_PACKET_SIZE 48
|
||||
#define NTP_EPOCH_DELTA 2208988800ULL
|
||||
#define NTP_MIN_UNIX 1704067200ULL
|
||||
#define NTP_MAX_UNIX 2147483647ULL
|
||||
#define NTP_WAIT_MS 5000U
|
||||
|
||||
static ql_task_t test_task;
|
||||
static ql_sem_t callback_done;
|
||||
static volatile int callback_status = -1;
|
||||
static int test_started;
|
||||
|
||||
static unsigned int read_u32(const unsigned char *p)
|
||||
{
|
||||
return ((unsigned int)p[0] << 24) | ((unsigned int)p[1] << 16) |
|
||||
((unsigned int)p[2] << 8) | p[3];
|
||||
}
|
||||
|
||||
static void write_u32(unsigned char *p, unsigned int value)
|
||||
{
|
||||
p[0] = (unsigned char)(value >> 24);
|
||||
p[1] = (unsigned char)(value >> 16);
|
||||
p[2] = (unsigned char)(value >> 8);
|
||||
p[3] = (unsigned char)value;
|
||||
}
|
||||
|
||||
/* Validate the response to our connected UDP socket, including request echo.
|
||||
* This is a reachability/clock test, not authenticated NTP. Handle era 1
|
||||
* (2036 rollover), but reject epochs outside the target's signed time range. */
|
||||
static int parse_ntp_reply(const unsigned char *reply, unsigned int length,
|
||||
const unsigned char *request, unsigned int *epoch)
|
||||
{
|
||||
unsigned int version;
|
||||
unsigned long long seconds;
|
||||
if (length < NTP_PACKET_SIZE) return -1;
|
||||
version = (reply[0] >> 3) & 7U;
|
||||
if ((reply[0] >> 6) == 3 || (version != 3 && version != 4) ||
|
||||
(reply[0] & 7U) != 4 || reply[1] == 0 || reply[1] > 15) return -2;
|
||||
if (memcmp(reply + 24, request + 40, 8) != 0) return -3;
|
||||
if (read_u32(reply + 32) == 0 && read_u32(reply + 36) == 0) return -4;
|
||||
seconds = read_u32(reply + 40);
|
||||
if (seconds == 0 && read_u32(reply + 44) == 0) return -4;
|
||||
if (seconds < NTP_EPOCH_DELTA) seconds += 0x100000000ULL;
|
||||
seconds -= NTP_EPOCH_DELTA;
|
||||
if (seconds < NTP_MIN_UNIX || seconds > NTP_MAX_UNIX) return -5;
|
||||
*epoch = (unsigned int)seconds;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int probe_ntp(unsigned int *epoch, unsigned int *sample_ticks)
|
||||
{
|
||||
struct addrinfo hints, *addresses = NULL;
|
||||
unsigned char request[NTP_PACKET_SIZE] = {0}, reply[96];
|
||||
unsigned int began, elapsed, token;
|
||||
char ip[48] = {0};
|
||||
int fd = -1, ret, received;
|
||||
fd_set readers;
|
||||
struct timeval timeout;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
printf("[APP-NTP-TEST] begin server=%s port=123 protocol=UDP pdp=1\r\n",
|
||||
APP_NTP_TEST_SERVER);
|
||||
ret = getaddrinfo_with_pcid(APP_NTP_TEST_SERVER, "123", &hints, &addresses, 1);
|
||||
if (ret != 0 || addresses == NULL) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=dns rc=%d\r\n", ret);
|
||||
if (addresses) freeaddrinfo(addresses);
|
||||
return -1;
|
||||
}
|
||||
inet_ntop(AF_INET, &((struct sockaddr_in *)addresses->ai_addr)->sin_addr,
|
||||
ip, sizeof(ip));
|
||||
printf("[APP-NTP-TEST] dns_ok ip=%s response_not_yet_verified=1\r\n", ip);
|
||||
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (fd < 0 || fd >= FD_SETSIZE) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=socket fd=%d\r\n", fd);
|
||||
goto failed;
|
||||
}
|
||||
ret = connect(fd, addresses->ai_addr, addresses->ai_addrlen);
|
||||
if (ret != 0) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=udp_connect rc=%d\r\n", ret);
|
||||
goto failed;
|
||||
}
|
||||
request[0] = (4U << 3) | 3U; /* NTP v4 client. */
|
||||
token = ql_rtos_get_systicks();
|
||||
write_u32(request + 40, 0xe0000000U ^ token);
|
||||
write_u32(request + 44, token ^ 0x6e747031U);
|
||||
began = ql_rtos_get_systicks();
|
||||
ret = send(fd, request, sizeof(request), 0);
|
||||
if (ret != (int)sizeof(request)) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=send rc=%d\r\n", ret);
|
||||
goto failed;
|
||||
}
|
||||
/* Keep one deadline even if an unrelated or invalid packet arrives. */
|
||||
while ((elapsed = (ql_rtos_get_systicks() - began) * 5U) < NTP_WAIT_MS) {
|
||||
timeout.tv_sec = (NTP_WAIT_MS - elapsed) / 1000;
|
||||
timeout.tv_usec = ((NTP_WAIT_MS - elapsed) % 1000) * 1000;
|
||||
FD_ZERO(&readers);
|
||||
FD_SET(fd, &readers);
|
||||
ret = select(fd + 1, &readers, NULL, NULL, &timeout);
|
||||
if (ret <= 0) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=receive_wait rc=%d "
|
||||
"cause=undetermined_check_whitelist_udp123_and_server\r\n", ret);
|
||||
goto failed;
|
||||
}
|
||||
received = recv(fd, reply, sizeof(reply), 0);
|
||||
if (received <= 0) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=receive rc=%d\r\n", received);
|
||||
goto failed;
|
||||
}
|
||||
ret = parse_ntp_reply(reply, (unsigned int)received, request, epoch);
|
||||
if (ret != 0) {
|
||||
printf("[APP-NTP-TEST] rejected_reply bytes=%d reason=%d\r\n", received, ret);
|
||||
continue;
|
||||
}
|
||||
*sample_ticks = ql_rtos_get_systicks();
|
||||
printf("[APP-NTP-TEST] UDP_RESPONSE_VERIFIED ip=%s epoch=%lu rtt_ms=%lu\r\n",
|
||||
ip, (unsigned long)*epoch,
|
||||
(unsigned long)(*sample_ticks - began) * 5UL);
|
||||
close(fd);
|
||||
freeaddrinfo(addresses);
|
||||
return 0;
|
||||
}
|
||||
printf("[APP-NTP-TEST] FAIL stage=valid_response_timeout\r\n");
|
||||
failed:
|
||||
if (fd >= 0) close(fd);
|
||||
freeaddrinfo(addresses);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ntp_callback(int status)
|
||||
{
|
||||
callback_status = status;
|
||||
/* This semaphore lives for the boot, including callbacks after timeout. */
|
||||
ql_rtos_semaphore_release(callback_done);
|
||||
}
|
||||
|
||||
static void ntp_test_worker(void *arg)
|
||||
{
|
||||
unsigned int reference, reference_ticks, now_ticks, expected;
|
||||
time_t before, after;
|
||||
ql_rtc_time_t calendar;
|
||||
long long delta;
|
||||
int ret, calendar_ret;
|
||||
(void)arg;
|
||||
|
||||
if (probe_ntp(&reference, &reference_ticks) != 0) goto done;
|
||||
if (!m_get_api_ptr || !m_get_api_ptr("ql_ntp_set_server") ||
|
||||
!m_get_api_ptr("ql_ntp_set_cid") || !m_get_api_ptr("ql_ntp_sync_ex")) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=sdk_api_unavailable udp_verified=1\r\n");
|
||||
goto done;
|
||||
}
|
||||
ret = ql_rtos_semaphore_create(&callback_done, 0);
|
||||
if (ret != 0) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=semaphore rc=%d\r\n", ret);
|
||||
goto done;
|
||||
}
|
||||
ret = ql_ntp_set_server(APP_NTP_TEST_SERVER);
|
||||
if (ret != QL_NTP_SUCCESS) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=sdk_server rc=%d\r\n", ret);
|
||||
goto done;
|
||||
}
|
||||
ret = ql_ntp_set_cid(1);
|
||||
if (ret != QL_NTP_SUCCESS) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=sdk_cid rc=%d\r\n", ret);
|
||||
goto done;
|
||||
}
|
||||
before = time(NULL);
|
||||
printf("[APP-NTP-TEST] sdk_sync_begin app_before=%ld\r\n", (long)before);
|
||||
ql_ntp_sync_ex(ntp_callback);
|
||||
ret = ql_rtos_semaphore_wait(callback_done, 25000);
|
||||
if (ret != 0) {
|
||||
printf("[APP-NTP-TEST] FAIL stage=sdk_callback_timeout rc=%d "
|
||||
"no_new_request_until_reboot=1\r\n", ret);
|
||||
goto done;
|
||||
}
|
||||
after = time(NULL); /* SDK time(&value) writeback is not assumed. */
|
||||
now_ticks = ql_rtos_get_systicks();
|
||||
expected = reference + (now_ticks - reference_ticks) / 200U;
|
||||
delta = (long long)after - expected;
|
||||
memset(&calendar, 0, sizeof(calendar));
|
||||
calendar_ret = ql_rtc_get_time(&calendar);
|
||||
printf("[APP-NTP-TEST] sdk_result callback=%d app_before=%ld app_after=%ld "
|
||||
"expected=%lu delta_s=%lld rtc_rc=%d\r\n",
|
||||
callback_status, (long)before, (long)after,
|
||||
(unsigned long)expected, delta, calendar_ret);
|
||||
printf("[APP-NTP-TEST] app_calendar=%04d-%02d-%02dT%02d:%02d:%02d\r\n",
|
||||
calendar.tm_year, calendar.tm_mon, calendar.tm_mday,
|
||||
calendar.tm_hour, calendar.tm_min, calendar.tm_sec);
|
||||
if (callback_status == QL_NTP_SUCCESS && calendar_ret == 0 &&
|
||||
after >= (time_t)NTP_MIN_UNIX && delta >= -6 && delta <= 6) {
|
||||
printf("[APP-NTP-TEST] PASS udp_response=verified app_utc=verified "
|
||||
"wss_still_requires_separate_handshake_check=1\r\n");
|
||||
} else {
|
||||
printf("[APP-NTP-TEST] FAIL stage=app_clock_verification\r\n");
|
||||
}
|
||||
done:
|
||||
printf("[APP-NTP-TEST] finished one_test_per_boot=1\r\n");
|
||||
ql_rtos_task_delete(NULL);
|
||||
}
|
||||
|
||||
void app_ntp_test_start(void)
|
||||
{
|
||||
int ret;
|
||||
if (test_started) return; /* Called only by the PDP management task. */
|
||||
test_started = 1;
|
||||
ret = ql_rtos_task_create(&test_task, 6 * 1024, 110, "app_ntp_test",
|
||||
ntp_test_worker, NULL);
|
||||
if (ret != 0) {
|
||||
test_started = 0;
|
||||
printf("[APP-NTP-TEST] FAIL stage=create_task rc=%d\r\n", ret);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "hal_api.h"
|
||||
#include "nfc.h"
|
||||
#include "ota_baseline.h"
|
||||
#include "time_diag.h"
|
||||
|
||||
|
||||
#define APP_ENABLE_SLEEP
|
||||
@@ -147,6 +148,7 @@ static void custom_app_task(void * argv)
|
||||
|
||||
hal_os_get_build_time(tempbuf);
|
||||
printf("BUILD TIME:%s\r\n",tempbuf);
|
||||
talkingq_time_diag_boot();
|
||||
printf("[OTA-BASELINE] layout=%s profile=%s app_phys=0x%08lX app_xip=0x%08lX app_size=%lu fs_size=%lu slot=single\r\n",
|
||||
BANBAN_OTA_LAYOUT_ID,
|
||||
BANBAN_OTA_BUILD_PROFILE,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef APP_NITZ_TEST_H
|
||||
#define APP_NITZ_TEST_H
|
||||
|
||||
void app_nitz_test_start(void);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef TALKINGQ_APP_NTP_TEST_H
|
||||
#define TALKINGQ_APP_NTP_TEST_H
|
||||
|
||||
/* Test firmware: one asynchronous test per boot, after PDP activation. */
|
||||
void app_ntp_test_start(void);
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef TALKINGQ_APP_TIME_DIAG_H
|
||||
#define TALKINGQ_APP_TIME_DIAG_H
|
||||
|
||||
#include <time.h>
|
||||
|
||||
void talkingq_time_diag_boot(void);
|
||||
/* Raw kernel snapshot only: safe to call before the application's first RTC read. */
|
||||
void talkingq_time_diag_log(const char *phase);
|
||||
/* Returns the original time(NULL) sample for the existing WSS threshold check. */
|
||||
time_t talkingq_time_diag_wss_time(void);
|
||||
|
||||
#endif
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "sockets.h"
|
||||
#include "ql_rtos.h"
|
||||
#include <time.h>
|
||||
#include "time_diag.h"
|
||||
#include "rws_handshake.h"
|
||||
#ifdef LIBRWS_USING_MBED_TLS
|
||||
#include "mbedtls/base64.h"
|
||||
@@ -1945,7 +1946,8 @@ int rws_ssl_conn(_rws_socket *s)
|
||||
LOG_E("WSS requires a trusted root CA\n");
|
||||
goto exit;
|
||||
}
|
||||
if (time(NULL) < 1704067200) {
|
||||
stage = "check RTC time";
|
||||
if (talkingq_time_diag_wss_time() < 1704067200) {
|
||||
LOG_E("WSS requires valid network/RTC time before certificate verification\n");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "ql_atcmd.h"
|
||||
#include "http_downfile.h"
|
||||
#include "ota_post_update.h"
|
||||
#include "time_diag.h"
|
||||
#include "app_nitz_test.h"
|
||||
|
||||
//#define NTP_SERVER_DOMAIN "ntp.aliyun.com"
|
||||
|
||||
@@ -141,6 +143,8 @@ void ql_datacall_demo_thread(void *arvg)
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
talkingq_time_diag_log("pdp-active");
|
||||
app_nitz_test_start();
|
||||
ql_get_data_call_info(1, 0, &info);
|
||||
printf("info.profile_idx: %d\r\n", info.profile_idx);
|
||||
printf("info.ip_version: %d\r\n", info.ip_version);
|
||||
@@ -197,6 +201,8 @@ int app_net_mgt_init()
|
||||
// ql_rtos_semaphore_create(&g_ql_ntp_sem,0);
|
||||
|
||||
ql_rtc_set_nitz_mode(1);
|
||||
printf("[TIME-DIAG] nitz_enabled=1 app_nitz_test=first_valid_after_pdp ntp_test=off sys_probe=off\r\n");
|
||||
talkingq_time_diag_log("nitz-enabled");
|
||||
|
||||
ql_task_t appTaskRef = NULL;
|
||||
ql_rtos_task_create(&appTaskRef, 1024*4, 100, "app_net_mgt", ql_datacall_demo_thread, NULL);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "ql_func.h"
|
||||
#include "ql_rtc.h"
|
||||
#include "ql_rtos.h"
|
||||
#include "ql_dev.h"
|
||||
#include "build_macro.h"
|
||||
#include "time_diag.h"
|
||||
#include "../../../../ql-config/config/talkingq_time_diag.h"
|
||||
|
||||
void talkingq_time_diag_log(const char *phase)
|
||||
{
|
||||
talkingq_time_diag_snapshot_fn snapshot;
|
||||
talkingq_time_diag_snapshot s;
|
||||
int ret;
|
||||
|
||||
snapshot = m_get_api_ptr ? (talkingq_time_diag_snapshot_fn)
|
||||
m_get_api_ptr(TALKINGQ_TIME_DIAG_API_NAME) : NULL;
|
||||
if (!snapshot) {
|
||||
printf("[TIME-DIAG] phase=%s ticks=%lu kernel_diag=unavailable "
|
||||
"need_matching_diagnostic_kernel=1\r\n", phase,
|
||||
(unsigned long)ql_rtos_get_systicks());
|
||||
return;
|
||||
}
|
||||
memset(&s, 0, sizeof(s));
|
||||
ret = snapshot(&s, sizeof(s));
|
||||
if (ret != 0 || s.abi_version != TALKINGQ_TIME_DIAG_ABI_VERSION) {
|
||||
printf("[TIME-DIAG] phase=%s kernel_diag=abi_mismatch rc=%d abi=%u\r\n",
|
||||
phase, ret, s.abi_version);
|
||||
return;
|
||||
}
|
||||
printf("[TIME-DIAG] phase=%s ticks=%u abi=%u rtc_init=%u "
|
||||
"counter=%lu sys_offset=%lu app_offset=%lu "
|
||||
"sys_raw=%lu app_raw=%lu follows_sys=%u app_ever=%u storage=%d\r\n",
|
||||
phase, s.sample_ticks, s.abi_version, s.rtc_initialized,
|
||||
(unsigned long)s.counter, (unsigned long)s.sys_offset,
|
||||
(unsigned long)s.app_offset,
|
||||
(unsigned long)(s.counter + s.sys_offset),
|
||||
(unsigned long)(s.counter + s.app_offset),
|
||||
s.app_follows_sys, s.app_ever_configured, s.offset_storage);
|
||||
printf("[TIME-DIAG] phase=%s ticks=%u net_tz_min=%d rtc_tz_hour=%d source=%d "
|
||||
"ntp_type=%d ntp_status=%d ntp_ready=%d gnss_rtc=%d cid=%d "
|
||||
"triggers=%d dns_attempted=%d ntp_ip=%.31s\r\n",
|
||||
phase, s.sample_ticks, s.network_tz_minutes, s.rtc_tz_hours, s.rtc_source,
|
||||
s.ntp_type, s.ntp_status, s.ntp_ready, s.gnss_rtc_status, s.gnss_cid,
|
||||
s.ntp_triggers, s.ntp_dns_attempted, s.ntp_server);
|
||||
}
|
||||
|
||||
void talkingq_time_diag_boot(void)
|
||||
{
|
||||
char firmware[128] = {0};
|
||||
int ret = ql_dev_get_firmware_version(firmware, sizeof(firmware));
|
||||
firmware[sizeof(firmware) - 1] = '\0';
|
||||
printf("[TIME-DIAG] boot app_ver=%s app_build=%s app_rev=%s "
|
||||
"diag_build=%s %s firmware_rc=%d firmware=%s\r\n",
|
||||
CUSTOM_APP_BUILD_VER, CUSTOM_APP_BUILD_TIME, CUSTOM_APP_BUILD_REV,
|
||||
__DATE__, __TIME__, ret, firmware);
|
||||
talkingq_time_diag_log("boot-before-nitz");
|
||||
}
|
||||
|
||||
time_t talkingq_time_diag_wss_time(void)
|
||||
{
|
||||
time_t original;
|
||||
time_t with_pointer;
|
||||
time_t pointer_value = 1;
|
||||
ql_rtc_time_t rtc;
|
||||
int rtc_ret;
|
||||
|
||||
/* Capture raw flags first. Do not replace or correct the existing gate. */
|
||||
talkingq_time_diag_log("wss-before-time-read");
|
||||
original = time(NULL);
|
||||
with_pointer = time(&pointer_value);
|
||||
memset(&rtc, 0, sizeof(rtc));
|
||||
rtc_ret = ql_rtc_get_time(&rtc);
|
||||
printf("[TIME-DIAG] phase=wss-precheck ticks=%lu time_null=%ld "
|
||||
"time_ptr_return=%ld time_ptr_value=%ld rtc_rc=%d "
|
||||
"app_calendar=%04d-%02d-%02dT%02d:%02d:%02d min_epoch=1704067200 valid=%d\r\n",
|
||||
(unsigned long)ql_rtos_get_systicks(), (long)original,
|
||||
(long)with_pointer, (long)pointer_value, rtc_ret,
|
||||
rtc.tm_year, rtc.tm_mon, rtc.tm_mday, rtc.tm_hour, rtc.tm_min, rtc.tm_sec,
|
||||
original >= 1704067200);
|
||||
talkingq_time_diag_log("wss-after-time-read");
|
||||
return original;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef TALKINGQ_TIME_DIAG_ABI_H
|
||||
#define TALKINGQ_TIME_DIAG_ABI_H
|
||||
|
||||
/* Shared by the ARMCC kernel and GCC application. No pointers or enums in ABI. */
|
||||
#define TALKINGQ_TIME_DIAG_ABI_VERSION 1U
|
||||
#define TALKINGQ_TIME_DIAG_API_NAME "talkingq_time_diag_snapshot_v1"
|
||||
#define TALKINGQ_TIME_DIAG_API_HASH 0x11B09482U
|
||||
|
||||
typedef struct {
|
||||
unsigned int abi_version;
|
||||
unsigned int sample_ticks;
|
||||
unsigned int rtc_initialized;
|
||||
unsigned int counter;
|
||||
unsigned int sys_offset;
|
||||
unsigned int app_offset;
|
||||
unsigned int app_follows_sys;
|
||||
unsigned int app_ever_configured;
|
||||
int offset_storage;
|
||||
int network_tz_minutes;
|
||||
int rtc_tz_hours;
|
||||
int rtc_source;
|
||||
int ntp_type;
|
||||
int ntp_status;
|
||||
int ntp_ready;
|
||||
int gnss_rtc_status;
|
||||
int gnss_cid;
|
||||
int ntp_triggers;
|
||||
int ntp_dns_attempted;
|
||||
char ntp_server[32];
|
||||
} talkingq_time_diag_snapshot;
|
||||
|
||||
typedef int (*talkingq_time_diag_snapshot_fn)(talkingq_time_diag_snapshot *, unsigned int);
|
||||
|
||||
int talkingq_time_diag_snapshot_v1(talkingq_time_diag_snapshot *out, unsigned int size);
|
||||
void talkingq_time_diag_trace(const char *phase);
|
||||
|
||||
#endif
|
||||
@@ -12,6 +12,7 @@ MAKEFILE_PATH :=$(subst $(strip \),/,$(abspath $(lastword $(MAKEFILE_LIST))))
|
||||
SRC_FILES+= \
|
||||
quec_kernel_shim_api.c \
|
||||
ql_user_api_map.c \
|
||||
talkingq_time_diag.c \
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
@@ -30,6 +30,7 @@ WHEN WHO WHAT, WHERE, WHY
|
||||
#include "ql_keypad.h"
|
||||
#include "ql_rtc.h"
|
||||
#include "ql_time.h"
|
||||
#include "../../../ql-config/config/talkingq_time_diag.h"
|
||||
#include "ql_power.h"
|
||||
#include "ql_nw.h"
|
||||
#include "ql_platform.h"
|
||||
@@ -103,6 +104,7 @@ ql_open_api_list vUserAPITable[] __attribute__((used, section("DDR_USER_API_TABL
|
||||
{
|
||||
{0x60BCEA5A, (void*)ql_user_api_map_test},
|
||||
{0xFFFFFFFF, (void*)quec_kernel_api_test},
|
||||
{TALKINGQ_TIME_DIAG_API_HASH, (void *)talkingq_time_diag_snapshot_v1},
|
||||
|
||||
#ifdef QL_MBEDTLS
|
||||
/************************************************************************************************
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "ql_type.h"
|
||||
#include "quec_cust_feature.h"
|
||||
#include "pmic.h"
|
||||
#include "pmic_rtc.h"
|
||||
#include "quec_rtc.h"
|
||||
#include "ql_rtos.h"
|
||||
#include "../../../ql-config/config/talkingq_time_diag.h"
|
||||
|
||||
/* SDK globals are linked by name, never by firmware-specific RAM addresses. */
|
||||
extern unsigned char m_rtc_app_sys_keeping_synced;
|
||||
extern PMIC_RTC_OFFSET_STORAGE m_pmic_rtc_offset_storage_type;
|
||||
#ifdef NTP
|
||||
extern int get_ntp_time_type(void);
|
||||
extern int GetNTPStatus(void);
|
||||
extern int talkingq_ntp_diag_ready(void);
|
||||
#endif
|
||||
#ifdef QUEC_GNSS_NTP
|
||||
extern int quec_get_rtc_status(void);
|
||||
extern int quec_get_nw_status(void);
|
||||
extern char *get_ntp_server_ip(void);
|
||||
extern char ntp_sync_cnt;
|
||||
extern char ntp_dns_gain;
|
||||
#endif
|
||||
|
||||
int talkingq_time_diag_snapshot_v1(talkingq_time_diag_snapshot *out, unsigned int size)
|
||||
{
|
||||
if (out == NULL || size != sizeof(*out)) return -1;
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->abi_version = TALKINGQ_TIME_DIAG_ABI_VERSION;
|
||||
out->sample_ticks = ql_rtos_get_systicks();
|
||||
out->ntp_type = out->ntp_status = out->ntp_ready = -1;
|
||||
out->gnss_rtc_status = out->gnss_cid = -1;
|
||||
out->ntp_triggers = out->ntp_dns_attempted = -1;
|
||||
out->rtc_initialized = pmic_rtc_INIT_FLAG_GET() ? 1U : 0U;
|
||||
if (!out->rtc_initialized) return 0;
|
||||
|
||||
/* Read raw RTC storage, not PMIC_RTC_GetTime_Count(): that getter can
|
||||
* enable APP-follow-SYS and would disturb the boot state under diagnosis. */
|
||||
out->app_follows_sys = m_rtc_app_sys_keeping_synced;
|
||||
out->app_ever_configured = pmic_userdata_get_app_rtc_ever_configed() ? 1U : 0U;
|
||||
out->offset_storage = (int)m_pmic_rtc_offset_storage_type;
|
||||
out->sys_offset = PM812_RTC_GET_32B_SYS_OFFSET();
|
||||
out->app_offset = PM812_RTC_GET_32B_APP_OFFSET();
|
||||
out->counter = PM812_RTC_GET_32B_COUNTER();
|
||||
out->network_tz_minutes = GetTimeZone();
|
||||
out->rtc_tz_hours = PMIC_RTC_GetTimezone();
|
||||
out->rtc_source = (int)quec_get_rtc_source();
|
||||
#ifdef NTP
|
||||
out->ntp_type = get_ntp_time_type();
|
||||
out->ntp_status = GetNTPStatus();
|
||||
out->ntp_ready = talkingq_ntp_diag_ready();
|
||||
#endif
|
||||
#ifdef QUEC_GNSS_NTP
|
||||
out->gnss_rtc_status = quec_get_rtc_status();
|
||||
out->gnss_cid = quec_get_nw_status();
|
||||
out->ntp_triggers = (unsigned char)ntp_sync_cnt;
|
||||
out->ntp_dns_attempted = (unsigned char)ntp_dns_gain;
|
||||
snprintf(out->ntp_server, sizeof(out->ntp_server), "%.31s", get_ntp_server_ip());
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void talkingq_time_diag_trace(const char *phase)
|
||||
{
|
||||
talkingq_time_diag_snapshot s;
|
||||
if (talkingq_time_diag_snapshot_v1(&s, sizeof(s)) != 0) return;
|
||||
printf("[TIME-DIAG] kernel phase=%s build=%s %s rtc_init=%u "
|
||||
"counter=%lu sys_offset=%lu app_offset=%lu follows_sys=%u app_ever=%u "
|
||||
"storage=%d\r\n",
|
||||
phase, __DATE__, __TIME__, s.rtc_initialized,
|
||||
(unsigned long)s.counter, (unsigned long)s.sys_offset,
|
||||
(unsigned long)s.app_offset, s.app_follows_sys, s.app_ever_configured,
|
||||
s.offset_storage);
|
||||
printf("[TIME-DIAG] kernel phase=%s ticks=%u net_tz_min=%d rtc_tz_hour=%d source=%d "
|
||||
"ntp_type=%d ntp_status=%d ntp_ready=%d gnss_rtc=%d triggers=%d\r\n",
|
||||
phase, s.sample_ticks, s.network_tz_minutes, s.rtc_tz_hours, s.rtc_source,
|
||||
s.ntp_type, s.ntp_status, s.ntp_ready, s.gnss_rtc_status, s.ntp_triggers);
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "quec_sockets.h"
|
||||
#include "gps_app.h"
|
||||
#include "quec_mfpr_api.h"
|
||||
#include "../../../ql-config/config/talkingq_time_diag.h"
|
||||
|
||||
#define GPS_VIA_UART
|
||||
|
||||
@@ -415,6 +416,8 @@ void quec_gps_nw_status_monitor(void * argv)
|
||||
{
|
||||
int cid_num = 0;
|
||||
struct sockaddr sockaddr = {0};
|
||||
int last_type = -1, last_status = -1, last_rtc = -1;
|
||||
int current_type, current_status, dns_ret;
|
||||
while(1)
|
||||
{
|
||||
cid_num = quec_gps_get_cid_act();
|
||||
@@ -429,12 +432,17 @@ void quec_gps_nw_status_monitor(void * argv)
|
||||
memcpy(&gps_nw_ctx_ptr.gps_ip4_addr, &(((struct sockaddr_in *)&sockaddr)->sin_addr), sizeof(struct in_addr));
|
||||
quec_rtos_task_sleep_s(2);
|
||||
if(!ntp_dns_gain){
|
||||
quec_gps_ntp_set_server(quec_gps_cfg->ntp_url);
|
||||
dns_ret = quec_gps_ntp_set_server(quec_gps_cfg->ntp_url);
|
||||
printf("[TIME-DIAG] gnss_ntp_dns cid=%d server=%.31s rc=%d ip=%.31s\r\n",
|
||||
cid_num, quec_gps_cfg->ntp_url, dns_ret, get_ntp_server_ip());
|
||||
ntp_dns_gain++;
|
||||
}
|
||||
/*限制成功激活pdp后ntp的同步次数*/
|
||||
if((gps_nw_ctx_ptr.rtc_status !=1)&&(ntp_sync_cnt < 10))
|
||||
{
|
||||
printf("[TIME-DIAG] gnss_ntp_trigger cid=%d attempt=%u ntp_type=%d ntp_status=%d\r\n",
|
||||
cid_num, (unsigned int)(unsigned char)ntp_sync_cnt + 1U,
|
||||
get_ntp_time_type(), GetNTPStatus());
|
||||
get_ntp_time_internal();
|
||||
ntp_sync_cnt++;
|
||||
GPS_LOGD("Ntp sync cnt:%d!!!",ntp_sync_cnt);
|
||||
@@ -453,6 +461,15 @@ void quec_gps_nw_status_monitor(void * argv)
|
||||
{
|
||||
gps_nw_ctx_ptr.rtc_status = 1;
|
||||
}
|
||||
current_type = get_ntp_time_type();
|
||||
current_status = GetNTPStatus();
|
||||
if (current_type != last_type || current_status != last_status ||
|
||||
gps_nw_ctx_ptr.rtc_status != last_rtc) {
|
||||
talkingq_time_diag_trace("gnss-time-state");
|
||||
last_type = current_type;
|
||||
last_status = current_status;
|
||||
last_rtc = gps_nw_ctx_ptr.rtc_status;
|
||||
}
|
||||
//RTI_LOG("Found an active pdp %d:%d:%d:%d:%d:%d!!!",tm.tm_year,tm.tm_mon,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
|
||||
}
|
||||
}
|
||||
@@ -472,6 +489,7 @@ int quec_get_nw_status(void)
|
||||
int gps_nw_active(void)
|
||||
{
|
||||
int cid_num = 0;
|
||||
int dns_ret;
|
||||
cid_num = quec_gps_get_cid_act();
|
||||
struct sockaddr sockaddr = {0};
|
||||
if(cid_num>0)
|
||||
@@ -483,7 +501,9 @@ int gps_nw_active(void)
|
||||
quec_soc_get_ipv4v6_local_addr(cid_num, AF_INET, &sockaddr);
|
||||
memcpy(&gps_nw_ctx_ptr.gps_ip4_addr, &(((struct sockaddr_in *)&sockaddr)->sin_addr), sizeof(struct in_addr));
|
||||
if(!ntp_dns_gain){
|
||||
quec_gps_ntp_set_server(quec_gps_cfg->ntp_url);
|
||||
dns_ret = quec_gps_ntp_set_server(quec_gps_cfg->ntp_url);
|
||||
printf("[TIME-DIAG] gnss_ntp_dns cid=%d server=%.31s rc=%d ip=%.31s\r\n",
|
||||
cid_num, quec_gps_cfg->ntp_url, dns_ret, get_ntp_server_ip());
|
||||
ntp_dns_gain++;
|
||||
}
|
||||
GPS_LOGI("Found an active pdp!!!");
|
||||
|
||||
@@ -77,6 +77,7 @@ static const char rcsid[] = "$OpenBSD: rdate.c,v 1.22 2004/02/18 20:10:53 jmc Ex
|
||||
#endif
|
||||
#include "utltime.h"
|
||||
#include "quec_cust_feature.h"
|
||||
#include "../../../ql-config/config/talkingq_time_diag.h"
|
||||
/* there are systems without libutil; for portability */
|
||||
#ifdef HAVE_UTIL_H
|
||||
#include <util.h>
|
||||
@@ -204,6 +205,11 @@ int GetNTPStatus(void)
|
||||
return NTP_SYNC_SUCCESS;
|
||||
}
|
||||
|
||||
int talkingq_ntp_diag_ready(void)
|
||||
{
|
||||
return NtpFlagRef != NULL && NTPTaskRef != NULL;
|
||||
}
|
||||
|
||||
static unsigned int ntp_retry_timer[] = {
|
||||
(1 * 1000 / 5), /*2 sec*/
|
||||
(10 * 1000 / 5), /*10 sec*/
|
||||
@@ -278,11 +284,14 @@ extern char *get_ntp_server_ip(void);
|
||||
while(try_times > 0 && count < 8)
|
||||
{
|
||||
--try_times;
|
||||
printf("[TIME-DIAG] ntp_request attempt=%d server=%.31s\r\n",
|
||||
3 - try_times, strlen(hname) > 0 ? hname : hname_default);
|
||||
if(strlen(hname) > 0)
|
||||
ret = ntp_client(hname, family, &new, &adjust, corrleaps);
|
||||
else
|
||||
ret = ntp_client(hname_default, family, &new, &adjust, corrleaps);
|
||||
|
||||
printf("[TIME-DIAG] ntp_result attempt=%d rc=%d\r\n", 3 - try_times, ret);
|
||||
if(ret != 0)
|
||||
{
|
||||
continue;
|
||||
@@ -297,6 +306,7 @@ extern char *get_ntp_server_ip(void);
|
||||
if (new.tv_sec > 60 * 60 * 24 * 365)
|
||||
{
|
||||
SaveNTPParameters(ltm);
|
||||
printf("[TIME-DIAG] ntp_rtc_written epoch=%ld\r\n", (long)tim);
|
||||
break;
|
||||
}
|
||||
OSATaskSleep(200);
|
||||
@@ -311,6 +321,7 @@ extern char *get_ntp_server_ip(void);
|
||||
|
||||
SetNTPStatus(2);//failed
|
||||
}
|
||||
talkingq_time_diag_trace("ntp-finished");
|
||||
|
||||
}
|
||||
|
||||
@@ -325,12 +336,14 @@ void NTP_Init_Internal(void)
|
||||
str = psm_get_wrapper("Debug", NULL, "debug_ntp_disable");
|
||||
if (str && strcmp(str, "1") == 0)
|
||||
{
|
||||
printf("[TIME-DIAG] ntp_init skipped debug_ntp_disable=1\r\n");
|
||||
free(str);
|
||||
return;
|
||||
}
|
||||
if (str)
|
||||
free(str);
|
||||
|
||||
printf("[TIME-DIAG] ntp_init debug_ntp_disable=0\r\n");
|
||||
/* Ntp Flag initialize. */
|
||||
status = OSAFlagCreate(&NtpFlagRef);
|
||||
ASSERT(status == OS_SUCCESS);
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
"""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, sleeps;
|
||||
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) {assert(ms==5000); ++sleeps; 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==23 && queries<3) 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; /* PMIC +8 local calendar exceeds signed32 range */
|
||||
if (mode==18) value=1704067200; /* Old but valid snapshot: age cannot be proven. */
|
||||
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;
|
||||
if (mode==25) local+=28800; /* Invalid PMIC conversion (+16 hours total). */
|
||||
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<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==24) zone_minutes=0;
|
||||
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<=5) || mode==7 || mode==15 || mode==17 || mode==19 || mode==25)
|
||||
assert(sets==0);
|
||||
else assert(sets==1);
|
||||
if (mode==1 || mode==19) {assert(queries==0 && sleeps==0);}
|
||||
else if ((mode>=2 && mode<=5) || mode==17 || mode==23) {assert(queries==3 && sleeps==2);}
|
||||
else if (mode==16) {assert(queries==2 && sleeps==1);}
|
||||
else {assert(queries==1 && sleeps==0);}
|
||||
if (mode==22) {
|
||||
assert(saved.tm_year==2026 && saved.tm_mon==10 && saved.tm_mday==26);
|
||||
assert(saved.tm_hour==16 && saved.tm_min==0 && saved.tm_sec==10);
|
||||
assert(app_epoch==1793001610); /* Local RTC +8, UTC remains unshifted. */
|
||||
}
|
||||
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_valid_nitz', 3: 'FAIL stage=no_valid_nitz',
|
||||
4: 'FAIL stage=no_valid_nitz', 5: 'FAIL stage=no_valid_nitz',
|
||||
6: 'PASS source=nitz', 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=local_calendar', 16: 'PASS source=nitz',
|
||||
17: 'FAIL stage=no_valid_nitz', 18: 'PASS source=nitz',
|
||||
19: 'FAIL stage=create_task', 20: 'FAIL stage=app_clock_verification',
|
||||
21: 'PASS source=nitz', 22: 'PASS source=nitz', 23: 'PASS source=nitz',
|
||||
24: 'PASS source=nitz', 25: 'FAIL stage=local_calendar'}
|
||||
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]' not in log, log
|
||||
assert 'stage=freshness' not in log, log
|
||||
if expected.startswith('PASS'):
|
||||
assert 'age=unknown first_valid_after_pdp=1 max_rtc_writes=1' in log, log
|
||||
else:
|
||||
assert '[APP-NITZ-TEST] PASS ' not in log, log
|
||||
print('PASS: parser validation and 26 first-snapshot/retry/timezone/RTC scenarios (ASan/UBSan)')
|
||||
@@ -0,0 +1,216 @@
|
||||
"""Compile the actual APP NTP tester with scripted network/SDK responses.
|
||||
|
||||
These are host regression tests, not evidence of device whitelist access.
|
||||
"""
|
||||
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-ntp-test-') as directory:
|
||||
work = Path(directory)
|
||||
headers = {
|
||||
'ql_rtos.h': '''#include <stddef.h>
|
||||
typedef void *ql_task_t;
|
||||
typedef void *ql_sem_t;
|
||||
unsigned int ql_rtos_get_systicks(void);
|
||||
int ql_rtos_task_create(ql_task_t *, unsigned int, unsigned char, char *, void (*)(void *), void *, ...);
|
||||
int ql_rtos_task_delete(ql_task_t);
|
||||
int ql_rtos_semaphore_create(ql_sem_t *, unsigned int);
|
||||
int ql_rtos_semaphore_wait(ql_sem_t, unsigned int);
|
||||
int ql_rtos_semaphore_release(ql_sem_t);
|
||||
''',
|
||||
'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(ql_rtc_time_t *);
|
||||
''',
|
||||
'ql_ntp.h': '''enum {QL_NTP_SUCCESS=0};
|
||||
int ql_ntp_set_server(char *);
|
||||
int ql_ntp_set_cid(int);
|
||||
void ql_ntp_sync_ex(void (*)(int));
|
||||
''',
|
||||
'netdb.h': '''#include <sys/socket.h>
|
||||
struct addrinfo {
|
||||
int ai_flags,ai_family,ai_socktype,ai_protocol;
|
||||
socklen_t ai_addrlen;
|
||||
struct sockaddr *ai_addr;
|
||||
struct addrinfo *ai_next;
|
||||
};
|
||||
int getaddrinfo_with_pcid(const char *,const char *,const struct addrinfo *,struct addrinfo **,unsigned char);
|
||||
#define freeaddrinfo fake_freeaddrinfo
|
||||
void fake_freeaddrinfo(struct addrinfo *);
|
||||
''',
|
||||
'sockets.h': '''#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <arpa/inet.h>
|
||||
#define socket fake_socket
|
||||
#define connect fake_connect
|
||||
#define send fake_send
|
||||
#define recv fake_recv
|
||||
#define close fake_close
|
||||
#define select fake_select
|
||||
int fake_socket(int,int,int);
|
||||
int fake_connect(int,const struct sockaddr *,socklen_t);
|
||||
int fake_send(int,const void *,size_t,int);
|
||||
int fake_recv(int,void *,size_t,int);
|
||||
int fake_close(int);
|
||||
int fake_select(int,fd_set *,fd_set *,fd_set *,struct timeval *);
|
||||
''',
|
||||
}
|
||||
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, sends, closes, frees, syncs, signals;
|
||||
static unsigned int ticks=100;
|
||||
static unsigned char sent[48];
|
||||
static time_t app_epoch=100;
|
||||
static void (*saved_callback)(int);
|
||||
static struct sockaddr_in address;
|
||||
static struct addrinfo result;
|
||||
unsigned int ql_rtos_get_systicks(void) { return ticks; }
|
||||
int getaddrinfo_with_pcid(const char *name,const char *port,
|
||||
const struct addrinfo *hints,struct addrinfo **out,unsigned char cid) {
|
||||
assert(strcmp(name,"ntp.aliyun.com")==0 && strcmp(port,"123")==0 && cid==1);
|
||||
assert(hints->ai_family==AF_INET && hints->ai_socktype==SOCK_DGRAM);
|
||||
if (mode==1) return -9;
|
||||
address.sin_family=AF_INET; address.sin_port=htons(123);
|
||||
address.sin_addr.s_addr=htonl(0xc0000201U);
|
||||
result.ai_addr=(struct sockaddr *)&address; result.ai_addrlen=sizeof(address);
|
||||
*out=&result; return 0;
|
||||
}
|
||||
void fake_freeaddrinfo(struct addrinfo *p) {assert(p==&result); ++frees;}
|
||||
int fake_socket(int domain,int type,int protocol) {
|
||||
assert(domain==AF_INET && type==SOCK_DGRAM && protocol==IPPROTO_UDP);
|
||||
return mode==10 ? -1 : 7;
|
||||
}
|
||||
int fake_connect(int fd,const struct sockaddr *p,socklen_t size) {
|
||||
assert(fd==7 && p==(struct sockaddr *)&address && size==sizeof(address));
|
||||
return mode==11 ? -1 : 0;
|
||||
}
|
||||
int fake_send(int fd,const void *data,size_t size,int flags) {
|
||||
assert(fd==7 && size==48 && flags==0); ++sends;
|
||||
memcpy(sent,data,48); assert(sent[0]==0x23);
|
||||
return mode==12 ? -1 : 48;
|
||||
}
|
||||
int fake_select(int nfds,fd_set *readers,fd_set *writers,fd_set *errors,struct timeval *timeout) {
|
||||
assert(nfds==8 && FD_ISSET(7,readers) && writers==NULL && errors==NULL);
|
||||
assert(timeout->tv_sec<=5); ticks+=20;
|
||||
return mode==2 ? 0 : 1;
|
||||
}
|
||||
static void valid_reply(unsigned char *p,const unsigned char *request) {
|
||||
memset(p,0,48); p[0]=0x24; p[1]=2;
|
||||
memcpy(p+24,request+40,8);
|
||||
write_u32(p+32,4008988800U); write_u32(p+40,4008988800U);
|
||||
}
|
||||
int fake_recv(int fd,void *buffer,size_t size,int flags) {
|
||||
assert(fd==7 && size>=48 && flags==0);
|
||||
valid_reply(buffer,sent); return 48;
|
||||
}
|
||||
int fake_close(int fd) {assert(fd==7); ++closes; return 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_ntp_test")==0);
|
||||
++creates; *task=(void *)1; entry(arg); return 0;
|
||||
}
|
||||
int ql_rtos_task_delete(ql_task_t task) {assert(task==NULL); return 0;}
|
||||
int ql_rtos_semaphore_create(ql_sem_t *sem,unsigned int count) {
|
||||
assert(count==0);
|
||||
if (mode==13) return -1;
|
||||
*sem=(void *)2; return 0;
|
||||
}
|
||||
int ql_rtos_semaphore_wait(ql_sem_t sem,unsigned int timeout) {
|
||||
assert(sem==(void *)2 && timeout==25000);
|
||||
ticks+=100;
|
||||
if (mode==5) return -1;
|
||||
assert(signals==1); return 0;
|
||||
}
|
||||
int ql_rtos_semaphore_release(ql_sem_t sem) {assert(sem==(void *)2); ++signals; return 0;}
|
||||
static intptr_t lookup(char *name) {
|
||||
assert(strncmp(name,"ql_ntp_",7)==0);
|
||||
return mode==6 ? 0 : 1;
|
||||
}
|
||||
intptr_t (*m_get_api_ptr)(char *)=lookup;
|
||||
int ql_ntp_set_server(char *name) {assert(strcmp(name,"ntp.aliyun.com")==0); return mode==14 ? 1 : 0;}
|
||||
int ql_ntp_set_cid(int cid) {assert(cid==1); return mode==15 ? 1 : 0;}
|
||||
void ql_ntp_sync_ex(void (*callback)(int)) {
|
||||
++syncs; saved_callback=callback;
|
||||
if (mode==5) return;
|
||||
if (mode!=3) app_epoch=1800000000;
|
||||
if (mode==7) app_epoch+=28800;
|
||||
callback(mode==4 ? 1 : 0);
|
||||
}
|
||||
time_t time(time_t *out) {(void)out; return app_epoch;}
|
||||
int ql_rtc_get_time(ql_rtc_time_t *calendar) {
|
||||
calendar->tm_year=2027; return mode==8 ? -1 : 0;
|
||||
}
|
||||
static void check_parser(void) {
|
||||
unsigned char request[48]={0},reply[48]; unsigned int epoch=0;
|
||||
request[40]=9; valid_reply(reply,request);
|
||||
assert(parse_ntp_reply(reply,48,request,&epoch)==0 && epoch==1800000000);
|
||||
assert(parse_ntp_reply(reply,47,request,&epoch)==-1);
|
||||
reply[0]|=0xc0; assert(parse_ntp_reply(reply,48,request,&epoch)==-2);
|
||||
valid_reply(reply,request); reply[1]=0;
|
||||
assert(parse_ntp_reply(reply,48,request,&epoch)==-2);
|
||||
reply[1]=16; assert(parse_ntp_reply(reply,48,request,&epoch)==-2);
|
||||
valid_reply(reply,request); reply[0]=0x23;
|
||||
assert(parse_ntp_reply(reply,48,request,&epoch)==-2);
|
||||
valid_reply(reply,request); reply[24]^=1;
|
||||
assert(parse_ntp_reply(reply,48,request,&epoch)==-3);
|
||||
valid_reply(reply,request); memset(reply+32,0,8);
|
||||
assert(parse_ntp_reply(reply,48,request,&epoch)==-4);
|
||||
valid_reply(reply,request); memset(reply+40,0,8);
|
||||
assert(parse_ntp_reply(reply,48,request,&epoch)==-4);
|
||||
valid_reply(reply,request); write_u32(reply+40,2208988800U+100);
|
||||
assert(parse_ntp_reply(reply,48,request,&epoch)==-5);
|
||||
valid_reply(reply,request); write_u32(reply+40,(unsigned int)(NTP_EPOCH_DELTA+2100000000ULL));
|
||||
assert(parse_ntp_reply(reply,48,request,&epoch)==0 && epoch==2100000000U);
|
||||
}
|
||||
int main(int argc,char **argv) {
|
||||
assert(argc==2); mode=atoi(argv[1]); check_parser();
|
||||
app_ntp_test_start(); app_ntp_test_start(); assert(creates==1);
|
||||
assert(frees==(mode==1 ? 0 : 1));
|
||||
assert(closes==((mode==1 || mode==10) ? 0 : 1));
|
||||
if (mode==1 || mode==2 || mode==6 || mode>=10) assert(syncs==0);
|
||||
else assert(syncs==1);
|
||||
if (mode==5) {saved_callback(0); assert(signals==1);}
|
||||
return 0;
|
||||
}
|
||||
'''.replace('APP_SOURCE', str(APP / 'app_ntp_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',
|
||||
'-I', str(work), '-I', str(APP / 'inc'),
|
||||
str(work / 'test.c'), '-o', str(work / 'test')], check=True)
|
||||
cases = {
|
||||
0: 'PASS udp_response=verified app_utc=verified',
|
||||
1: 'FAIL stage=dns',
|
||||
2: 'FAIL stage=receive_wait',
|
||||
3: 'FAIL stage=app_clock_verification',
|
||||
4: 'FAIL stage=app_clock_verification',
|
||||
5: 'FAIL stage=sdk_callback_timeout',
|
||||
6: 'FAIL stage=sdk_api_unavailable',
|
||||
7: 'FAIL stage=app_clock_verification',
|
||||
8: 'FAIL stage=app_clock_verification',
|
||||
10: 'FAIL stage=socket',
|
||||
11: 'FAIL stage=udp_connect',
|
||||
12: 'FAIL stage=send',
|
||||
13: 'FAIL stage=semaphore',
|
||||
14: 'FAIL stage=sdk_server',
|
||||
15: 'FAIL stage=sdk_cid',
|
||||
}
|
||||
for mode, expected in cases.items():
|
||||
log = subprocess.check_output([str(work / 'test'), str(mode)], text=True)
|
||||
assert expected in log, log
|
||||
if mode != 0:
|
||||
assert '[APP-NTP-TEST] PASS ' not in log, log
|
||||
print('PASS: packet validation and 15 network/SDK/clock scenarios; no device traffic sent')
|
||||
@@ -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')
|
||||
@@ -0,0 +1,146 @@
|
||||
"""Host behavioral checks for the actual diagnostic modules; no device required."""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
APP = ROOT / 'ql-application/threadx/interface/custom_app'
|
||||
KERNEL = ROOT / 'ql-kernel/threadx/api_map/talkingq_time_diag.c'
|
||||
|
||||
|
||||
def api_hash(name):
|
||||
value = 0
|
||||
for byte in name.encode():
|
||||
term = (value * 5527 + byte * 7) & 0xffffffff
|
||||
value = (term ^ ((term & 65535) ** 2)) & 0xffffffff
|
||||
return value
|
||||
|
||||
|
||||
assert api_hash('ql_user_api_map_test') == 0x60BCEA5A
|
||||
abi = (ROOT / 'ql-config/config/talkingq_time_diag.h').read_text()
|
||||
hash_value = int(re.search(r'API_HASH 0x([0-9A-F]+)', abi)[1], 16)
|
||||
assert api_hash('talkingq_time_diag_snapshot_v1') == hash_value
|
||||
table = (ROOT / 'ql-kernel/threadx/api_map/ql_user_api_map.c').read_text()
|
||||
assert f'{hash_value:08x}' not in table.lower()
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='time-diag-') as directory:
|
||||
work = Path(directory)
|
||||
headers = {
|
||||
'ql_func.h': '#include <stdint.h>\nextern intptr_t (*m_get_api_ptr)(const char *);\n',
|
||||
'ql_rtos.h': 'unsigned int ql_rtos_get_systicks(void);\n',
|
||||
'ql_dev.h': 'int ql_dev_get_firmware_version(char *, unsigned int);\n',
|
||||
'build_macro.h': '#define CUSTOM_APP_BUILD_VER "test"\n#define CUSTOM_APP_BUILD_TIME "test"\n#define CUSTOM_APP_BUILD_REV "test"\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;\nint ql_rtc_get_time(ql_rtc_time_t *);\n',
|
||||
'ql_type.h': '',
|
||||
'quec_cust_feature.h': '',
|
||||
'pmic.h': 'typedef int PMIC_RTC_OFFSET_STORAGE;\nunsigned int PM812_RTC_GET_32B_SYS_OFFSET(void), PM812_RTC_GET_32B_APP_OFFSET(void), PM812_RTC_GET_32B_COUNTER(void);\n',
|
||||
'pmic_rtc.h': 'int pmic_rtc_INIT_FLAG_GET(void), pmic_userdata_get_app_rtc_ever_configed(void), PMIC_RTC_GetTimezone(void);\n',
|
||||
'quec_rtc.h': 'int GetTimeZone(void), quec_get_rtc_source(void);\n',
|
||||
}
|
||||
for name, content in headers.items():
|
||||
(work / name).write_text(content)
|
||||
(work / 'harness.c').write_text(r'''
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "talkingq_time_diag.h"
|
||||
#include "time_diag.h"
|
||||
#include "ql_rtc.h"
|
||||
unsigned char m_rtc_app_sys_keeping_synced;
|
||||
int m_pmic_rtc_offset_storage_type;
|
||||
char ntp_sync_cnt = 3, ntp_dns_gain = 1;
|
||||
static int initialized, raw_reads, api_mode, sample_index, write_pointer;
|
||||
static time_t samples[2];
|
||||
unsigned int ql_rtos_get_systicks(void) { return 123; }
|
||||
int pmic_rtc_INIT_FLAG_GET(void) { return initialized; }
|
||||
int pmic_userdata_get_app_rtc_ever_configed(void) { return 1; }
|
||||
unsigned int PM812_RTC_GET_32B_COUNTER(void) { ++raw_reads; return 0xfffffff0U; }
|
||||
unsigned int PM812_RTC_GET_32B_SYS_OFFSET(void) { ++raw_reads; return 100; }
|
||||
unsigned int PM812_RTC_GET_32B_APP_OFFSET(void) { ++raw_reads; return 50; }
|
||||
int PMIC_RTC_GetTimezone(void) { return 8; }
|
||||
int GetTimeZone(void) { return 480; }
|
||||
int quec_get_rtc_source(void) { return 1; }
|
||||
int get_ntp_time_type(void) { return 0; }
|
||||
int GetNTPStatus(void) { return 1; }
|
||||
int talkingq_ntp_diag_ready(void) { return 1; }
|
||||
int quec_get_rtc_status(void) { return 1; }
|
||||
int quec_get_nw_status(void) { return 2; }
|
||||
char *get_ntp_server_ip(void) { return "192.0.2.1"; }
|
||||
int ql_dev_get_firmware_version(char *out, unsigned int size) {
|
||||
snprintf(out, size, "test-firmware"); return 0;
|
||||
}
|
||||
int ql_rtc_get_time(ql_rtc_time_t *out) { (void)out; return -1; }
|
||||
time_t time(time_t *out) {
|
||||
time_t value;
|
||||
assert(sample_index < 2);
|
||||
value = samples[sample_index++];
|
||||
if (out && write_pointer) *out = value;
|
||||
return value;
|
||||
}
|
||||
static int bad_snapshot(talkingq_time_diag_snapshot *out, unsigned int size) {
|
||||
(void)size; out->abi_version = 99; return 0;
|
||||
}
|
||||
static intptr_t lookup(const char *name) {
|
||||
assert(strcmp(name, TALKINGQ_TIME_DIAG_API_NAME) == 0);
|
||||
if (api_mode == 0) return 0;
|
||||
if (api_mode == 1) return (intptr_t)bad_snapshot;
|
||||
return (intptr_t)talkingq_time_diag_snapshot_v1;
|
||||
}
|
||||
intptr_t (*m_get_api_ptr)(const char *) = lookup;
|
||||
int main(void) {
|
||||
talkingq_time_diag_snapshot s;
|
||||
assert(sizeof(s) == 108);
|
||||
assert(talkingq_time_diag_snapshot_v1(NULL, sizeof(s)) == -1);
|
||||
assert(talkingq_time_diag_snapshot_v1(&s, sizeof(s)-1) == -1);
|
||||
assert(talkingq_time_diag_snapshot_v1(&s, sizeof(s)) == 0);
|
||||
assert(!s.rtc_initialized && raw_reads == 0);
|
||||
initialized = 1;
|
||||
assert(talkingq_time_diag_snapshot_v1(&s, sizeof(s)) == 0);
|
||||
assert(raw_reads == 3 && s.sys_offset == 100 && s.app_offset == 50);
|
||||
assert(s.counter + s.sys_offset == 84 && s.counter + s.app_offset == 34);
|
||||
assert(s.app_follows_sys == 0 && m_rtc_app_sys_keeping_synced == 0);
|
||||
assert(s.network_tz_minutes == 480 && s.rtc_tz_hours == 8);
|
||||
#ifdef NTP
|
||||
assert(s.ntp_ready == 1 && s.ntp_status == 1);
|
||||
#else
|
||||
assert(s.ntp_ready == -1 && s.ntp_status == -1);
|
||||
#endif
|
||||
#ifdef QUEC_GNSS_NTP
|
||||
assert(s.gnss_cid == 2 && strcmp(s.ntp_server, "192.0.2.1") == 0);
|
||||
#else
|
||||
assert(s.gnss_cid == -1 && s.ntp_server[0] == 0);
|
||||
#endif
|
||||
talkingq_time_diag_boot(); /* old kernel */
|
||||
api_mode = 1; talkingq_time_diag_log("bad-abi");
|
||||
api_mode = 2; talkingq_time_diag_log("divergent");
|
||||
samples[0] = 100; samples[1] = 1800000000;
|
||||
assert(talkingq_time_diag_wss_time() == 100);
|
||||
samples[0] = 1800000000; samples[1] = 100;
|
||||
sample_index = 0; write_pointer = 1;
|
||||
assert(talkingq_time_diag_wss_time() == 1800000000);
|
||||
assert(m_rtc_app_sys_keeping_synced == 0);
|
||||
talkingq_time_diag_trace("kernel-check");
|
||||
puts("PASS");
|
||||
return 0;
|
||||
}
|
||||
''')
|
||||
compiler = shutil.which('clang') or shutil.which('cc')
|
||||
if not compiler:
|
||||
raise SystemExit('A native C compiler is required')
|
||||
for features in ([], ['-DNTP'], ['-DNTP', '-DQUEC_GNSS_NTP']):
|
||||
command = [compiler, '-std=c99', '-Wall', '-Wextra', '-Werror',
|
||||
'-I', str(work), '-I', str(APP / 'inc'),
|
||||
'-I', str(ROOT / 'ql-config/config'), *features,
|
||||
str(APP / 'time_diag.c'), str(KERNEL), str(work / 'harness.c'),
|
||||
'-o', str(work / 'test')]
|
||||
subprocess.run(command, check=True)
|
||||
log = subprocess.check_output([str(work / 'test')], text=True)
|
||||
for expected in ('kernel_diag=unavailable', 'kernel_diag=abi_mismatch',
|
||||
'sys_raw=84 app_raw=34', 'time_ptr_value=1',
|
||||
'time_ptr_value=100', 'valid=0', 'valid=1', 'PASS'):
|
||||
assert expected in log, expected
|
||||
print('PASS:', ' '.join(features) or 'optional features off')
|
||||
Reference in New Issue
Block a user