Compare commits

..
Author SHA1 Message Date
chenjintao db6109172c feat: add device diagnostics for missing voice replies
Track microphone levels, short reads and PCM buffer drops. Report audio
queue admission separately from TLS writes, expose server replies and
ACKs before filtering, and capture per-round counters before timeouts.

Add diagnostic regression coverage and support POSIX hosts in WSS tests.
2026-10-26 18:35:24 +08:00
chenjintao 280b7a4149 chore: refresh EG800AKCN 1605 prebuilt kernel baseline 2026-10-26 14:19:21 +08:00
38 changed files with 365 additions and 1655 deletions
-56
View File
@@ -1,56 +0,0 @@
# 纯 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` 单独回归,但不在本次开机流程中运行。
-46
View File
@@ -1,46 +0,0 @@
# 纯 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 测试指令,因此尚未执行本测试固件的设备侧验证,不能宣称白名单已经放行。
-31
View File
@@ -1,31 +0,0 @@
# 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 目标固件编译、链接或实机复现。
+29
View File
@@ -0,0 +1,29 @@
# 对话无回复诊断日志
设备端新增四类日志,按 `round`、`session`、单调运行时间关联。诊断不调整对话协议、VAD 阈值或回复等待时限。
| 标记 | 内容 | 可以缩小的范围 |
| --- | --- | --- |
| `MIC-DIAG` | 录音打开结果及句柄、读取返回值/字节数、全零帧、峰值/平均绝对幅值、录音事件、PCM 缓冲写入丢失 | 区分未取得 PCM、全零输入、已有音频但没有通过 VAD、音频交给编码器前丢失 |
| `VOICE-TX` | 认证消息入队结果;START/END 的序号、类型、字节数、返回值和入队耗时;音频入队失败 | 区分请求未入队、内存分配失败、正常提交发送队列 |
| `WSS-IO` | 实际 TLS 写入结果、累计写入次数和字节;TLS 错误码、部分写入进度和重试;实际接收字节;关闭时的累计收发及未消费接收字节 | 区分入队后未完成写入、对端关闭、收到字节但应用层未获得完整消息 |
| `VOICE-RX` | 接收文本长度、前 192 字节、当前会话、是否通过会话准入检查;二进制消息计数 | 显示 START_ACK/END_ACK、未知回复、被准入检查丢弃的消息。二进制消息仍沿用原有忽略行为 |
| `VOICE-DIAG` | START/END 入队后的摘要、超时断开前和断开回调时的摘要 | 汇总入队成功/失败数、音频载荷字节、文本/二进制消息数、ACK 次数、末次收包距今时长和回复等待时长 |
拾音统计按一次录音累计,每秒及停止时输出。`peak`/`mean_abs` 是 16 位有符号 PCM 的绝对幅值,不是分贝;`zero` 统计有效读取长度内全部为零的帧。`short` 包括零长度以及所有非 640 字节的读取。PCM 写入不足或失败计入 `ring_drops`,同时记录丢失字节数。录音 tick 回调不打印,其他重复事件限频。
`VOICE-TX ret=0` 只代表入队成功。`queue_bytes` 只计成功入队的音频载荷,不含自定义包头、WebSocket 帧头、认证和控制消息。`WSS-IO tx_bytes` 是传给传输层并成功返回的明文字节数,含握手/帧头,不能与 `queue_bytes` 直接比较;TLS 失败时已写出的部分字节由 `tls_write_failed`/`tls_write_timeout` 的 `sent` 单独报告。正常写入首条和每 50 次打印一次,实际接收前 20 次及每 50 次打印,断开时输出累计值。`tick`/`elapsed_ticks` 使用 RTOS tick,其他带 `ms` 的字段使用单调毫秒。
`start_ack`/`end_ack` 按收到的文本子串统计,包含被准入检查拒绝的消息;它们不是对当前 session 的服务端确认保证。应结合 `VOICE-RX` 原文中的会话标识判断归属。若服务端协议不发送 ACK,ACK 为零本身不代表失败。`accepted=1` 表示通过外层会话准入检查,后续仍可能因 OTA、打断状态、URL 或 session 校验而未播放。文本仅保留前 192 字节,原有错误消息完整分块日志继续保留。
复现时保留开机到失败后第二次对话结束的完整串口日志。推荐按以下顺序判断:
1. `MIC-DIAG` 检查读取长度、返回值和电平,结合既有 FVAD 语音确认日志判断拾音与 VAD。
2. `VOICE-TX` 检查 START、音频、END 入队;`queue_fail` 和 `ring_drops` 应为零。
3. `WSS-IO` 检查实际写入是否失败,关闭时是否有接收字节和残留未消费数据。
4. `VOICE-RX` 检查 ACK、未知错误文本、被拒绝的回复或二进制数据。
5. `timeout-before-disconnect` 检查 `wait_ms`。`wait=2` 是等待回复,`wait=1` 是无语音,`wait=3` 是 END 入队失败。`response` 表示现有业务逻辑已接收可用音频 URL,不等同于收到任意文本或 TTS_START。
即使 TLS 写入成功,也不能证明服务器应用收到完整录音。若已经看到归属正确的 END_ACK,却没有后续 TTS,优先按 session 查服务端识别和生成链路;若没有 ACK,也需要结合服务端接收记录判断。设备单侧日志不能完全区分服务端未处理、网络中断和回复延迟。
本机验证:`python3 tools/test_voice_diag.py`、`python3 tools/test_conversation_timeout.py`、`python3 tools/test_wss_io.py`、`python3 tools/test_wss_receive.py`。诊断测试直接编译实际函数并注入录音、缓冲区、队列及收包结果;不替代目标固件构建及设备实测。
@@ -82,8 +82,6 @@ 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 \
@@ -1,226 +0,0 @@
#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);
}
}
@@ -1,229 +0,0 @@
#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);
}
}
@@ -1,121 +0,0 @@
#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,7 +30,6 @@
#include "hal_api.h"
#include "nfc.h"
#include "ota_baseline.h"
#include "time_diag.h"
#define APP_ENABLE_SLEEP
@@ -148,7 +147,6 @@ 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,
@@ -1,6 +0,0 @@
#ifndef APP_NITZ_TEST_H
#define APP_NITZ_TEST_H
void app_nitz_test_start(void);
#endif
@@ -1,7 +0,0 @@
#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
@@ -1,7 +0,0 @@
#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
@@ -1,12 +0,0 @@
#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
@@ -96,6 +96,9 @@ typedef struct _rws_socket_struct
int command;
unsigned int next_message_id;
/* Worker-owned transport counters; socket creation zero-initializes these. */
unsigned int diag_tx_calls, diag_tx_bytes, diag_tx_failed;
unsigned int diag_rx_calls, diag_rx_bytes;
rws_bool is_connected; // sock connected + handshake done
@@ -28,7 +28,6 @@
#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"
@@ -194,17 +193,30 @@ static int rws_tls_send_all(_rws_socket *s, const unsigned char *data, size_t si
{
size_t sent = 0;
unsigned int started = ql_rtos_get_systicks();
if (!s->ssl) return -1;
unsigned int retries = 0;
if (!s->ssl) {
printf("[WSS-IO] tls_write_no_context requested=%u\n", (unsigned int)size);
return -1;
}
while (sent < size) {
int ret = mbedtls_ssl_write(&s->ssl->ssl_ctx, data + sent, size - sent);
if (ret > 0) {
sent += (size_t)ret;
} else if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
printf("[WSS-IO] tls_write_failed ret=%d sent=%u requested=%u retries=%u elapsed_ticks=%u\n",
ret, (unsigned int)sent, (unsigned int)size, retries,
(unsigned int)(ql_rtos_get_systicks() - started));
return -1;
} else {
retries++;
rws_thread_sleep(5);
}
if (sent < size && (unsigned int)(ql_rtos_get_systicks() - started) >= 1000) return -1;
if (sent < size && (unsigned int)(ql_rtos_get_systicks() - started) >= 1000) {
printf("[WSS-IO] tls_write_timeout last_ret=%d sent=%u requested=%u retries=%u elapsed_ticks=%u\n",
ret, (unsigned int)sent, (unsigned int)size, retries,
(unsigned int)(ql_rtos_get_systicks() - started));
return -1;
}
}
return (int)sent;
}
@@ -219,7 +231,6 @@ rws_bool rws_socket_send(_rws_socket *s, const void *data, const size_t data_siz
#ifdef LIBRWS_USING_MBED_TLS
if (s->scheme && strcmp(s->scheme, "wss") == 0)
{
LOG_D("[T:%.8d, L:%.5d, %*.*s] try send data with tls", ql_rtos_get_systicks(), __LINE__, DBG_FUNCTION_NUM, DBG_FUNCTION_NUM, __FUNCTION__);
sended = rws_tls_send_all(s, (const unsigned char *)data, data_size);
}
else
@@ -244,6 +255,14 @@ rws_bool rws_socket_send(_rws_socket *s, const void *data, const size_t data_siz
}
#endif
s->diag_tx_calls++;
if (sended > 0) s->diag_tx_bytes += (unsigned int)sended;
if (sended != (int)data_size) s->diag_tx_failed++;
if (s->diag_tx_calls == 1 || s->diag_tx_calls % 50 == 0 || sended != (int)data_size) {
printf("[WSS-IO] write tick=%u requested=%u ret=%d calls=%u bytes=%u failed=%u\n",
ql_rtos_get_systicks(), (unsigned int)data_size, sended,
s->diag_tx_calls, s->diag_tx_bytes, s->diag_tx_failed);
}
if (sended == (int)data_size)
{
rws_socket_error_cnt = 0;
@@ -284,7 +303,6 @@ rws_bool rws_socket_recv(_rws_socket *s)
rws_bool result = rws_true;
int is_reading = 1, error_number = -1, len = -1;
char *received = NULL;
size_t total_len = 0;
// char *buff = 0;
// LOG_D("[T:%.8d, L:%.5d, %*.*s] receive data", ql_rtos_get_systicks(), __LINE__, DBG_FUNCTION_NUM, DBG_FUNCTION_NUM, __FUNCTION__);
@@ -366,7 +384,12 @@ rws_bool rws_socket_recv(_rws_socket *s)
if (len > 0)
{
total_len += len;
s->diag_rx_calls++;
s->diag_rx_bytes += (unsigned int)len;
if (s->diag_rx_calls <= 20 || s->diag_rx_calls % 50 == 0)
printf("[WSS-IO] read tick=%u bytes=%d calls=%u total=%u buffered=%u command=%d\n",
ql_rtos_get_systicks(), len, s->diag_rx_calls, s->diag_rx_bytes,
(unsigned int)s->received_len, s->command);
if (s->command == COMMAND_WAIT_HANDSHAKE_RESPONCE &&
s->received_len + (size_t)len > RWS_HANDSHAKE_MAX + RWS_RECEIVE_BUFF_SIZE) {
s->error = rws_error_new_code_descr(rws_error_code_parse_handshake, "WebSocket headers too large");
@@ -1651,9 +1674,12 @@ void rws_socket_resize_received(_rws_socket *s, const size_t size)
void rws_socket_close(_rws_socket *s)
{
s->received_len = 0;
if (s->socket != RWS_INVALID_SOCKET)
{
printf("[WSS-IO] close tick=%u tx_calls=%u tx_bytes=%u tx_failed=%u rx_calls=%u rx_bytes=%u buffered=%u command=%d\n",
ql_rtos_get_systicks(), s->diag_tx_calls, s->diag_tx_bytes,
s->diag_tx_failed, s->diag_rx_calls, s->diag_rx_bytes,
(unsigned int)s->received_len, s->command);
#ifdef LIBRWS_USING_MBED_TLS
if (s->scheme && strcmp(s->scheme, "wss") == 0)
{
@@ -1670,6 +1696,7 @@ void rws_socket_close(_rws_socket *s)
WSACleanup();
#endif
}
s->received_len = 0;
s->is_connected = rws_false;
}
@@ -1946,8 +1973,7 @@ int rws_ssl_conn(_rws_socket *s)
LOG_E("WSS requires a trusted root CA\n");
goto exit;
}
stage = "check RTC time";
if (talkingq_time_diag_wss_time() < 1704067200) {
if (time(NULL) < 1704067200) {
LOG_E("WSS requires valid network/RTC time before certificate verification\n");
goto exit;
}
@@ -21,8 +21,6 @@
#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"
@@ -143,8 +141,6 @@ 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);
@@ -201,8 +197,6 @@ 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);
@@ -1,84 +0,0 @@
#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;
}
@@ -71,6 +71,55 @@ static int vad_tts_playback_finished_status = 0;
// 统计
static uint32 frame_count = 0;
static uint32 speech_frame_count = 0;
static struct {
unsigned int reads, short_reads, read_errors, zero_frames, samples, peak;
unsigned int ring_drops, ring_drop_bytes;
uint64_t abs_sum, last_log_ms, started_ms;
} mic_diag;
static void mic_diag_log(const char *stage)
{
printf("[MIC-DIAG] %s uptime_ms=%u age_ms=%u reads=%u short=%u read_errors=%u zero=%u peak=%u mean_abs=%u ring_drops=%u ring_drop_bytes=%u\n",
stage, (unsigned int)hal_os_up_time_ms(),
(unsigned int)(hal_os_up_time_ms() - mic_diag.started_ms),
mic_diag.reads, mic_diag.short_reads, mic_diag.read_errors, mic_diag.zero_frames,
mic_diag.peak, mic_diag.samples ? (unsigned int)(mic_diag.abs_sum / mic_diag.samples) : 0,
mic_diag.ring_drops, mic_diag.ring_drop_bytes);
}
/* Observe both preroll and live PCM writes; retain the caller's wakeup policy. */
static int mic_diag_write(const void *data, int len)
{
int written = hal_os_rb_write(ring_sender, data, len, 25);
if (written < len) {
mic_diag.ring_drops++;
mic_diag.ring_drop_bytes += (unsigned int)(len - (written > 0 ? written : 0));
}
return written;
}
static void mic_diag_read(const uint8_t *data, int len, int ret)
{
int i, bounded = len > VAD_FRAME_BYTES ? VAD_FRAME_BYTES : len;
unsigned int peak = 0;
mic_diag.reads++;
if (ret < 0) mic_diag.read_errors++;
if (len != VAD_FRAME_BYTES) mic_diag.short_reads++;
for (i = 0; i + 1 < bounded; i += 2) {
int sample = (int16_t)((unsigned int)data[i] | ((unsigned int)data[i + 1] << 8));
unsigned int magnitude = (unsigned int)(sample < 0 ? -sample : sample);
if (magnitude > peak) peak = magnitude;
mic_diag.abs_sum += magnitude;
mic_diag.samples++;
}
if (bounded > 0 && peak == 0) mic_diag.zero_frames++;
if (peak > mic_diag.peak) mic_diag.peak = peak;
if (mic_diag.reads == 1 || hal_os_up_time_ms() - mic_diag.last_log_ms >= 1000) {
printf("[MIC-DIAG] read ret=%d bytes=%d frame_peak=%u state=%d\n", ret, len, peak, g_state);
mic_diag_log("sampling");
mic_diag.last_log_ms = hal_os_up_time_ms();
}
}
extern int audio_packet_type;
@@ -84,7 +133,17 @@ static audio_record_handle record_hd;
static void ql_audio_record_callback(acm_audio_track_handle handle, acm_audio_record_event_t event)
{
//printf("audio_record_callback event:%d\r\n", event);
static int last_event = -1;
static uint64_t last_log_ms;
uint64_t now;
(void)handle;
if (event == AUDIO_RECORD_EVENT_TICK || event == AUDIO_RECORD_EVENT_NONE) return;
now = hal_os_up_time_ms();
if ((int)event != last_event || now - last_log_ms >= 1000) {
printf("[MIC-DIAG] callback event=%d uptime_ms=%u\n", event, (unsigned int)now);
last_event = (int)event;
last_log_ms = now;
}
}
void vad_tts_playback_finished()
@@ -206,7 +265,13 @@ int vad_audio_init(void) {
// 启动录音
int vad_audio_start(void) {
vad_audio_init();
memset(&mic_diag, 0, sizeof(mic_diag));
mic_diag.started_ms = hal_os_up_time_ms();
{
int init_ret = vad_audio_init();
printf("[MIC-DIAG] open ret=%d handle=%u uptime_ms=%u\n",
init_ret, (unsigned int)record_hd, (unsigned int)hal_os_up_time_ms());
}
frame_count = 0;
speech_frame_count = 0;
@@ -217,6 +282,7 @@ int vad_audio_start(void) {
// 停止录音
int vad_audio_stop(void) {
mic_diag_log("stop");
printf("Audio recording stopped, frames: %d, speech: %d",
frame_count, speech_frame_count);
@@ -235,6 +301,7 @@ int vad_audio_stop(void) {
// 停止录音
int vad_audio_stop_no_wait_resp(void) {
mic_diag_log("cancel");
conversation_cancel_wait();
printf("Audio recording stopped no_wait_resp, frames: %d, speech: %d",
@@ -381,7 +448,10 @@ void vad_audio_process(void) {
case STATE_RECORDING_ACTIVE:
memset(mic_buf, 0, sizeof(mic_buf));
//每次读取640字节音频数据
ql_audio_record_read(record_hd, mic_buf, &record_len);
{
int read_ret = ql_audio_record_read(record_hd, mic_buf, &record_len);
mic_diag_read(mic_buf, record_len, read_ret);
}
if (g_state == STATE_RECORDING_WAIT_SOUND) {
/* 首次开口确认前不向服务端发送静音。只在本地保留最近500ms,
@@ -392,7 +462,7 @@ void vad_audio_process(void) {
vad_preroll_count++;
}
} else {
if(hal_os_rb_write(ring_sender, (uint8*)mic_buf, record_len, 25)>0){
if(mic_diag_write(mic_buf, record_len)>0){
ql_rtos_semaphore_release(sender_sem);
}
}
@@ -437,7 +507,7 @@ void vad_audio_process(void) {
int preroll_queued = 0;
for (n = 0; n < vad_preroll_count; n++) {
size_t frame_index = (first + n) % VAD_PREROLL_FRAMES;
if (hal_os_rb_write(ring_sender, vad_preroll[frame_index], VAD_FRAME_BYTES, 25) > 0) {
if (mic_diag_write(vad_preroll[frame_index], VAD_FRAME_BYTES) > 0) {
preroll_queued = 1;
}
}
@@ -62,6 +62,12 @@ enum { WAIT_NONE, WAIT_NO_SPEECH, WAIT_REPLY, WAIT_SEND_FAILURE };
static int conversation_wait = WAIT_NONE;
static int timeout_action_busy = 0;
static int round_response_received = 0;
/* Queue admission is distinct from a completed TLS write and from server ACK. */
static struct {
unsigned int tx_packets, tx_bytes, tx_failed, rx_text, rx_bin;
unsigned int start_ack, end_ack;
uint64_t started_ms, last_rx_ms;
} voice_diag;
static unsigned int active_session_round = 0;
static int active_session_valid = 0;
static uint64_t startup_start_ms = 0;
@@ -87,6 +93,10 @@ static void conversation_unlock(void)
static void conversation_begin_round(void)
{
conversation_round++;
memset(&voice_diag, 0, sizeof(voice_diag));
voice_diag.started_ms = hal_os_up_time_ms();
printf("[VOICE-DIAG] begin round=%u uptime_ms=%u\n",
conversation_round, (unsigned int)voice_diag.started_ms);
conversation_wait = WAIT_NONE;
round_response_received = 0;
active_session_valid = 0;
@@ -217,6 +227,20 @@ unsigned char get_xiaoice_connected_status(void)
return xiaoice_wss_connect_status;
}
/* Called with conversation_mutex held. Ages use monotonic milliseconds. */
static void voice_diag_snapshot(const char *stage)
{
uint64_t now = hal_os_up_time_ms();
printf("[VOICE-DIAG] %s round=%u session=%s age_ms=%u queue_pkts=%u queue_bytes=%u queue_fail=%u rx_text=%u rx_bin=%u start_ack=%u end_ack=%u last_rx_age_ms=%d wait=%d wait_ms=%u response=%d\n",
stage, conversation_round, active_session_valid ? sessionId : "none",
(unsigned int)(now - voice_diag.started_ms), voice_diag.tx_packets,
voice_diag.tx_bytes, voice_diag.tx_failed, voice_diag.rx_text,
voice_diag.rx_bin, voice_diag.start_ack, voice_diag.end_ack,
voice_diag.rx_text || voice_diag.rx_bin ? (int)(now - voice_diag.last_rx_ms) : -1,
conversation_wait, conversation_wait == WAIT_REPLY ?
(unsigned int)(now - reply_wait_start_ms) : 0, round_response_received);
}
static void onopen(rws_socket socket)
{
if (socket != app->socket || !app->init) return;
@@ -245,6 +269,9 @@ static void onclose(rws_socket socket)
}
if (socket != app->socket) return;
conversation_lock();
voice_diag_snapshot("disconnected");
conversation_unlock();
xiaoice_wss_connect_status = 0;
app->socket = NULL;
/* The worker owns and releases this socket after the callback. */
@@ -649,6 +676,16 @@ static void conversation_log_error_message(const char *text, unsigned int len)
static void onmessage_text(rws_socket socket, const char *text, const unsigned int len)
{
conversation_lock();
voice_diag.rx_text++;
voice_diag.last_rx_ms = hal_os_up_time_ms();
if (strstr(text, "START_ACK")) voice_diag.start_ack++;
if (strstr(text, "END_ACK")) voice_diag.end_ack++;
/* Bounded preview includes unrecognized replies and ACKs before filtering. */
printf("[VOICE-RX] round=%u session=%s uptime_ms=%u len=%u accepted=%d preview=%.*s%s\n",
conversation_round, active_session_valid ? sessionId : "none",
(unsigned int)voice_diag.last_rx_ms, len,
conversation_accept_voice_message(text), (int)(len > 192 ? 192 : len), text,
len > 192 ? " [truncated]" : "");
if (strstr(text, "tts_error") != NULL || strstr(text, "tts_err") != NULL ||
strstr(text, "PROMPT_SOUND_URL") != NULL) {
conversation_log_error_message(text, len);
@@ -663,8 +700,16 @@ static void onmessage_text(rws_socket socket, const char *text, const unsigned i
}
static void onmessage_bin(rws_socket socket, const void * data, const unsigned int len)
{
LOG_D("message(bin): %d ", len);
{
(void)socket;
(void)data;
conversation_lock();
voice_diag.rx_bin++;
voice_diag.last_rx_ms = hal_os_up_time_ms();
if (voice_diag.rx_bin == 1 || voice_diag.rx_bin % 50 == 0)
printf("[VOICE-RX] binary round=%u len=%u count=%u ignored=1\n",
conversation_round, len, voice_diag.rx_bin);
conversation_unlock();
return;
}
@@ -811,7 +856,11 @@ int talkingq_ws_send_text(char *senddata)
LOG_D("talkingq_wss_send_text string = %s, len = %d", senddata, strlen(senddata));
rws_socket_send_text(app->socket, senddata);
{
int queued = rws_socket_send_text(app->socket, senddata);
printf("[VOICE-TX] auth/text queue uptime_ms=%u bytes=%u accepted=%d\n",
(unsigned int)hal_os_up_time_ms(), (unsigned int)strlen(senddata), queued != 0);
}
return 0;
}
@@ -1041,6 +1090,7 @@ void sendStartPacket(void) {
startPacket.dataSize = 0;
active_session_valid = sendRecordingAudioPacket(&startPacket) == 0;
voice_diag_snapshot("start-queued");
conversation_unlock();
// rt_kprintf("sendStartPacket, seq=%u\r\n", startPacket.sequenceNumber);
}
@@ -1059,6 +1109,7 @@ void sendEndPacket(void) {
reply_wait_start_ms = hal_os_up_time_ms();
conversation_wait = end_ret == 0 ? WAIT_REPLY : WAIT_SEND_FAILURE;
}
voice_diag_snapshot("end-queued");
}
conversation_unlock();
// rt_kprintf("sendEndPacket, seq=%u\r\n", endPacket.sequenceNumber);
@@ -1074,12 +1125,19 @@ void sendAudioPacket(const uint8_t* data, size_t dataSize) {
audioPacket.data = malloc(audioPacket.dataSize);
if(audioPacket.data==NULL){
RLOGE("audioPacket.data NULL");
conversation_lock();
voice_diag.tx_failed++;
printf("[VOICE-TX] alloc_failed round=%u seq=%u bytes=%u\n",
conversation_round, audioPacket.sequenceNumber, (unsigned int)dataSize);
conversation_unlock();
return;
}
memcpy(audioPacket.data, data, dataSize);
}
conversation_lock();
sendRecordingAudioPacket(&audioPacket);
conversation_unlock();
// rt_kprintf("sendAudioPacket, seq=%u\r\n", audioPacket.sequenceNumber);
if(audioPacket.data){
free(audioPacket.data);
@@ -1092,7 +1150,9 @@ void sendInterruptPacket(void) {
interruptPacket.packetType = 3;
interruptPacket.dataSize = 0;
conversation_lock();
sendRecordingAudioPacket(&interruptPacket);
conversation_unlock();
}
void sendAuthentication()
@@ -1122,6 +1182,9 @@ int sendRecordingAudioPacket(AudioPacket *packet) {
sendBuffer = malloc(DEVICE_ID_SIZE + SESSION_ID_SIZE + sizeof(uint32_t) * 2 + sizeof(uint32_t) + packet->dataSize);
if(sendBuffer==NULL){
RLOGE("sendBuffer NULL");
voice_diag.tx_failed++;
printf("[VOICE-TX] alloc_failed seq=%u type=%u bytes=%u\n",
packet->sequenceNumber, packet->packetType, (unsigned int)packet->dataSize);
return -1;
}
@@ -1151,7 +1214,21 @@ int sendRecordingAudioPacket(AudioPacket *packet) {
// memcpy(recorder->save_buf, (void *)sendBuffer, recorder->save_len);
//record_msg_send(recorder, RECORD_MSG_DATA, recorder->save_len);
uint64_t queue_started = hal_os_up_time_ms();
int send_ret = talkingq_ws_send_bin((uint8_t*)sendBuffer, totalSize);
if (send_ret == 0) {
voice_diag.tx_packets++;
voice_diag.tx_bytes += (unsigned int)packet->dataSize;
} else {
voice_diag.tx_failed++;
}
if (packet->packetType == 1 || packet->packetType == 2 || send_ret != 0 ||
voice_diag.tx_packets % 50 == 0) {
printf("[VOICE-TX] queue round=%u session=%s seq=%u type=%u bytes=%u ret=%d elapsed_ms=%u\n",
conversation_round, sessionId, packet->sequenceNumber, packet->packetType,
(unsigned int)packet->dataSize, send_ret,
(unsigned int)(hal_os_up_time_ms() - queue_started));
}
free(sendBuffer);
return send_ret;
// rt_kprintf("sendRecordingAudioPacket - Device ID: %s, Session ID: %s\n", deviceId, sessionId);
@@ -1493,6 +1570,7 @@ static void conversation_check_timeout(void)
(conversation_wait == WAIT_NO_SPEECH ||
conversation_wait == WAIT_SEND_FAILURE ||
hal_os_up_time_ms() - reply_wait_start_ms > 5000)) {
voice_diag_snapshot("timeout-before-disconnect");
expired = conversation_wait;
expired_round = wait_round;
conversation_wait = WAIT_NONE;
-37
View File
@@ -1,37 +0,0 @@
#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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-1
View File
@@ -12,7 +12,6 @@ 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,7 +30,6 @@ 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"
@@ -104,7 +103,6 @@ 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
/************************************************************************************************
@@ -1,80 +0,0 @@
#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);
}
+3 -23
View File
@@ -17,7 +17,6 @@
#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
@@ -416,8 +415,6 @@ 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();
@@ -432,18 +429,13 @@ 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){
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());
quec_gps_ntp_set_server(quec_gps_cfg->ntp_url);
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();
get_ntp_time_internal();
ntp_sync_cnt++;
GPS_LOGD("Ntp sync cnt:%d!!!",ntp_sync_cnt);
}
@@ -461,15 +453,6 @@ 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);
}
}
@@ -489,7 +472,6 @@ 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)
@@ -501,9 +483,7 @@ 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){
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());
quec_gps_ntp_set_server(quec_gps_cfg->ntp_url);
ntp_dns_gain++;
}
GPS_LOGI("Found an active pdp!!!");
-13
View File
@@ -77,7 +77,6 @@ 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>
@@ -205,11 +204,6 @@ 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*/
@@ -284,14 +278,11 @@ 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;
@@ -306,7 +297,6 @@ 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);
@@ -321,7 +311,6 @@ extern char *get_ntp_server_ip(void);
SetNTPStatus(2);//failed
}
talkingq_time_diag_trace("ntp-finished");
}
@@ -336,14 +325,12 @@ 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);
-181
View File
@@ -1,181 +0,0 @@
"""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)')
-216
View File
@@ -1,216 +0,0 @@
"""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')
-90
View File
@@ -1,90 +0,0 @@
"""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')
+2 -1
View File
@@ -38,6 +38,7 @@ static int queued, disconnected, flags_reset, mutex_locked, player_mq_couter, va
static uint64_t now;
static unsigned int gSequenceNumber;
static int end_result;
static char sessionId[33] = "test-session";
typedef struct { unsigned int sequenceNumber; int packetType, dataSize; } AudioPacket;
static int ql_rtos_mutex_lock(ql_mutex_t m, unsigned int t) {
(void)m; (void)t; assert(!mutex_locked); mutex_locked = 1; return 0;
@@ -66,7 +67,7 @@ static void talkingq_ws_disconnect(void) {
static int sendRecordingAudioPacket(AudioPacket *p) { assert(p->packetType == 2); return end_result; }
'''
harness += source[state_start:state_end]
for name in ['conversation_try_begin', 'sendEndPacket', 'conversation_check_timeout', 'conversation_accept_voice_message', 'conversation_finish_startup_prompt', 'conversation_find_session_id', 'role_notice_is_url', 'role_notice_start_locked', 'role_notice_poll_locked', 'app_role_notice_handle_url', 'app_role_notice_before_finish', 'role_notice_finished']:
for name in ['voice_diag_snapshot', 'conversation_try_begin', 'sendEndPacket', 'conversation_check_timeout', 'conversation_accept_voice_message', 'conversation_finish_startup_prompt', 'conversation_find_session_id', 'role_notice_is_url', 'role_notice_start_locked', 'role_notice_poll_locked', 'app_role_notice_handle_url', 'app_role_notice_before_finish', 'role_notice_finished']:
harness += function(name)
harness += r'''
static void reset(void) {
-146
View File
@@ -1,146 +0,0 @@
"""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')
+112
View File
@@ -0,0 +1,112 @@
"""Compile actual microphone, queue and receive diagnostics with scripted inputs."""
from pathlib import Path
import subprocess
import tempfile
root = Path(__file__).resolve().parents[1]
app = root / 'ql-application/threadx/interface/custom_app'
voice = (app / 'ws_talkingq.c').read_text()
mic = (app / 'vad_det.c').read_text()
def function(source, signature):
start = source.index(signature)
opening = source.index('{', start)
depth, end = 1, opening + 1
while depth:
depth += (source[end] == '{') - (source[end] == '}')
end += 1
return source[start:end] + '\n'
harness = r'''
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define VAD_FRAME_BYTES 640
#define DEVICE_ID_SIZE 32
#define SESSION_ID_SIZE 33
#define RLOGE(...) ((void)0)
typedef int rws_socket;
static int g_mic_working;
static int g_state, ring_result = 640, fail_alloc, queue_result, dispatched;
static unsigned int conversation_round = 7;
static int active_session_valid = 1;
static char sessionId[33] = "session", deviceId[32] = "device";
static void *ring_sender;
static uint64_t now = 1000;
static int mutex_locked;
static uint64_t hal_os_up_time_ms(void) { return now; }
static void conversation_lock(void) { assert(!mutex_locked); mutex_locked = 1; }
static void conversation_unlock(void) { assert(mutex_locked); mutex_locked = 0; }
static int hal_os_rb_write(void *ring, const void *data, int len, unsigned int timeout) {
(void)ring; (void)data; (void)len; assert(timeout == 25); return ring_result;
}
static void *test_malloc(size_t size) { return fail_alloc ? NULL : malloc(size); }
static int talkingq_ws_send_bin(uint8_t *data, int len) {
uint32_t seq;
assert(len >= DEVICE_ID_SIZE + SESSION_ID_SIZE + 12);
memcpy(&seq, data + DEVICE_ID_SIZE + SESSION_ID_SIZE, 4);
assert(seq == 9);
return queue_result;
}
typedef struct { uint32_t sequenceNumber, packetType, dataSize; uint8_t *data; } AudioPacket;
static int conversation_accept_voice_message(const char *text) { return strstr(text, "old") == NULL; }
static void conversation_log_error_message(const char *text, unsigned int len) { (void)text; (void)len; }
static void onmessage_text_locked(rws_socket socket, const char *text, unsigned int len) {
(void)socket; (void)text; (void)len; assert(mutex_locked); dispatched++;
}
'''
start = voice.index('static struct {')
end = voice.index('} voice_diag;', start) + len('} voice_diag;')
harness += voice[start:end] + '\n'
start = mic.index('static struct {')
end = mic.index('extern int audio_packet_type;', start)
harness += mic[start:end]
harness += '#define malloc test_malloc\n'
harness += function(voice, 'int sendRecordingAudioPacket(AudioPacket *packet) {')
harness += '#undef malloc\n'
harness += function(voice, 'static void onmessage_text(rws_socket socket,')
harness += function(voice, 'static void onmessage_bin(rws_socket socket,')
harness += r'''
int main(void) {
uint8_t pcm[640] = {0};
AudioPacket packet = {9, 0, 4, pcm};
mic_diag.started_ms = now;
mic_diag_read(pcm, 640, 0);
assert(mic_diag.reads == 1 && mic_diag.zero_frames == 1 && mic_diag.peak == 0);
/* Full-scale negative input must avoid int16 absolute-value overflow. */
pcm[0] = 0; pcm[1] = 128;
mic_diag_read(pcm, 640, 0);
assert(mic_diag.peak == 32768 && mic_diag.abs_sum == 32768 && mic_diag.samples == 640);
mic_diag_read(pcm, 0, -1);
assert(mic_diag.short_reads == 1 && mic_diag.read_errors == 1 && mic_diag.zero_frames == 1);
mic_diag_read(pcm, 10000, 0); /* Diagnostic sampling stays inside the buffer. */
assert(mic_diag.samples == 960 && mic_diag.short_reads == 2);
ring_result = 640; assert(mic_diag_write(pcm, 640) == 640 && !mic_diag.ring_drops);
ring_result = -1; assert(mic_diag_write(pcm, 640) == -1);
ring_result = 320; assert(mic_diag_write(pcm, 640) == 320);
assert(mic_diag.ring_drops == 2 && mic_diag.ring_drop_bytes == 960);
mic_diag_log("test");
queue_result = 0; assert(sendRecordingAudioPacket(&packet) == 0);
assert(voice_diag.tx_packets == 1 && voice_diag.tx_bytes == 4 && !voice_diag.tx_failed);
queue_result = -1; assert(sendRecordingAudioPacket(&packet) == -1);
fail_alloc = 1; assert(sendRecordingAudioPacket(&packet) == -1);
assert(voice_diag.tx_packets == 1 && voice_diag.tx_bytes == 4 && voice_diag.tx_failed == 2);
onmessage_text(0, "START_ACK", 9);
onmessage_text(0, "END_ACK", 7);
onmessage_text(0, "unknown backend response", 24);
onmessage_text(0, "old TTS_START", 13);
assert(voice_diag.start_ack == 1 && voice_diag.end_ack == 1 && voice_diag.rx_text == 4);
assert(dispatched == 3 && !mutex_locked);
now = 2000; onmessage_bin(0, pcm, 640);
assert(voice_diag.rx_bin == 1 && voice_diag.last_rx_ms == now && !mutex_locked);
puts("PASS: microphone silence/level/short reads/errors, PCM drops, queue failure/allocation failure, ACK visibility, unknown/rejected replies, ignored binary replies");
}
'''
with tempfile.TemporaryDirectory(prefix='banban-voice-diag-') as directory:
cfile = Path(directory) / 'diag.c'
exe = Path(directory) / 'diag-test'
cfile.write_text(harness)
subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', '-fsanitize=address,undefined', str(cfile), '-o', str(exe)], check=True)
subprocess.run([str(exe)], check=True)
+6 -2
View File
@@ -1,6 +1,7 @@
"""Compile actual SDK-independent function bodies with scripted TLS/RTOS stubs."""
from pathlib import Path
import subprocess
import os
root = Path(__file__).resolve().parents[1]
lib = root / 'ql-application/threadx/interface/custom_app/lib/librws/src'
@@ -75,6 +76,9 @@ output.mkdir(parents=True, exist_ok=True)
cfile = output / 'test_wss_io.c'
cfile.write_text(harness, encoding='utf-8')
exe = output / 'test_wss_io.exe'
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile),
'/Fo' + str(output / 'test_wss_io.obj'), '/Fe' + str(exe)], check=True)
if os.name == "nt":
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/TC', str(cfile),
'/Fo' + str(output / 'test_wss_io.obj'), '/Fe' + str(exe)], check=True)
else:
subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', str(cfile), '-o', str(exe)], check=True)
subprocess.run([str(exe)], check=True)
+8 -3
View File
@@ -1,6 +1,7 @@
"""Exercise the real receive/upgrade loop with delayed TLS records."""
from pathlib import Path
import subprocess
import os
root = Path(__file__).resolve().parents[1]
lib = root / 'ql-application/threadx/interface/custom_app/lib/librws'
@@ -37,6 +38,7 @@ typedef struct {
char *payload_buff, *received;
size_t received_size, received_len;
int socket, command, is_connected;
unsigned int diag_rx_calls, diag_rx_bytes;
const char *error;
} _rws_socket;
static int replies[16], reply_count, calls, closes;
@@ -120,7 +122,10 @@ output.mkdir(parents=True, exist_ok=True)
cfile = output / 'test_wss_receive.c'
cfile.write_text(harness, encoding='utf-8')
exe = output / 'test_wss_receive.exe'
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/wd4018', '/TC',
'/I' + str(lib / 'inc'), str(cfile), str(lib / 'src/rws_handshake.c'),
'/Fo' + str(output) + '/', '/Fe' + str(exe)], check=True)
if os.name == "nt":
subprocess.run(['cl', '/nologo', '/W4', '/WX', '/wd4018', '/TC',
'/I' + str(lib / 'inc'), str(cfile), str(lib / 'src/rws_handshake.c'),
'/Fo' + str(output) + '/', '/Fe' + str(exe)], check=True)
else:
subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', '-Wno-sign-compare', '-I' + str(lib / 'inc'), str(cfile), str(lib / 'src/rws_handshake.c'), '-o', str(exe)], check=True)
subprocess.run([str(exe)], check=True)