test: verify NTP reachability and APP clock on device
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user