|
|
|
@@ -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);
|
|
|
|
|
}
|
|
|
|
|
}
|