test: verify NTP reachability and APP clock on device

This commit is contained in:
chenjintao
2026-10-26 15:24:13 +08:00
parent c53a117368
commit 03627a26fa
6 changed files with 500 additions and 1 deletions
@@ -83,6 +83,7 @@ SRC_FILES+= \
mqtt.c \
mqtt_connect_diag.c \
time_diag.c \
app_ntp_test.c \
vad_det.c \
sleep_wake.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 "ota_post_update.h"
#include "time_diag.h"
#include "app_ntp_test.h"
//#define NTP_SERVER_DOMAIN "ntp.aliyun.com"
@@ -143,6 +144,7 @@ void ql_datacall_demo_thread(void *arvg)
if(ret == 0)
{
talkingq_time_diag_log("pdp-active");
app_ntp_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);
@@ -199,7 +201,7 @@ 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_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");
ql_task_t appTaskRef = NULL;