|
|
|
@@ -0,0 +1,226 @@
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include "ql_rtos.h"
|
|
|
|
|
#include "ql_func.h"
|
|
|
|
|
#include "ql_nw.h"
|
|
|
|
|
#include "ql_rtc.h"
|
|
|
|
|
#include "app_nitz_test.h"
|
|
|
|
|
|
|
|
|
|
/* SDK wrapper uses the same PMIC timezone as time(); no libc TZ assumptions. */
|
|
|
|
|
extern struct tm *ql_localtime_r(const time_t *, struct tm *);
|
|
|
|
|
#define MIN_EPOCH 1704067200LL
|
|
|
|
|
#define MAX_EPOCH 2147483647LL
|
|
|
|
|
#define ATTEMPTS 3
|
|
|
|
|
#define SAMPLE_MS 3000U
|
|
|
|
|
|
|
|
|
|
static ql_task_t test_task;
|
|
|
|
|
static int test_started;
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
time_t utc;
|
|
|
|
|
unsigned int ticks;
|
|
|
|
|
int zone_quarters;
|
|
|
|
|
} nitz_sample;
|
|
|
|
|
|
|
|
|
|
static int leap(int year)
|
|
|
|
|
{
|
|
|
|
|
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Explicit Gregorian conversion: no mktime() or APP clock dependency. */
|
|
|
|
|
static int calendar_epoch(int year, int month, int day, int hour, int minute,
|
|
|
|
|
int second, long long *result)
|
|
|
|
|
{
|
|
|
|
|
static const int days[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
|
|
|
|
long long total = 0;
|
|
|
|
|
int y, m;
|
|
|
|
|
if (year < 1970 || year > 2038 || month < 1 || month > 12 ||
|
|
|
|
|
day < 1 || day > days[month - 1] + (month == 2 && leap(year)) ||
|
|
|
|
|
hour < 0 || hour > 23 || minute < 0 || minute > 59 ||
|
|
|
|
|
second < 0 || second > 59) return -1;
|
|
|
|
|
for (y = 1970; y < year; ++y) total += 365 + leap(y);
|
|
|
|
|
for (m = 1; m < month; ++m) total += days[m - 1] + (m == 2 && leap(year));
|
|
|
|
|
*result = ((total + day - 1) * 24 + hour) * 3600 + minute * 60 + second;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int two_digits(const char *s)
|
|
|
|
|
{
|
|
|
|
|
if (s[0] < '0' || s[0] > '9' || s[1] < '0' || s[1] > '9') return -1;
|
|
|
|
|
return (s[0] - '0') * 10 + s[1] - '0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Accept SDK documented "YY/MM/DD HH:MM:SS +32 00" and compact
|
|
|
|
|
* "YY/MM/DD,HH:MM:SS+32". NITZ UT is UTC; zone is metadata, not subtracted.
|
|
|
|
|
* abs_time/leap_sec have insufficient documented semantics to use as epoch. */
|
|
|
|
|
static int parse_nitz(const char *s, time_t *epoch, int *zone)
|
|
|
|
|
{
|
|
|
|
|
size_t n = strlen(s), pos = 17;
|
|
|
|
|
int yy, mo, dd, hh, mm, ss, q, dst;
|
|
|
|
|
long long value;
|
|
|
|
|
if (n < 20 || s[2] != '/' || s[5] != '/' ||
|
|
|
|
|
(s[8] != ' ' && s[8] != ',') || s[11] != ':' || s[14] != ':') return -1;
|
|
|
|
|
yy = two_digits(s); mo = two_digits(s + 3); dd = two_digits(s + 6);
|
|
|
|
|
hh = two_digits(s + 9); mm = two_digits(s + 12); ss = two_digits(s + 15);
|
|
|
|
|
if (yy < 0 || calendar_epoch(2000 + yy, mo, dd, hh, mm, ss, &value) != 0 ||
|
|
|
|
|
value < MIN_EPOCH || value > MAX_EPOCH) return -2;
|
|
|
|
|
if (s[pos] == ' ') ++pos;
|
|
|
|
|
if (n < pos + 3 || (s[pos] != '+' && s[pos] != '-')) return -3;
|
|
|
|
|
q = two_digits(s + pos + 1);
|
|
|
|
|
if (q < 0 || q > 56 || (s[pos] == '-' && q > 48)) return -3;
|
|
|
|
|
*zone = s[pos] == '-' ? -q : q;
|
|
|
|
|
pos += 3;
|
|
|
|
|
if (pos < n) {
|
|
|
|
|
if (n != pos + 3 || s[pos] != ' ') return -4;
|
|
|
|
|
dst = two_digits(s + pos + 1);
|
|
|
|
|
if (dst < 0 || dst > 2) return -4;
|
|
|
|
|
}
|
|
|
|
|
*epoch = (time_t)value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int get_sample(int attempt, int index, nitz_sample *sample)
|
|
|
|
|
{
|
|
|
|
|
QL_NW_NITZ_TIME_INFO_T info;
|
|
|
|
|
unsigned int began = ql_rtos_get_systicks();
|
|
|
|
|
int rc, parsed;
|
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
|
rc = ql_nw_get_nitz_time_info(&info);
|
|
|
|
|
sample->ticks = ql_rtos_get_systicks();
|
|
|
|
|
printf("[APP-NITZ-TEST] sample attempt=%d index=%d rc=%d raw=\"%.*s\" "
|
|
|
|
|
"abs_time=%lu leap_sec=%u query_ms=%lu\r\n", attempt, index, rc,
|
|
|
|
|
(int)sizeof(info.nitz_time), info.nitz_time, info.abs_time,
|
|
|
|
|
(unsigned int)info.leap_sec,
|
|
|
|
|
(unsigned long)(sample->ticks - began) * 5UL);
|
|
|
|
|
if (rc != QL_NW_SUCCESS ||
|
|
|
|
|
memchr(info.nitz_time, '\0', sizeof(info.nitz_time)) == NULL) return -1;
|
|
|
|
|
parsed = parse_nitz(info.nitz_time, &sample->utc, &sample->zone_quarters);
|
|
|
|
|
if (parsed != 0) {
|
|
|
|
|
printf("[APP-NITZ-TEST] rejected stage=parse reason=%d\r\n", parsed);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
/* A long blocking query makes the timestamp's age ambiguous. */
|
|
|
|
|
if (sample->ticks - began > 400U) return -1;
|
|
|
|
|
printf("[APP-NITZ-TEST] parsed utc=%ld zone_quarters=%d zone_minutes=%d\r\n",
|
|
|
|
|
(long)sample->utc, sample->zone_quarters, sample->zone_quarters * 15);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int write_and_verify(const nitz_sample *reference)
|
|
|
|
|
{
|
|
|
|
|
struct tm local;
|
|
|
|
|
ql_rtc_time_t target, actual;
|
|
|
|
|
time_t epoch, before = time(NULL), after;
|
|
|
|
|
long long expected, local_epoch, delta, actual_local = 0;
|
|
|
|
|
int rc, read_rc, calendar_ok;
|
|
|
|
|
expected = (long long)reference->utc +
|
|
|
|
|
(ql_rtos_get_systicks() - reference->ticks) / 200U;
|
|
|
|
|
if (expected > MAX_EPOCH) {
|
|
|
|
|
printf("[APP-NITZ-TEST] FAIL stage=epoch_range\r\n");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
epoch = (time_t)expected;
|
|
|
|
|
memset(&local, 0, sizeof(local));
|
|
|
|
|
if (ql_localtime_r(&epoch, &local) == NULL ||
|
|
|
|
|
calendar_epoch(local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
|
|
|
|
|
local.tm_hour, local.tm_min, local.tm_sec, &local_epoch) != 0 ||
|
|
|
|
|
local_epoch < MIN_EPOCH || local_epoch > MAX_EPOCH ||
|
|
|
|
|
local_epoch - expected < -43200 || local_epoch - expected > 50400) {
|
|
|
|
|
printf("[APP-NITZ-TEST] FAIL stage=local_calendar\r\n");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
memset(&target, 0, sizeof(target));
|
|
|
|
|
target.tm_year = local.tm_year + 1900;
|
|
|
|
|
target.tm_mon = local.tm_mon + 1;
|
|
|
|
|
target.tm_mday = local.tm_mday;
|
|
|
|
|
target.tm_hour = local.tm_hour;
|
|
|
|
|
target.tm_min = local.tm_min;
|
|
|
|
|
target.tm_sec = local.tm_sec;
|
|
|
|
|
target.tm_wday = local.tm_wday;
|
|
|
|
|
printf("[APP-NITZ-TEST] set_begin app_before=%ld expected_utc=%ld "
|
|
|
|
|
"pmic_zone_minutes=%ld app_calendar=%04d-%02d-%02dT%02d:%02d:%02d\r\n",
|
|
|
|
|
(long)before, (long)epoch, (long)((local_epoch - expected) / 60),
|
|
|
|
|
target.tm_year, target.tm_mon, target.tm_mday,
|
|
|
|
|
target.tm_hour, target.tm_min, target.tm_sec);
|
|
|
|
|
rc = ql_rtc_set_time(&target);
|
|
|
|
|
memset(&actual, 0, sizeof(actual));
|
|
|
|
|
read_rc = ql_rtc_get_time(&actual);
|
|
|
|
|
after = time(NULL);
|
|
|
|
|
expected = (long long)reference->utc +
|
|
|
|
|
(ql_rtos_get_systicks() - reference->ticks) / 200U;
|
|
|
|
|
delta = (long long)after - expected;
|
|
|
|
|
calendar_ok = calendar_epoch(actual.tm_year, actual.tm_mon, actual.tm_mday,
|
|
|
|
|
actual.tm_hour, actual.tm_min, actual.tm_sec,
|
|
|
|
|
&actual_local) == 0;
|
|
|
|
|
actual_local -= local_epoch + (expected - (long long)epoch);
|
|
|
|
|
printf("[APP-NITZ-TEST] readback set_rc=%d rtc_rc=%d app_after=%ld "
|
|
|
|
|
"expected_utc=%lld delta_s=%lld calendar=%04d-%02d-%02dT%02d:%02d:%02d\r\n",
|
|
|
|
|
rc, read_rc, (long)after, expected, delta, actual.tm_year, actual.tm_mon,
|
|
|
|
|
actual.tm_mday, actual.tm_hour, actual.tm_min, actual.tm_sec);
|
|
|
|
|
if (rc != 0 || read_rc != 0 || after < MIN_EPOCH ||
|
|
|
|
|
delta < -3 || delta > 3 || !calendar_ok ||
|
|
|
|
|
actual_local < -3 || actual_local > 3) {
|
|
|
|
|
printf("[APP-NITZ-TEST] FAIL stage=app_clock_verification\r\n");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
printf("[APP-NITZ-TEST] PASS source=nitz app_utc=verified "
|
|
|
|
|
"wss_requires_separate_handshake_check=1\r\n");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void nitz_test_worker(void *arg)
|
|
|
|
|
{
|
|
|
|
|
nitz_sample first, second;
|
|
|
|
|
int attempt;
|
|
|
|
|
long long advance, elapsed, drift;
|
|
|
|
|
unsigned int span;
|
|
|
|
|
(void)arg;
|
|
|
|
|
printf("[APP-NITZ-TEST] begin version=1 source=operator_nitz ntp=off\r\n");
|
|
|
|
|
if (!m_get_api_ptr || !m_get_api_ptr("ql_nw_get_nitz_time_info") ||
|
|
|
|
|
!m_get_api_ptr("ql_localtime_r") || !m_get_api_ptr("ql_rtc_set_time") ||
|
|
|
|
|
!m_get_api_ptr("ql_rtc_get_time")) {
|
|
|
|
|
printf("[APP-NITZ-TEST] FAIL stage=sdk_api_unavailable\r\n");
|
|
|
|
|
goto done;
|
|
|
|
|
}
|
|
|
|
|
for (attempt = 1; attempt <= ATTEMPTS; ++attempt) {
|
|
|
|
|
if (get_sample(attempt, 1, &first) == 0) {
|
|
|
|
|
ql_rtos_task_sleep_ms(SAMPLE_MS);
|
|
|
|
|
if (get_sample(attempt, 2, &second) == 0) {
|
|
|
|
|
span = second.ticks - first.ticks; /* unsigned handles tick wrap */
|
|
|
|
|
elapsed = span / 200U;
|
|
|
|
|
advance = (long long)second.utc - first.utc;
|
|
|
|
|
drift = advance - elapsed;
|
|
|
|
|
printf("[APP-NITZ-TEST] freshness advance_s=%lld elapsed_s=%lld "
|
|
|
|
|
"drift_s=%lld\r\n", advance, elapsed, drift);
|
|
|
|
|
if (span >= 400U && span <= 2000U && advance > 0 &&
|
|
|
|
|
drift >= -1 && drift <= 1 &&
|
|
|
|
|
first.zone_quarters == second.zone_quarters) {
|
|
|
|
|
write_and_verify(&second);
|
|
|
|
|
goto done; /* Never repeat an uncertain RTC write. */
|
|
|
|
|
}
|
|
|
|
|
printf("[APP-NITZ-TEST] rejected stage=freshness "
|
|
|
|
|
"cached_or_inconsistent_nitz=1 rtc_not_written=1\r\n");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (attempt < ATTEMPTS) ql_rtos_task_sleep_ms(5000);
|
|
|
|
|
}
|
|
|
|
|
printf("[APP-NITZ-TEST] FAIL stage=no_fresh_nitz rtc_not_written=1\r\n");
|
|
|
|
|
done:
|
|
|
|
|
printf("[APP-NITZ-TEST] finished one_test_per_boot=1\r\n");
|
|
|
|
|
ql_rtos_task_delete(NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void app_nitz_test_start(void)
|
|
|
|
|
{
|
|
|
|
|
int rc;
|
|
|
|
|
if (test_started) return; /* PDP management is the sole caller. */
|
|
|
|
|
test_started = 1;
|
|
|
|
|
rc = ql_rtos_task_create(&test_task, 6 * 1024, 110, "app_nitz_test",
|
|
|
|
|
nitz_test_worker, NULL);
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
test_started = 0;
|
|
|
|
|
printf("[APP-NITZ-TEST] FAIL stage=create_task rc=%d\r\n", rc);
|
|
|
|
|
}
|
|
|
|
|
}
|