test: verify NTP reachability and APP clock on device
This commit is contained in:
@@ -0,0 +1,44 @@
|
|||||||
|
# 纯 APP 的设备侧 NTP 测试
|
||||||
|
|
||||||
|
本次测试只新增 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 测试指令,因此尚未执行本测试固件的设备侧验证,不能宣称白名单已经放行。
|
||||||
@@ -83,6 +83,7 @@ SRC_FILES+= \
|
|||||||
mqtt.c \
|
mqtt.c \
|
||||||
mqtt_connect_diag.c \
|
mqtt_connect_diag.c \
|
||||||
time_diag.c \
|
time_diag.c \
|
||||||
|
app_ntp_test.c \
|
||||||
vad_det.c \
|
vad_det.c \
|
||||||
sleep_wake.c \
|
sleep_wake.c \
|
||||||
battery.c \
|
battery.c \
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "ql_rtos.h"
|
||||||
|
#include "ql_func.h"
|
||||||
|
#include "ql_ntp.h"
|
||||||
|
#include "ql_rtc.h"
|
||||||
|
#include "netdb.h"
|
||||||
|
#include "sockets.h"
|
||||||
|
#include "app_ntp_test.h"
|
||||||
|
|
||||||
|
/* Use the existing GNSS NTP provider first; do not assume it is whitelisted. */
|
||||||
|
#ifndef APP_NTP_TEST_SERVER
|
||||||
|
#define APP_NTP_TEST_SERVER "ntp.aliyun.com"
|
||||||
|
#endif
|
||||||
|
#define NTP_PACKET_SIZE 48
|
||||||
|
#define NTP_EPOCH_DELTA 2208988800ULL
|
||||||
|
#define NTP_MIN_UNIX 1704067200ULL
|
||||||
|
#define NTP_MAX_UNIX 2147483647ULL
|
||||||
|
#define NTP_WAIT_MS 5000U
|
||||||
|
|
||||||
|
static ql_task_t test_task;
|
||||||
|
static ql_sem_t callback_done;
|
||||||
|
static volatile int callback_status = -1;
|
||||||
|
static int test_started;
|
||||||
|
|
||||||
|
static unsigned int read_u32(const unsigned char *p)
|
||||||
|
{
|
||||||
|
return ((unsigned int)p[0] << 24) | ((unsigned int)p[1] << 16) |
|
||||||
|
((unsigned int)p[2] << 8) | p[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_u32(unsigned char *p, unsigned int value)
|
||||||
|
{
|
||||||
|
p[0] = (unsigned char)(value >> 24);
|
||||||
|
p[1] = (unsigned char)(value >> 16);
|
||||||
|
p[2] = (unsigned char)(value >> 8);
|
||||||
|
p[3] = (unsigned char)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate the response to our connected UDP socket, including request echo.
|
||||||
|
* This is a reachability/clock test, not authenticated NTP. Handle era 1
|
||||||
|
* (2036 rollover), but reject epochs outside the target's signed time range. */
|
||||||
|
static int parse_ntp_reply(const unsigned char *reply, unsigned int length,
|
||||||
|
const unsigned char *request, unsigned int *epoch)
|
||||||
|
{
|
||||||
|
unsigned int version;
|
||||||
|
unsigned long long seconds;
|
||||||
|
if (length < NTP_PACKET_SIZE) return -1;
|
||||||
|
version = (reply[0] >> 3) & 7U;
|
||||||
|
if ((reply[0] >> 6) == 3 || (version != 3 && version != 4) ||
|
||||||
|
(reply[0] & 7U) != 4 || reply[1] == 0 || reply[1] > 15) return -2;
|
||||||
|
if (memcmp(reply + 24, request + 40, 8) != 0) return -3;
|
||||||
|
if (read_u32(reply + 32) == 0 && read_u32(reply + 36) == 0) return -4;
|
||||||
|
seconds = read_u32(reply + 40);
|
||||||
|
if (seconds == 0 && read_u32(reply + 44) == 0) return -4;
|
||||||
|
if (seconds < NTP_EPOCH_DELTA) seconds += 0x100000000ULL;
|
||||||
|
seconds -= NTP_EPOCH_DELTA;
|
||||||
|
if (seconds < NTP_MIN_UNIX || seconds > NTP_MAX_UNIX) return -5;
|
||||||
|
*epoch = (unsigned int)seconds;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int probe_ntp(unsigned int *epoch, unsigned int *sample_ticks)
|
||||||
|
{
|
||||||
|
struct addrinfo hints, *addresses = NULL;
|
||||||
|
unsigned char request[NTP_PACKET_SIZE] = {0}, reply[96];
|
||||||
|
unsigned int began, elapsed, token;
|
||||||
|
char ip[48] = {0};
|
||||||
|
int fd = -1, ret, received;
|
||||||
|
fd_set readers;
|
||||||
|
struct timeval timeout;
|
||||||
|
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
hints.ai_socktype = SOCK_DGRAM;
|
||||||
|
hints.ai_protocol = IPPROTO_UDP;
|
||||||
|
printf("[APP-NTP-TEST] begin server=%s port=123 protocol=UDP pdp=1\r\n",
|
||||||
|
APP_NTP_TEST_SERVER);
|
||||||
|
ret = getaddrinfo_with_pcid(APP_NTP_TEST_SERVER, "123", &hints, &addresses, 1);
|
||||||
|
if (ret != 0 || addresses == NULL) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=dns rc=%d\r\n", ret);
|
||||||
|
if (addresses) freeaddrinfo(addresses);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
inet_ntop(AF_INET, &((struct sockaddr_in *)addresses->ai_addr)->sin_addr,
|
||||||
|
ip, sizeof(ip));
|
||||||
|
printf("[APP-NTP-TEST] dns_ok ip=%s response_not_yet_verified=1\r\n", ip);
|
||||||
|
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
|
if (fd < 0 || fd >= FD_SETSIZE) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=socket fd=%d\r\n", fd);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
ret = connect(fd, addresses->ai_addr, addresses->ai_addrlen);
|
||||||
|
if (ret != 0) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=udp_connect rc=%d\r\n", ret);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
request[0] = (4U << 3) | 3U; /* NTP v4 client. */
|
||||||
|
token = ql_rtos_get_systicks();
|
||||||
|
write_u32(request + 40, 0xe0000000U ^ token);
|
||||||
|
write_u32(request + 44, token ^ 0x6e747031U);
|
||||||
|
began = ql_rtos_get_systicks();
|
||||||
|
ret = send(fd, request, sizeof(request), 0);
|
||||||
|
if (ret != (int)sizeof(request)) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=send rc=%d\r\n", ret);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
/* Keep one deadline even if an unrelated or invalid packet arrives. */
|
||||||
|
while ((elapsed = (ql_rtos_get_systicks() - began) * 5U) < NTP_WAIT_MS) {
|
||||||
|
timeout.tv_sec = (NTP_WAIT_MS - elapsed) / 1000;
|
||||||
|
timeout.tv_usec = ((NTP_WAIT_MS - elapsed) % 1000) * 1000;
|
||||||
|
FD_ZERO(&readers);
|
||||||
|
FD_SET(fd, &readers);
|
||||||
|
ret = select(fd + 1, &readers, NULL, NULL, &timeout);
|
||||||
|
if (ret <= 0) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=receive_wait rc=%d "
|
||||||
|
"cause=undetermined_check_whitelist_udp123_and_server\r\n", ret);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
received = recv(fd, reply, sizeof(reply), 0);
|
||||||
|
if (received <= 0) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=receive rc=%d\r\n", received);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
ret = parse_ntp_reply(reply, (unsigned int)received, request, epoch);
|
||||||
|
if (ret != 0) {
|
||||||
|
printf("[APP-NTP-TEST] rejected_reply bytes=%d reason=%d\r\n", received, ret);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*sample_ticks = ql_rtos_get_systicks();
|
||||||
|
printf("[APP-NTP-TEST] UDP_RESPONSE_VERIFIED ip=%s epoch=%lu rtt_ms=%lu\r\n",
|
||||||
|
ip, (unsigned long)*epoch,
|
||||||
|
(unsigned long)(*sample_ticks - began) * 5UL);
|
||||||
|
close(fd);
|
||||||
|
freeaddrinfo(addresses);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=valid_response_timeout\r\n");
|
||||||
|
failed:
|
||||||
|
if (fd >= 0) close(fd);
|
||||||
|
freeaddrinfo(addresses);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ntp_callback(int status)
|
||||||
|
{
|
||||||
|
callback_status = status;
|
||||||
|
/* This semaphore lives for the boot, including callbacks after timeout. */
|
||||||
|
ql_rtos_semaphore_release(callback_done);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ntp_test_worker(void *arg)
|
||||||
|
{
|
||||||
|
unsigned int reference, reference_ticks, now_ticks, expected;
|
||||||
|
time_t before, after;
|
||||||
|
ql_rtc_time_t calendar;
|
||||||
|
long long delta;
|
||||||
|
int ret, calendar_ret;
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
if (probe_ntp(&reference, &reference_ticks) != 0) goto done;
|
||||||
|
if (!m_get_api_ptr || !m_get_api_ptr("ql_ntp_set_server") ||
|
||||||
|
!m_get_api_ptr("ql_ntp_set_cid") || !m_get_api_ptr("ql_ntp_sync_ex")) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=sdk_api_unavailable udp_verified=1\r\n");
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
ret = ql_rtos_semaphore_create(&callback_done, 0);
|
||||||
|
if (ret != 0) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=semaphore rc=%d\r\n", ret);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
ret = ql_ntp_set_server(APP_NTP_TEST_SERVER);
|
||||||
|
if (ret != QL_NTP_SUCCESS) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=sdk_server rc=%d\r\n", ret);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
ret = ql_ntp_set_cid(1);
|
||||||
|
if (ret != QL_NTP_SUCCESS) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=sdk_cid rc=%d\r\n", ret);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
before = time(NULL);
|
||||||
|
printf("[APP-NTP-TEST] sdk_sync_begin app_before=%ld\r\n", (long)before);
|
||||||
|
ql_ntp_sync_ex(ntp_callback);
|
||||||
|
ret = ql_rtos_semaphore_wait(callback_done, 25000);
|
||||||
|
if (ret != 0) {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=sdk_callback_timeout rc=%d "
|
||||||
|
"no_new_request_until_reboot=1\r\n", ret);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
after = time(NULL); /* SDK time(&value) writeback is not assumed. */
|
||||||
|
now_ticks = ql_rtos_get_systicks();
|
||||||
|
expected = reference + (now_ticks - reference_ticks) / 200U;
|
||||||
|
delta = (long long)after - expected;
|
||||||
|
memset(&calendar, 0, sizeof(calendar));
|
||||||
|
calendar_ret = ql_rtc_get_time(&calendar);
|
||||||
|
printf("[APP-NTP-TEST] sdk_result callback=%d app_before=%ld app_after=%ld "
|
||||||
|
"expected=%lu delta_s=%lld rtc_rc=%d\r\n",
|
||||||
|
callback_status, (long)before, (long)after,
|
||||||
|
(unsigned long)expected, delta, calendar_ret);
|
||||||
|
printf("[APP-NTP-TEST] app_calendar=%04d-%02d-%02dT%02d:%02d:%02d\r\n",
|
||||||
|
calendar.tm_year, calendar.tm_mon, calendar.tm_mday,
|
||||||
|
calendar.tm_hour, calendar.tm_min, calendar.tm_sec);
|
||||||
|
if (callback_status == QL_NTP_SUCCESS && calendar_ret == 0 &&
|
||||||
|
after >= (time_t)NTP_MIN_UNIX && delta >= -6 && delta <= 6) {
|
||||||
|
printf("[APP-NTP-TEST] PASS udp_response=verified app_utc=verified "
|
||||||
|
"wss_still_requires_separate_handshake_check=1\r\n");
|
||||||
|
} else {
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=app_clock_verification\r\n");
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
printf("[APP-NTP-TEST] finished one_test_per_boot=1\r\n");
|
||||||
|
ql_rtos_task_delete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void app_ntp_test_start(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
if (test_started) return; /* Called only by the PDP management task. */
|
||||||
|
test_started = 1;
|
||||||
|
ret = ql_rtos_task_create(&test_task, 6 * 1024, 110, "app_ntp_test",
|
||||||
|
ntp_test_worker, NULL);
|
||||||
|
if (ret != 0) {
|
||||||
|
test_started = 0;
|
||||||
|
printf("[APP-NTP-TEST] FAIL stage=create_task rc=%d\r\n", ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef TALKINGQ_APP_NTP_TEST_H
|
||||||
|
#define TALKINGQ_APP_NTP_TEST_H
|
||||||
|
|
||||||
|
/* Test firmware: one asynchronous test per boot, after PDP activation. */
|
||||||
|
void app_ntp_test_start(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "http_downfile.h"
|
#include "http_downfile.h"
|
||||||
#include "ota_post_update.h"
|
#include "ota_post_update.h"
|
||||||
#include "time_diag.h"
|
#include "time_diag.h"
|
||||||
|
#include "app_ntp_test.h"
|
||||||
|
|
||||||
//#define NTP_SERVER_DOMAIN "ntp.aliyun.com"
|
//#define NTP_SERVER_DOMAIN "ntp.aliyun.com"
|
||||||
|
|
||||||
@@ -143,6 +144,7 @@ void ql_datacall_demo_thread(void *arvg)
|
|||||||
if(ret == 0)
|
if(ret == 0)
|
||||||
{
|
{
|
||||||
talkingq_time_diag_log("pdp-active");
|
talkingq_time_diag_log("pdp-active");
|
||||||
|
app_ntp_test_start();
|
||||||
ql_get_data_call_info(1, 0, &info);
|
ql_get_data_call_info(1, 0, &info);
|
||||||
printf("info.profile_idx: %d\r\n", info.profile_idx);
|
printf("info.profile_idx: %d\r\n", info.profile_idx);
|
||||||
printf("info.ip_version: %d\r\n", info.ip_version);
|
printf("info.ip_version: %d\r\n", info.ip_version);
|
||||||
@@ -199,7 +201,7 @@ int app_net_mgt_init()
|
|||||||
// ql_rtos_semaphore_create(&g_ql_ntp_sem,0);
|
// ql_rtos_semaphore_create(&g_ql_ntp_sem,0);
|
||||||
|
|
||||||
ql_rtc_set_nitz_mode(1);
|
ql_rtc_set_nitz_mode(1);
|
||||||
printf("[TIME-DIAG] nitz_enabled=1 app_ntp_enabled=0\r\n");
|
printf("[TIME-DIAG] nitz_enabled=1 app_ntp_test=once_after_pdp\r\n");
|
||||||
talkingq_time_diag_log("nitz-enabled");
|
talkingq_time_diag_log("nitz-enabled");
|
||||||
|
|
||||||
ql_task_t appTaskRef = NULL;
|
ql_task_t appTaskRef = NULL;
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
"""Compile the actual APP NTP tester with scripted network/SDK responses.
|
||||||
|
|
||||||
|
These are host regression tests, not evidence of device whitelist access.
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
APP = ROOT / 'ql-application/threadx/interface/custom_app'
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix='app-ntp-test-') as directory:
|
||||||
|
work = Path(directory)
|
||||||
|
headers = {
|
||||||
|
'ql_rtos.h': '''#include <stddef.h>
|
||||||
|
typedef void *ql_task_t;
|
||||||
|
typedef void *ql_sem_t;
|
||||||
|
unsigned int ql_rtos_get_systicks(void);
|
||||||
|
int ql_rtos_task_create(ql_task_t *, unsigned int, unsigned char, char *, void (*)(void *), void *, ...);
|
||||||
|
int ql_rtos_task_delete(ql_task_t);
|
||||||
|
int ql_rtos_semaphore_create(ql_sem_t *, unsigned int);
|
||||||
|
int ql_rtos_semaphore_wait(ql_sem_t, unsigned int);
|
||||||
|
int ql_rtos_semaphore_release(ql_sem_t);
|
||||||
|
''',
|
||||||
|
'ql_func.h': '#include <stdint.h>\nextern intptr_t (*m_get_api_ptr)(char *);\n',
|
||||||
|
'ql_rtc.h': '''typedef struct {
|
||||||
|
int tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday;
|
||||||
|
} ql_rtc_time_t;
|
||||||
|
int ql_rtc_get_time(ql_rtc_time_t *);
|
||||||
|
''',
|
||||||
|
'ql_ntp.h': '''enum {QL_NTP_SUCCESS=0};
|
||||||
|
int ql_ntp_set_server(char *);
|
||||||
|
int ql_ntp_set_cid(int);
|
||||||
|
void ql_ntp_sync_ex(void (*)(int));
|
||||||
|
''',
|
||||||
|
'netdb.h': '''#include <sys/socket.h>
|
||||||
|
struct addrinfo {
|
||||||
|
int ai_flags,ai_family,ai_socktype,ai_protocol;
|
||||||
|
socklen_t ai_addrlen;
|
||||||
|
struct sockaddr *ai_addr;
|
||||||
|
struct addrinfo *ai_next;
|
||||||
|
};
|
||||||
|
int getaddrinfo_with_pcid(const char *,const char *,const struct addrinfo *,struct addrinfo **,unsigned char);
|
||||||
|
#define freeaddrinfo fake_freeaddrinfo
|
||||||
|
void fake_freeaddrinfo(struct addrinfo *);
|
||||||
|
''',
|
||||||
|
'sockets.h': '''#include <sys/socket.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#define socket fake_socket
|
||||||
|
#define connect fake_connect
|
||||||
|
#define send fake_send
|
||||||
|
#define recv fake_recv
|
||||||
|
#define close fake_close
|
||||||
|
#define select fake_select
|
||||||
|
int fake_socket(int,int,int);
|
||||||
|
int fake_connect(int,const struct sockaddr *,socklen_t);
|
||||||
|
int fake_send(int,const void *,size_t,int);
|
||||||
|
int fake_recv(int,void *,size_t,int);
|
||||||
|
int fake_close(int);
|
||||||
|
int fake_select(int,fd_set *,fd_set *,fd_set *,struct timeval *);
|
||||||
|
''',
|
||||||
|
}
|
||||||
|
for name, content in headers.items():
|
||||||
|
(work / name).write_text(content)
|
||||||
|
harness = r'''
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "APP_SOURCE"
|
||||||
|
static int mode, creates, sends, closes, frees, syncs, signals;
|
||||||
|
static unsigned int ticks=100;
|
||||||
|
static unsigned char sent[48];
|
||||||
|
static time_t app_epoch=100;
|
||||||
|
static void (*saved_callback)(int);
|
||||||
|
static struct sockaddr_in address;
|
||||||
|
static struct addrinfo result;
|
||||||
|
unsigned int ql_rtos_get_systicks(void) { return ticks; }
|
||||||
|
int getaddrinfo_with_pcid(const char *name,const char *port,
|
||||||
|
const struct addrinfo *hints,struct addrinfo **out,unsigned char cid) {
|
||||||
|
assert(strcmp(name,"ntp.aliyun.com")==0 && strcmp(port,"123")==0 && cid==1);
|
||||||
|
assert(hints->ai_family==AF_INET && hints->ai_socktype==SOCK_DGRAM);
|
||||||
|
if (mode==1) return -9;
|
||||||
|
address.sin_family=AF_INET; address.sin_port=htons(123);
|
||||||
|
address.sin_addr.s_addr=htonl(0xc0000201U);
|
||||||
|
result.ai_addr=(struct sockaddr *)&address; result.ai_addrlen=sizeof(address);
|
||||||
|
*out=&result; return 0;
|
||||||
|
}
|
||||||
|
void fake_freeaddrinfo(struct addrinfo *p) {assert(p==&result); ++frees;}
|
||||||
|
int fake_socket(int domain,int type,int protocol) {
|
||||||
|
assert(domain==AF_INET && type==SOCK_DGRAM && protocol==IPPROTO_UDP);
|
||||||
|
return mode==10 ? -1 : 7;
|
||||||
|
}
|
||||||
|
int fake_connect(int fd,const struct sockaddr *p,socklen_t size) {
|
||||||
|
assert(fd==7 && p==(struct sockaddr *)&address && size==sizeof(address));
|
||||||
|
return mode==11 ? -1 : 0;
|
||||||
|
}
|
||||||
|
int fake_send(int fd,const void *data,size_t size,int flags) {
|
||||||
|
assert(fd==7 && size==48 && flags==0); ++sends;
|
||||||
|
memcpy(sent,data,48); assert(sent[0]==0x23);
|
||||||
|
return mode==12 ? -1 : 48;
|
||||||
|
}
|
||||||
|
int fake_select(int nfds,fd_set *readers,fd_set *writers,fd_set *errors,struct timeval *timeout) {
|
||||||
|
assert(nfds==8 && FD_ISSET(7,readers) && writers==NULL && errors==NULL);
|
||||||
|
assert(timeout->tv_sec<=5); ticks+=20;
|
||||||
|
return mode==2 ? 0 : 1;
|
||||||
|
}
|
||||||
|
static void valid_reply(unsigned char *p,const unsigned char *request) {
|
||||||
|
memset(p,0,48); p[0]=0x24; p[1]=2;
|
||||||
|
memcpy(p+24,request+40,8);
|
||||||
|
write_u32(p+32,4008988800U); write_u32(p+40,4008988800U);
|
||||||
|
}
|
||||||
|
int fake_recv(int fd,void *buffer,size_t size,int flags) {
|
||||||
|
assert(fd==7 && size>=48 && flags==0);
|
||||||
|
valid_reply(buffer,sent); return 48;
|
||||||
|
}
|
||||||
|
int fake_close(int fd) {assert(fd==7); ++closes; return 0;}
|
||||||
|
int ql_rtos_task_create(ql_task_t *task,unsigned int size,unsigned char priority,char *name,
|
||||||
|
void (*entry)(void *),void *arg,...) {
|
||||||
|
assert(size>=4096 && priority==110 && strcmp(name,"app_ntp_test")==0);
|
||||||
|
++creates; *task=(void *)1; entry(arg); return 0;
|
||||||
|
}
|
||||||
|
int ql_rtos_task_delete(ql_task_t task) {assert(task==NULL); return 0;}
|
||||||
|
int ql_rtos_semaphore_create(ql_sem_t *sem,unsigned int count) {
|
||||||
|
assert(count==0);
|
||||||
|
if (mode==13) return -1;
|
||||||
|
*sem=(void *)2; return 0;
|
||||||
|
}
|
||||||
|
int ql_rtos_semaphore_wait(ql_sem_t sem,unsigned int timeout) {
|
||||||
|
assert(sem==(void *)2 && timeout==25000);
|
||||||
|
ticks+=100;
|
||||||
|
if (mode==5) return -1;
|
||||||
|
assert(signals==1); return 0;
|
||||||
|
}
|
||||||
|
int ql_rtos_semaphore_release(ql_sem_t sem) {assert(sem==(void *)2); ++signals; return 0;}
|
||||||
|
static intptr_t lookup(char *name) {
|
||||||
|
assert(strncmp(name,"ql_ntp_",7)==0);
|
||||||
|
return mode==6 ? 0 : 1;
|
||||||
|
}
|
||||||
|
intptr_t (*m_get_api_ptr)(char *)=lookup;
|
||||||
|
int ql_ntp_set_server(char *name) {assert(strcmp(name,"ntp.aliyun.com")==0); return mode==14 ? 1 : 0;}
|
||||||
|
int ql_ntp_set_cid(int cid) {assert(cid==1); return mode==15 ? 1 : 0;}
|
||||||
|
void ql_ntp_sync_ex(void (*callback)(int)) {
|
||||||
|
++syncs; saved_callback=callback;
|
||||||
|
if (mode==5) return;
|
||||||
|
if (mode!=3) app_epoch=1800000000;
|
||||||
|
if (mode==7) app_epoch+=28800;
|
||||||
|
callback(mode==4 ? 1 : 0);
|
||||||
|
}
|
||||||
|
time_t time(time_t *out) {(void)out; return app_epoch;}
|
||||||
|
int ql_rtc_get_time(ql_rtc_time_t *calendar) {
|
||||||
|
calendar->tm_year=2027; return mode==8 ? -1 : 0;
|
||||||
|
}
|
||||||
|
static void check_parser(void) {
|
||||||
|
unsigned char request[48]={0},reply[48]; unsigned int epoch=0;
|
||||||
|
request[40]=9; valid_reply(reply,request);
|
||||||
|
assert(parse_ntp_reply(reply,48,request,&epoch)==0 && epoch==1800000000);
|
||||||
|
assert(parse_ntp_reply(reply,47,request,&epoch)==-1);
|
||||||
|
reply[0]|=0xc0; assert(parse_ntp_reply(reply,48,request,&epoch)==-2);
|
||||||
|
valid_reply(reply,request); reply[1]=0;
|
||||||
|
assert(parse_ntp_reply(reply,48,request,&epoch)==-2);
|
||||||
|
reply[1]=16; assert(parse_ntp_reply(reply,48,request,&epoch)==-2);
|
||||||
|
valid_reply(reply,request); reply[0]=0x23;
|
||||||
|
assert(parse_ntp_reply(reply,48,request,&epoch)==-2);
|
||||||
|
valid_reply(reply,request); reply[24]^=1;
|
||||||
|
assert(parse_ntp_reply(reply,48,request,&epoch)==-3);
|
||||||
|
valid_reply(reply,request); memset(reply+32,0,8);
|
||||||
|
assert(parse_ntp_reply(reply,48,request,&epoch)==-4);
|
||||||
|
valid_reply(reply,request); memset(reply+40,0,8);
|
||||||
|
assert(parse_ntp_reply(reply,48,request,&epoch)==-4);
|
||||||
|
valid_reply(reply,request); write_u32(reply+40,2208988800U+100);
|
||||||
|
assert(parse_ntp_reply(reply,48,request,&epoch)==-5);
|
||||||
|
valid_reply(reply,request); write_u32(reply+40,(unsigned int)(NTP_EPOCH_DELTA+2100000000ULL));
|
||||||
|
assert(parse_ntp_reply(reply,48,request,&epoch)==0 && epoch==2100000000U);
|
||||||
|
}
|
||||||
|
int main(int argc,char **argv) {
|
||||||
|
assert(argc==2); mode=atoi(argv[1]); check_parser();
|
||||||
|
app_ntp_test_start(); app_ntp_test_start(); assert(creates==1);
|
||||||
|
assert(frees==(mode==1 ? 0 : 1));
|
||||||
|
assert(closes==((mode==1 || mode==10) ? 0 : 1));
|
||||||
|
if (mode==1 || mode==2 || mode==6 || mode>=10) assert(syncs==0);
|
||||||
|
else assert(syncs==1);
|
||||||
|
if (mode==5) {saved_callback(0); assert(signals==1);}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
'''.replace('APP_SOURCE', str(APP / 'app_ntp_test.c'))
|
||||||
|
(work / 'test.c').write_text(harness)
|
||||||
|
compiler = shutil.which('clang') or shutil.which('cc')
|
||||||
|
if not compiler:
|
||||||
|
raise SystemExit('A native C compiler is required')
|
||||||
|
subprocess.run([compiler, '-std=c99', '-Wall', '-Wextra', '-Werror',
|
||||||
|
'-I', str(work), '-I', str(APP / 'inc'),
|
||||||
|
str(work / 'test.c'), '-o', str(work / 'test')], check=True)
|
||||||
|
cases = {
|
||||||
|
0: 'PASS udp_response=verified app_utc=verified',
|
||||||
|
1: 'FAIL stage=dns',
|
||||||
|
2: 'FAIL stage=receive_wait',
|
||||||
|
3: 'FAIL stage=app_clock_verification',
|
||||||
|
4: 'FAIL stage=app_clock_verification',
|
||||||
|
5: 'FAIL stage=sdk_callback_timeout',
|
||||||
|
6: 'FAIL stage=sdk_api_unavailable',
|
||||||
|
7: 'FAIL stage=app_clock_verification',
|
||||||
|
8: 'FAIL stage=app_clock_verification',
|
||||||
|
10: 'FAIL stage=socket',
|
||||||
|
11: 'FAIL stage=udp_connect',
|
||||||
|
12: 'FAIL stage=send',
|
||||||
|
13: 'FAIL stage=semaphore',
|
||||||
|
14: 'FAIL stage=sdk_server',
|
||||||
|
15: 'FAIL stage=sdk_cid',
|
||||||
|
}
|
||||||
|
for mode, expected in cases.items():
|
||||||
|
log = subprocess.check_output([str(work / 'test'), str(mode)], text=True)
|
||||||
|
assert expected in log, log
|
||||||
|
if mode != 0:
|
||||||
|
assert '[APP-NTP-TEST] PASS ' not in log, log
|
||||||
|
print('PASS: packet validation and 15 network/SDK/clock scenarios; no device traffic sent')
|
||||||
Reference in New Issue
Block a user