* Copyright (C) 2026 Xiaomi Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* This file contains code derived from MimiClaw (https://github.com/memovai/mimiclaw)
* Copyright (c) 2026 Ziboyan Wang, licensed under the MIT License.
* See NOTICE file for the original MIT License terms.
*/
#include "tools/tool_get_time.h"
#include "agent_config.h"
#include "agent_compat.h"
#include "infra/http_proxy.h"
#include "infra/vela_tls.h"
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
static const char *TAG = "tool_time";
static const char *MONTHS[] = {
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"
};
static bool parse_and_set_time(const char *date_str, char *out, size_t out_size)
{
int day, year, hour, min, sec;
char mon_str[4] = {0};
if (sscanf(date_str, "%*[^,], %d %3s %d %d:%d:%d",
&day, mon_str, &year, &hour, &min, &sec) != 6) {
return false;
}
int mon = -1;
for (int i = 0; i < 12; i++) {
if (strcmp(mon_str, MONTHS[i]) == 0) { mon = i; break; }
}
if (mon < 0) return false;
struct tm tm_utc;
memset(&tm_utc, 0, sizeof(tm_utc));
tm_utc.tm_sec = sec;
tm_utc.tm_min = min;
tm_utc.tm_hour = hour;
tm_utc.tm_mday = day;
tm_utc.tm_mon = mon;
tm_utc.tm_year = year - 1900;
* mktime() interprets its argument as local time (per TZ), so we
* must use timegm() or equivalent to get the correct UTC epoch.
* NuttX provides timegm(); if unavailable, temporarily override TZ. */
#ifdef __NuttX__
time_t t = timegm(&tm_utc);
#else
char *old_tz = getenv("TZ");
char saved_tz[64] = {0};
if (old_tz) strncpy(saved_tz, old_tz, sizeof(saved_tz) - 1);
setenv("TZ", "UTC0", 1);
tzset();
time_t t = mktime(&tm_utc);
if (old_tz) setenv("TZ", saved_tz, 1);
else unsetenv("TZ");
tzset();
#endif
if (t < 0) return false;
struct timeval tv = { .tv_sec = t, .tv_usec = 0 };
settimeofday(&tv, NULL);
* zoneinfo lookup errors on NuttX romfs */
struct tm local;
time_t local_epoch = t + 8 * 3600;
gmtime_r(&local_epoch, &local);
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S (%A)", &local);
snprintf(out, out_size, "%s CST (UTC+8), UNIX epoch: %lld", time_str, (long long)t);
return true;
}
static int fetch_time_via_proxy(char *out, size_t out_size)
{
proxy_conn_t *conn = proxy_conn_open("www.baidu.com", 443, 10000);
if (!conn) return ERROR;
const char *req =
"HEAD / HTTP/1.1\r\n"
"Host: www.baidu.com\r\n"
"Connection: close\r\n\r\n";
if (proxy_conn_write(conn, req, strlen(req)) < 0) {
proxy_conn_close(conn);
return ERROR;
}
char buf[1024];
int total = 0;
while (total < (int)sizeof(buf) - 1) {
int n = proxy_conn_read(conn, buf + total, sizeof(buf) - 1 - total, 10000);
if (n <= 0) break;
total += n;
buf[total] = '\0';
if (strstr(buf, "\r\n\r\n")) break;
}
proxy_conn_close(conn);
char *date_hdr = strcasestr(buf, "\r\nDate: ");
if (!date_hdr) return ERROR;
date_hdr += 8;
char *eol = strstr(date_hdr, "\r\n");
if (!eol) return ERROR;
char date_val[64];
size_t dlen = (size_t)(eol - date_hdr);
if (dlen >= sizeof(date_val)) return ERROR;
memcpy(date_val, date_hdr, dlen);
date_val[dlen] = '\0';
if (!parse_and_set_time(date_val, out, out_size)) return ERROR;
return OK;
}
static int fetch_time_direct(char *out, size_t out_size)
{
char date_val[64] = {0};
int ret = vela_https_head_date("www.baidu.com", "443", "/", date_val, sizeof(date_val));
if (ret != 0 || date_val[0] == '\0') return ERROR;
if (!parse_and_set_time(date_val, out, out_size)) return ERROR;
return OK;
}
int tool_get_time_execute(const char *input_json, char *output, size_t output_size)
{
syslog(LOG_INFO, "[%s] Fetching current time...\n", TAG);
time_t now = time(NULL);
if (now > 1735689600) {
* NuttX's localtime_r tries to open zoneinfo/<TZ> from romfs,
* which fails for POSIX TZ strings like "CST-8" and spams
* ERROR logs. We bypass this by computing UTC+8 manually. */
struct tm utc_tm;
time_t local_epoch = now + 8 * 3600;
gmtime_r(&local_epoch, &utc_tm);
char time_str[64];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", &utc_tm);
snprintf(output, output_size,
"%s CST (UTC+8), UNIX epoch: %lld",
time_str, (long long)now);
syslog(LOG_INFO, "[%s] Time (local clock): %s\n", TAG, output);
return OK;
}
int err;
if (http_proxy_is_enabled()) {
err = fetch_time_via_proxy(output, output_size);
} else {
err = fetch_time_direct(output, output_size);
}
if (err == OK) {
syslog(LOG_INFO, "[%s] Time (network): %s\n", TAG, output);
} else {
snprintf(output, output_size, "Error: failed to fetch time (err=%d)", err);
syslog(LOG_ERR, "[%s] %s\n", TAG, output);
}
return err;
}