diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 7c6de1d..86e9e2a 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 103 +#define MONTAUK_BUILD_NUMBER 107 diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 70f3120..56629c9 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -16,7 +16,7 @@ #include "Terminal.hpp" // SYS_PRINT, SYS_PUTCHAR #include "Filesystem.hpp" // SYS_OPEN, SYS_READ, SYS_GETSIZE, SYS_CLOSE, SYS_READDIR, SYS_FWRITE, SYS_FCREATE #include "Heap.hpp" // SYS_ALLOC, SYS_FREE -#include "Time.hpp" // SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME +#include "Time.hpp" // SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME, SYS_GETUNIXTIME #include "Keyboard.hpp" // SYS_ISKEYAVAILABLE, SYS_GETKEY, SYS_GETCHAR #include "Info.hpp" // SYS_GETINFO #include "Graphics.hpp" // SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE @@ -245,6 +245,8 @@ namespace montauk::abi { if (!UserMemory::Writable(frame->arg1)) return BadPointer; Sys_GetTime((DateTime*)frame->arg1); return Success; + case SYS_GETUNIXTIME: + return Sys_GetUnixTime(); case SYS_SETUNIXTIME: if (!Sched::Consent::Require(CAP_SET_TIME)) return CapabilityDenied; return Sys_SetUnixTime((int64_t)frame->arg1); diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index 7d0db5d..9fe7c93 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -405,6 +405,9 @@ namespace montauk::abi { and returns 1 while more remains, 0 once done. CAP_STORAGE_ADMIN. */ static constexpr uint64_t SYS_FS_GROW = 208; // (drive, FsGrowStatus*, apply) -> 1 / 0 / status + /* Time.hpp -- wall clock as UTC unix seconds (SYS_GETTIME is local time). */ + static constexpr uint64_t SYS_GETUNIXTIME = 209; // () -> seconds + /* Kernel-owned process capabilities. User identities may namespace per-user resources, but never participate in authorization decisions. */ static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0; diff --git a/kernel/src/Api/Time.hpp b/kernel/src/Api/Time.hpp index c334833..188bc91 100644 --- a/kernel/src/Api/Time.hpp +++ b/kernel/src/Api/Time.hpp @@ -1,6 +1,6 @@ /* * Time.hpp - * SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME syscalls + * SYS_GETTICKS, SYS_GETMILLISECONDS, SYS_GETTIME, SYS_GETUNIXTIME syscalls * Copyright (c) 2026 Daniel Hammer */ @@ -39,6 +39,10 @@ namespace montauk::abi { return (int64_t)Timekeeping::GetTZOffset(); } + static int64_t Sys_GetUnixTime() { + return Timekeeping::GetUnixTimestamp(); + } + static int64_t Sys_SetUnixTime(int64_t unixSeconds) { return Timekeeping::SetUnixTimestamp(unixSeconds) ? Success : InvalidArgument; } diff --git a/kernel/src/Timekeeping/Time.cpp b/kernel/src/Timekeeping/Time.cpp index bb823b6..95fdbf7 100644 --- a/kernel/src/Timekeeping/Time.cpp +++ b/kernel/src/Timekeeping/Time.cpp @@ -37,7 +37,9 @@ static int64_t DateToEpoch(int year, int month, int day, int hour, int minute, i static Timekeeping::DateTime EpochToDate(int64_t epoch) { Timekeeping::DateTime dt = {}; - int64_t rem = epoch; + /* Before the first NTP sync on a board with no RTC the clock sits near + the epoch, and a negative time zone pushes local time before it. */ + int64_t rem = epoch < 0 ? 0 : epoch; int year = 1970; while (true) { int64_t daysInYear = IsLeapYear(year) ? 366 : 365; diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index b835f0f..c2fa9d9 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -317,6 +317,9 @@ namespace montauk::abi { and returns 1 while more remains, 0 once done. CAP_STORAGE_ADMIN. */ static constexpr uint64_t SYS_FS_GROW = 208; // (drive, FsGrowStatus*, apply) -> 1 / 0 / status + /* Time.hpp -- wall clock as UTC unix seconds (SYS_GETTIME is local time). */ + static constexpr uint64_t SYS_GETUNIXTIME = 209; // () -> seconds + /* Kernel-owned process capabilities. User identities may namespace per-user resources, but never participate in authorization decisions. */ static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0; diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index 3219abd..28403b8 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -232,6 +232,7 @@ extern "C" { #define MTK_SYS_WIN_DRAG_FORMATS 206 #define MTK_SYS_WIN_DRAG_GET_DATA 207 #define MTK_SYS_FS_GROW 208 +#define MTK_SYS_GETUNIXTIME 209 /* @SYSCALLS-END */ /* Syscall status codes (montauk/statuscodes.h for C++). */ @@ -805,6 +806,10 @@ static inline void mtk_gettime(mtk_datetime *out) { _mtk_syscall1(MTK_SYS_GETTIME, (long)out); } +static inline int64_t mtk_get_unix_time(void) { + return (int64_t)_mtk_syscall0(MTK_SYS_GETUNIXTIME); +} + static inline int mtk_set_unix_time(int64_t unix_seconds) { return (int)_mtk_syscall1(MTK_SYS_SETUNIXTIME, (long)unix_seconds); } diff --git a/programs/include/montauk/ntp.h b/programs/include/montauk/ntp.h index 472aa1e..a7f584e 100644 --- a/programs/include/montauk/ntp.h +++ b/programs/include/montauk/ntp.h @@ -51,42 +51,6 @@ inline uint32_t read_be32(const uint8_t* p) { ((uint32_t)p[2] << 8) | (uint32_t)p[3]; } -inline void write_be64(uint8_t* p, uint64_t value) { - for (int i = 7; i >= 0; i--) { - p[i] = (uint8_t)value; - value >>= 8; - } -} - -inline bool is_leap_year(int year) { - return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); -} - -inline int days_in_month(int month, int year) { - static const int days[] = {0, 31, 28, 31, 30, 31, 30, - 31, 31, 30, 31, 30, 31}; - return month == 2 && is_leap_year(year) ? 29 : days[month]; -} - -inline int64_t current_unix_seconds() { - montauk::abi::DateTime now = {}; - montauk::gettime(&now); - if (now.Year < 1970 || now.Month < 1 || now.Month > 12 || - now.Day < 1 || now.Day > days_in_month(now.Month, now.Year)) - return 0; - - int64_t days = 0; - for (int year = 1970; year < (int)now.Year; year++) - days += is_leap_year(year) ? 366 : 365; - for (int month = 1; month < (int)now.Month; month++) - days += days_in_month(month, now.Year); - days += now.Day - 1; - - int64_t local = days * 86400 + (int64_t)now.Hour * 3600 + - (int64_t)now.Minute * 60 + now.Second; - return local - (int64_t)montauk::gettz() * 60; -} - inline bool same_bytes(const uint8_t* a, const uint8_t* b, int count) { for (int i = 0; i < count; i++) { if (a[i] != b[i]) return false; @@ -115,14 +79,15 @@ inline int synchronize(const char* server, uint32_t timeout_ms = 5000, request[2] = 6; // poll exponent: 2^6 = 64 seconds request[3] = 0xEC; // precision: approximately 2^-20 seconds - int64_t unix_now = current_unix_seconds(); - if (unix_now <= 0) { + // The transmit timestamp only has to come back in the reply's origin + // field, so send a random cookie rather + // than the local clock, which may still be invalid before the first sync. + uint8_t cookie[8]; + if (montauk::getrandom(cookie, sizeof(cookie)) != (int64_t)sizeof(cookie)) { montauk::closesocket(fd); return CLOCK_ERROR; } - uint64_t transmit_timestamp = - ((uint64_t)unix_now + UNIX_EPOCH_DELTA) << 32; - write_be64(request + 40, transmit_timestamp); + for (int i = 0; i < 8; i++) request[40 + i] = cookie[i]; progress(progress_callback, "Sending NTP request..."); if (montauk::sendto(fd, request, sizeof(request), server_ip, SERVER_PORT) < 0) { diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index 6cf795c..09694d1 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -505,6 +505,8 @@ namespace montauk { // Timekeeping (wall-clock) inline void gettime(montauk::abi::DateTime* out) { syscall1(montauk::abi::SYS_GETTIME, (uint64_t)out); } + // UTC seconds since 1970 (gettime() is local time). + inline int64_t get_unix_time() { return syscall0(montauk::abi::SYS_GETUNIXTIME); } inline int set_unix_time(int64_t unix_seconds) { return (int)syscall1(montauk::abi::SYS_SETUNIXTIME, (uint64_t)unix_seconds); } diff --git a/programs/lib/tls/libtls.a b/programs/lib/tls/libtls.a index b9f9e88..f18eb2e 100644 Binary files a/programs/lib/tls/libtls.a and b/programs/lib/tls/libtls.a differ diff --git a/programs/lib/tls/obj/tls.o b/programs/lib/tls/obj/tls.o index c1198fa..2891de1 100644 Binary files a/programs/lib/tls/obj/tls.o and b/programs/lib/tls/obj/tls.o differ diff --git a/programs/lib/tls/tls.cpp b/programs/lib/tls/tls.cpp index 9534a2d..4280ef0 100644 --- a/programs/lib/tls/tls.cpp +++ b/programs/lib/tls/tls.cpp @@ -196,17 +196,12 @@ void free_trust_anchors(TrustAnchors* tas) { } void get_bearssl_time(uint32_t* days, uint32_t* seconds) { - montauk::abi::DateTime dt; - montauk::gettime(&dt); - int y = dt.Year, m = dt.Month, d = dt.Day; - uint32_t total = 365u * (uint32_t)y - + (uint32_t)((y+3)/4) - (uint32_t)((y+99)/100) + (uint32_t)((y+399)/400); - const int md[] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; - for (int mo = 1; mo < m && mo <= 12; mo++) total += md[mo]; - if (y%4==0 && (y%100!=0 || y%400==0) && m > 2) total++; - total += d - 1; - *days = total; - *seconds = (uint32_t)(dt.Hour*3600 + dt.Minute*60 + dt.Second); + // Certificate validity is UTC; BearSSL counts days from 0000-01-01, + // and 1970-01-01 is day 719528. + int64_t t = montauk::get_unix_time(); + if (t < 0) t = 0; + *days = 719528u + (uint32_t)(t / 86400); + *seconds = (uint32_t)(t % 86400); } int tls_send_all(int fd, const unsigned char* data, size_t len) { diff --git a/programs/src/ntp/main.cpp b/programs/src/ntp/main.cpp index c1a8505..c575f34 100644 --- a/programs/src/ntp/main.cpp +++ b/programs/src/ntp/main.cpp @@ -33,6 +33,12 @@ static bool load_settings(char* server, int server_cap) { return enabled; } +static bool reached_server(int result) { + return result == montauk::ntp::TIMEOUT || + result == montauk::ntp::INVALID_REPLY || + result == montauk::ntp::SERVER_REFUSED; +} + extern "C" void _start() { log("service started"); @@ -68,16 +74,21 @@ extern "C" void _start() { montauk::abi::NetCfg net = {}; montauk::get_netcfg(&net); + // No lease yet (DHCP may still be waiting for link): keep polling + // instead of backing off, so the first sync lands right after DHCP. if (net.ipAddress == 0 || net.dnsServer == 0) { - next_attempt = now + RETRY_INTERVAL_MS; montauk::sleep_ms(CONFIG_POLL_MS); continue; } int result = montauk::ntp::synchronize(server); log(montauk::ntp::result_string(result)); - next_attempt = montauk::get_milliseconds() + - (result == montauk::ntp::OK ? SYNC_INTERVAL_MS : RETRY_INTERVAL_MS); + // The server rate limit only applies once a request reached it; + // failures before sending (DNS, socket) retry at the poll interval. + uint64_t delay = SYNC_INTERVAL_MS; + if (result != montauk::ntp::OK) + delay = reached_server(result) ? RETRY_INTERVAL_MS : CONFIG_POLL_MS; + next_attempt = montauk::get_milliseconds() + delay; montauk::sleep_ms(CONFIG_POLL_MS); } }