#include #include #include #include #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); } }