From f3d7d339536ae7578d103ac0a926304e09aef653 Mon Sep 17 00:00:00 2001 From: "U-DESKTOP-L7O40S8\\dimas" Date: Mon, 16 Feb 2026 14:16:49 +0200 Subject: [PATCH 1/9] Add crypt.h, base64.h, time.h --- runtime/lavina.h | 3 + runtime/liblavina/base64.h | 188 +++++++++++++++ runtime/liblavina/core.h | 9 +- runtime/liblavina/crypto.h | 113 +++++++++ runtime/liblavina/string.h | 16 ++ runtime/liblavina/time.h | 455 +++++++++++++++++++++++++++++++++++++ 6 files changed, 783 insertions(+), 1 deletion(-) create mode 100644 runtime/liblavina/base64.h create mode 100644 runtime/liblavina/crypto.h create mode 100644 runtime/liblavina/time.h diff --git a/runtime/lavina.h b/runtime/lavina.h index 76eb837..9ab415e 100644 --- a/runtime/lavina.h +++ b/runtime/lavina.h @@ -1,9 +1,12 @@ #pragma once #include "liblavina/core.h" +#include "liblavina/crypto.h" +#include "liblavina/base64.h" #include "liblavina/dynamic.h" #include "liblavina/print.h" #include "liblavina/convert.h" #include "liblavina/string.h" +#include "liblavina/time.h" #include "liblavina/vector.h" #include "liblavina/hashset.h" #include "liblavina/hashmap.h" diff --git a/runtime/liblavina/base64.h b/runtime/liblavina/base64.h new file mode 100644 index 0000000..369c804 --- /dev/null +++ b/runtime/liblavina/base64.h @@ -0,0 +1,188 @@ +#pragma once +#include "core.h" + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +// Lookup table for decoding +static constexpr std::array init_base64_lookup() { + std::array lookup{}; + for (int i = 0; i < 256; i++) { + lookup[i] = -1; + } + for (size_t i = 0; i < 64; i++) { + lookup[static_cast("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i])] = i; + } + return lookup; +} + +static constexpr auto base64_lookup = init_base64_lookup(); + +inline std::string base64_encode(const std::string& input) { + std::string output; + output.reserve(((input.length() + 2) / 3) * 4); + + int i = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + for (char c : input) { + char_array_3[i++] = c; + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (int j = 0; j < 4; j++) { + output += base64_chars[char_array_4[j]]; + } + i = 0; + } + } + + if (i > 0) { + for (int j = i; j < 3; j++) { + char_array_3[j] = '\0'; + } + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + + for (int j = 0; j < i + 1; j++) { + output += base64_chars[char_array_4[j]]; + } + + while (i++ < 3) { + output += '='; + } + } + + return output; +} + +inline std::string base64_decode(const std::string& input) { + std::string output; + output.reserve((input.length() / 4) * 3); + + int i = 0; + unsigned char char_array_4[4], char_array_3[3]; + + for (char c : input) { + if (c == '=') { + break; + } + + int val = base64_lookup[static_cast(c)]; + if (val == -1) { + continue; + } + + char_array_4[i++] = static_cast(val); + if (i == 4) { + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0x0f) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x03) << 6) + char_array_4[3]; + + for (int j = 0; j < 3; j++) { + output += char_array_3[j]; + } + i = 0; + } + } + + if (i > 0) { + for (int j = i; j < 4; j++) { + char_array_4[j] = 0; + } + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0x0f) << 4) + ((char_array_4[2] & 0x3c) >> 2); + + for (int j = 0; j < i - 1; j++) { + output += char_array_3[j]; + } + } + + return output; +} + +// URL-safe Base64 variants +inline std::string base64_url_encode(const std::string& input) { + std::string encoded = base64_encode(input); + + // Replace characters and remove padding + for (char& c : encoded) { + if (c == '+') c = '-'; + else if (c == '/') c = '_'; + } + + // Remove padding (optional, depends on your requirements) + size_t pad_pos = encoded.find('='); + if (pad_pos != std::string::npos) { + encoded.erase(pad_pos); + } + + return encoded; +} + +inline std::string base64_url_decode(const std::string& input) { + std::string normalized = input; + + for (char& c : normalized) { + if (c == '-') c = '+'; + else if (c == '_') c = '/'; + } + + size_t pad = (4 - (normalized.length() % 4)) % 4; + normalized.append(pad, '='); + + return base64_decode(normalized); +} + +inline bool base64_is_valid(const std::string& input) { + if (input.empty()) { + return true; + } + + if (input.length() % 4 != 0) { + return false; + } + + size_t padding_count = 0; + bool padding_started = false; + + for (size_t i = 0; i < input.length(); i++) { + char c = input[i]; + + if (c == '=') { + padding_started = true; + padding_count++; + if (padding_count > 2) { + return false; + } + } else { + if (padding_started) { + return false; + } + if (base64_chars.find(c) == std::string::npos) { + return false; + } + } + } + + return true; +} + +// Overloads for binary data +inline std::string base64_encode_binary(const std::vector& input) { + return base64_encode(std::string(input.begin(), input.end())); +} + +inline std::vector base64_decode_binary(const std::string& input) { + std::string decoded = base64_decode(input); + return std::vector(decoded.begin(), decoded.end()); +} \ No newline at end of file diff --git a/runtime/liblavina/core.h b/runtime/liblavina/core.h index 27c2c3a..77ea7d0 100644 --- a/runtime/liblavina/core.h +++ b/runtime/liblavina/core.h @@ -20,14 +20,21 @@ #include #include #include +#include +#include +#include +#include #if defined(__unix__) || defined(__APPLE__) #include #include #include +#include +#include #endif #if defined(__APPLE__) #include #endif #if defined(_WIN32) #include -#endif +#include +#endif \ No newline at end of file diff --git a/runtime/liblavina/crypto.h b/runtime/liblavina/crypto.h new file mode 100644 index 0000000..4c61230 --- /dev/null +++ b/runtime/liblavina/crypto.h @@ -0,0 +1,113 @@ +#pragma once +#pragma comment(lib, "crypt32.lib") +#include "core.h" + +#if defined(__unix__) || defined(__APPLE__) +inline std::string sha256(const std::string& input) { + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX sha256; + + SHA256_Init(&sha256); + SHA256_Update(&sha256, input.c_str(), input.length()); + SHA256_Final(hash, &sha256); + + std::stringstream ss; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; + } + return ss.str(); +} + +inline std::string md5(const std::string& input) { + unsigned char hash[MD5_DIGEST_LENGTH]; + MD5_CTX md5; + + MD5_Init(&md5); + MD5_Update(&md5, input.c_str(), input.length()); + MD5_Final(hash, &md5); + + std::stringstream ss; + for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { + ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; + } + return ss.str(); +} +#endif + +#ifdef _WIN32 +inline std::string sha256(const std::string& input) { + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + BYTE hash[32]; + DWORD hashLen = 32; + std::stringstream ss; + + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) { + return "Failed to acquire crypto context."; + } + + if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash)) { + CryptReleaseContext(hProv, 0); + return "Failed to create hash object."; + } + + if (!CryptHashData(hHash, (BYTE*)input.c_str(), input.length(), 0)) { + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return "Failed to hash data."; + } + + if (!CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0)) { + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return "Failed to get hash."; + } + + for (DWORD i = 0; i < hashLen; i++) { + ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; + } + + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + + return ss.str(); +} + +inline std::string md5(const std::string& input) { + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + BYTE hash[16]; + DWORD hashLen = 16; + std::stringstream ss; + + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + return "Failed to acquire crypto context."; + } + + if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) { + CryptReleaseContext(hProv, 0); + return "Failed to create hash object."; + } + + if (!CryptHashData(hHash, (BYTE*)input.c_str(), input.length(), 0)) { + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return "Failed to hash data."; + } + + if (!CryptGetHashParam(hHash, HP_HASHVAL, hash, &hashLen, 0)) { + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + return "Failed to get hash."; + } + + for (DWORD i = 0; i < hashLen; i++) { + ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; + } + + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + + return ss.str(); +} +#endif \ No newline at end of file diff --git a/runtime/liblavina/string.h b/runtime/liblavina/string.h index 3da97e2..1ada331 100644 --- a/runtime/liblavina/string.h +++ b/runtime/liblavina/string.h @@ -28,6 +28,22 @@ inline std::string lv_replace(const std::string& s, const std::string& from, con return result; } +inline int lv_compare(const std::string& a, const std::string& b) { + size_t i = 0; + while (i < a.length() && i < b.length() && a[i] == b[i]) { + i++; + } + + if (i == a.length() && i == b.length()) { + return 0; + } + return 1; +} + +inline std::string lv_reverse(const std::string& s) { + return {s.rbegin(), s.rend()}; +} + inline std::vector lv_split(const std::string& s, const std::string& delim) { std::vector result; size_t start = 0, end; diff --git a/runtime/liblavina/time.h b/runtime/liblavina/time.h new file mode 100644 index 0000000..35b76a1 --- /dev/null +++ b/runtime/liblavina/time.h @@ -0,0 +1,455 @@ +#pragma once +#include "core.h" + +namespace TimeUtils { + struct DateTime { + int year; + int month; // 1-12 + int day; // 1-31 + int hour; // 0-23 + int minute; // 0-59 + int second; // 0-59 + int millisecond; // 0-999 + int microsecond; // 0-999 + int dayOfWeek; // 0-6 (Sunday = 0) + int dayOfYear; // 1-366 + + DateTime() : year(0), month(0), day(0), hour(0), minute(0), second(0), + millisecond(0), microsecond(0), dayOfWeek(0), dayOfYear(0) {} + }; + + struct Duration { + long long milliseconds; + + Duration() : milliseconds(0) {} + explicit Duration(long long ms) : milliseconds(ms) {} + + long long days() const { return milliseconds / (1000LL * 60 * 60 * 24); } + long long hours() const { return milliseconds / (1000LL * 60 * 60); } + long long minutes() const { return milliseconds / (1000LL * 60); } + long long seconds() const { return milliseconds / 1000LL; } + + double totalDays() const { return milliseconds / (1000.0 * 60 * 60 * 24); } + double totalHours() const { return milliseconds / (1000.0 * 60 * 60); } + double totalMinutes() const { return milliseconds / (1000.0 * 60); } + double totalSeconds() const { return milliseconds / 1000.0; } + }; + + // High-resolution timer + + class Timer { + private: + std::chrono::high_resolution_clock::time_point start_time; + bool running; + long long elapsed_ns; + + public: + Timer() : running(false), elapsed_ns(0) {} + + void start() { + start_time = std::chrono::high_resolution_clock::now(); + running = true; + elapsed_ns = 0; + } + + void stop() { + if (running) { + auto end_time = std::chrono::high_resolution_clock::now(); + elapsed_ns = std::chrono::duration_cast( + end_time - start_time).count(); + running = false; + } + } + + void reset() { + start_time = std::chrono::high_resolution_clock::now(); + elapsed_ns = 0; + running = false; + } + + long long elapsedNanoseconds() const { + if (running) { + auto current = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast( + current - start_time).count(); + } + return elapsed_ns; + } + + long long elapsedMicroseconds() const { return elapsedNanoseconds() / 1000; } + long long elapsedMilliseconds() const { return elapsedNanoseconds() / 1000000; } + double elapsedSeconds() const { return elapsedNanoseconds() / 1000000000.0; } + + bool isRunning() const { return running; } + }; + + // System time functions + + // Get current Unix timestamp in seconds + inline long long getCurrentTimestamp() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + } + + // Get current timestamp in milliseconds + inline long long getCurrentTimestampMs() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + } + + // Get current timestamp in microseconds + inline long long getCurrentTimestampUs() { + return std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + } + + // Get system uptime in milliseconds + inline long long getSystemUptime() { + #ifdef _WIN32 + return GetTickCount64(); + #else + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { + return static_cast(ts.tv_sec) * 1000LL + ts.tv_nsec / 1000000LL; + } + return 0; + #endif + } + + // Get high-resolution performance counter + inline long long getPerformanceCounter() { + #ifdef _WIN32 + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return counter.QuadPart; + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return static_cast(ts.tv_sec) * 1000000000LL + ts.tv_nsec; + #endif + } + + // Get performance counter frequency (ticks per second) + inline long long getPerformanceFrequency() { + #ifdef _WIN32 + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + return frequency.QuadPart; + #else + return 1000000000LL; // nanoseconds + #endif + } + + // DateTime conversion functions + + // Convert Unix timestamp to DateTime structure + inline DateTime timestampToDateTime(long long timestamp, bool localTime = true) { + DateTime dt; + time_t time = static_cast(timestamp); + + #ifdef _WIN32 + struct tm timeinfo; + if (localTime) { + localtime_s(&timeinfo, &time); + } else { + gmtime_s(&timeinfo, &time); + } + #else + struct tm timeinfo; + if (localTime) { + localtime_r(&time, &timeinfo); + } else { + gmtime_r(&time, &timeinfo); + } + #endif + + dt.year = timeinfo.tm_year + 1900; + dt.month = timeinfo.tm_mon + 1; + dt.day = timeinfo.tm_mday; + dt.hour = timeinfo.tm_hour; + dt.minute = timeinfo.tm_min; + dt.second = timeinfo.tm_sec; + dt.dayOfWeek = timeinfo.tm_wday; + dt.dayOfYear = timeinfo.tm_yday + 1; + + // Add milliseconds/microseconds if available + long long ms = timestamp % 1000; + if (ms < 0) ms += 1000; + dt.millisecond = static_cast(ms); + dt.microsecond = 0; + + return dt; + } + + // Get current DateTime + inline DateTime getCurrentDateTime(bool localTime = true) { + return timestampToDateTime(getCurrentTimestamp(), localTime); + } + + // Convert DateTime to Unix timestamp + inline long long dateTimeToTimestamp(const DateTime& dt) { + struct tm timeinfo = {}; + timeinfo.tm_year = dt.year - 1900; + timeinfo.tm_mon = dt.month - 1; + timeinfo.tm_mday = dt.day; + timeinfo.tm_hour = dt.hour; + timeinfo.tm_min = dt.minute; + timeinfo.tm_sec = dt.second; + timeinfo.tm_isdst = -1; + + return static_cast(mktime(&timeinfo)); + } + + // Formatting functions + + // Format DateTime as string + inline std::string formatDateTime(const DateTime& dt, const std::string& format = "%Y-%m-%d %H:%M:%S") { + struct tm timeinfo = {}; + timeinfo.tm_year = dt.year - 1900; + timeinfo.tm_mon = dt.month - 1; + timeinfo.tm_mday = dt.day; + timeinfo.tm_hour = dt.hour; + timeinfo.tm_min = dt.minute; + timeinfo.tm_sec = dt.second; + timeinfo.tm_wday = dt.dayOfWeek; + timeinfo.tm_yday = dt.dayOfYear - 1; + + char buffer[256]; + strftime(buffer, sizeof(buffer), format.c_str(), &timeinfo); + + std::string result(buffer); + + // Handle milliseconds placeholder {ms} + size_t ms_pos = result.find("{ms}"); + if (ms_pos != std::string::npos) { + std::ostringstream oss; + oss << std::setw(3) << std::setfill('0') << dt.millisecond; + result.replace(ms_pos, 4, oss.str()); + } + + return result; + } + + // Format current time as string + inline std::string formatCurrentTime(const std::string& format = "%Y-%m-%d %H:%M:%S", bool localTime = true) { + return formatDateTime(getCurrentDateTime(localTime), format); + } + + // Format timestamp as ISO 8601 string + inline std::string toISO8601(long long timestamp, bool localTime = true) { + DateTime dt = timestampToDateTime(timestamp, localTime); + std::ostringstream oss; + oss << std::setfill('0') + << std::setw(4) << dt.year << "-" + << std::setw(2) << dt.month << "-" + << std::setw(2) << dt.day << "T" + << std::setw(2) << dt.hour << ":" + << std::setw(2) << dt.minute << ":" + << std::setw(2) << dt.second; + + if (!localTime) { + oss << "Z"; + } + + return oss.str(); + } + + // Parse ISO 8601 string to timestamp (basic) + inline long long fromISO8601(const std::string& iso) { + DateTime dt; + char delimiter; + std::istringstream iss(iso); + + iss >> dt.year >> delimiter >> dt.month >> delimiter >> dt.day + >> delimiter >> dt.hour >> delimiter >> dt.minute >> delimiter >> dt.second; + + if (iss.fail()) { + return 0; + } + + return dateTimeToTimestamp(dt); + } + + // Sleep functions + + // Sleep for specified milliseconds + inline void sleepMs(long long milliseconds) { + #ifdef _WIN32 + Sleep(static_cast(milliseconds)); + #else + usleep(static_cast(milliseconds * 1000)); + #endif + } + + // Sleep for specified microseconds + inline void sleepUs(long long microseconds) { + #ifdef _WIN32 + // Windows Sleep has millisecond precision, so we use busy-wait for sub-millisecond + if (microseconds < 1000) { + auto start = std::chrono::high_resolution_clock::now(); + while (true) { + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(now - start).count(); + if (elapsed >= microseconds) break; + } + } else { + Sleep(static_cast(microseconds / 1000)); + } + #else + usleep(static_cast(microseconds)); + #endif + } + + // Sleep for specified seconds + inline void sleepSeconds(double seconds) { + sleepMs(static_cast(seconds * 1000)); + } + + // Utility functions + + // Calculate difference between two timestamps in milliseconds + inline long long timeDifference(long long timestamp1, long long timestamp2) { + return timestamp1 - timestamp2; + } + + // Check if year is leap year + inline bool isLeapYear(int year) { + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + } + + // Get number of days in month + inline int getDaysInMonth(int year, int month) { + static const int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if (month < 1 || month > 12) return 0; + if (month == 2 && isLeapYear(year)) return 29; + return days[month - 1]; + } + + // Add duration to DateTime + inline DateTime addDuration(const DateTime& dt, const Duration& duration) { + long long timestamp = dateTimeToTimestamp(dt); + timestamp += duration.seconds(); + return timestampToDateTime(timestamp, true); + } + + // Calculate duration between two DateTimes + inline Duration durationBetween(const DateTime& dt1, const DateTime& dt2) { + long long ts1 = dateTimeToTimestamp(dt1); + long long ts2 = dateTimeToTimestamp(dt2); + return Duration((ts1 - ts2) * 1000); + } + + // Timezone functions + + // Get timezone offset in seconds (local time - UTC) + inline long long getTimezoneOffset() { + time_t now = time(nullptr); + struct tm local_tm, utc_tm; + + #ifdef _WIN32 + localtime_s(&local_tm, &now); + gmtime_s(&utc_tm, &now); + #else + localtime_r(&now, &local_tm); + gmtime_r(&now, &utc_tm); + #endif + + // Convert both to timestamps and calculate difference + time_t local_time = mktime(&local_tm); + time_t utc_time = mktime(&utc_tm); + + return static_cast(difftime(local_time, utc_time)); + } + + // Convert local time to UTC + inline DateTime localToUTC(const DateTime& local) { + long long offset = getTimezoneOffset(); + long long timestamp = dateTimeToTimestamp(local) - offset; + return timestampToDateTime(timestamp, false); + } + + // Convert UTC to local time + inline DateTime utcToLocal(const DateTime& utc) { + long long offset = getTimezoneOffset(); + long long timestamp = dateTimeToTimestamp(utc) + offset; + return timestampToDateTime(timestamp, true); + } + + // Stopwatch class for easy time measurement + + class Stopwatch { + private: + Timer timer; + std::vector laps; + + public: + void start() { + timer.start(); + laps.clear(); + } + + void stop() { + timer.stop(); + } + + void reset() { + timer.reset(); + laps.clear(); + } + + void lap() { + laps.push_back(timer.elapsedMilliseconds()); + } + + long long elapsed() const { + return timer.elapsedMilliseconds(); + } + + const std::vector& getLaps() const { + return laps; + } + + size_t lapCount() const { + return laps.size(); + } + + long long getLap(size_t index) const { + if (index < laps.size()) { + return laps[index]; + } + return 0; + } + }; + + // Benchmark + + template + inline double benchmark(Func&& func, int iterations = 1) { + Timer timer; + timer.start(); + + for (int i = 0; i < iterations; ++i) { + func(); + } + + timer.stop(); + return timer.elapsedSeconds() / iterations; + } + + // Date/Time validation + + inline bool isValidDateTime(const DateTime& dt) { + if (dt.year < 1900 || dt.year > 3000) return false; + if (dt.month < 1 || dt.month > 12) return false; + if (dt.day < 1 || dt.day > getDaysInMonth(dt.year, dt.month)) return false; + if (dt.hour < 0 || dt.hour > 23) return false; + if (dt.minute < 0 || dt.minute > 59) return false; + if (dt.second < 0 || dt.second > 59) return false; + if (dt.millisecond < 0 || dt.millisecond > 999) return false; + if (dt.microsecond < 0 || dt.microsecond > 999) return false; + return true; + } +} // namespace TimeUtils \ No newline at end of file From 622ca92d8e90c839f85255ab81d35d575debd848 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Tue, 17 Feb 2026 20:41:28 +0300 Subject: [PATCH 2/9] ci: dont install g++14 since its already available --- .github/workflows/ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c4cca7..e385146 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,12 +19,9 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Install g++ (Linux) + - name: Setup g++ (Linux) if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y g++-14 - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 + run: sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 - name: Bootstrap run: make bootstrap From 2ceb217215e6ff84b961e92f1c45025372c059a1 Mon Sep 17 00:00:00 2001 From: generin0 Date: Sat, 21 Feb 2026 04:55:13 +0200 Subject: [PATCH 3/9] foxed md5 implementation, removed unnecessary func tions, and added inline keyword to improve performance. --- runtime/liblavina/crypto.h | 365 +++++++++++++++++++++++++++++++++++-- 1 file changed, 351 insertions(+), 14 deletions(-) diff --git a/runtime/liblavina/crypto.h b/runtime/liblavina/crypto.h index 4c61230..4657cb0 100644 --- a/runtime/liblavina/crypto.h +++ b/runtime/liblavina/crypto.h @@ -1,37 +1,374 @@ #pragma once #pragma comment(lib, "crypt32.lib") + #include "core.h" #if defined(__unix__) || defined(__APPLE__) -inline std::string sha256(const std::string& input) { - unsigned char hash[SHA256_DIGEST_LENGTH]; - SHA256_CTX sha256; +#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define SHR(x, n) ((x) >> (n)) + +#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +#define SIGMA0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define SIGMA1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define GAMMA0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define GAMMA1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +static const uint32_t SHA256_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static const uint32_t SHA256_H0[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +typedef struct { + uint8_t data[64]; + uint32_t datalen; + uint64_t bitlen; + uint32_t state[8]; +} SHA256_CTX; + +static inline void uint32_to_be(uint32_t value, uint8_t* bytes) { + bytes[0] = (value >> 24) & 0xFF; + bytes[1] = (value >> 16) & 0xFF; + bytes[2] = (value >> 8) & 0xFF; + bytes[3] = value & 0xFF; +} + +static inline void uint64_to_be(uint64_t value, uint8_t* bytes) { + for (int i = 7; i >= 0; i--) { + bytes[i] = value & 0xFF; + value >>= 8; + } +} + +// Initialization: ctx->datalen = 0; ctx->bitlen = 0; +// memcpy(ctx->state, SHA256_H0, sizeof(ctx->state)); +inline void sha256_init(SHA256_CTX* ctx) { + ctx->datalen = 0; + ctx->bitlen = 0; + memcpy(ctx->state, SHA256_H0, sizeof(ctx->state)); +} + +static inline void sha256_transform(SHA256_CTX* ctx, const uint8_t data[]) { + uint32_t w[64]; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2; - SHA256_Init(&sha256); - SHA256_Update(&sha256, input.c_str(), input.length()); - SHA256_Final(hash, &sha256); + for (int i = 0; i < 16; i++) { + w[i] = (data[i*4] << 24) | (data[i*4+1] << 16) | + (data[i*4+2] << 8) | (data[i*4+3]); + } + + for (int i = 16; i < 64; i++) { + w[i] = GAMMA1(w[i-2]) + w[i-7] + GAMMA0(w[i-15]) + w[i-16]; + } + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (int i = 0; i < 64; i++) { + t1 = h + SIGMA1(e) + CH(e, f, g) + SHA256_K[i] + w[i]; + t2 = SIGMA0(a) + MAJ(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +// ctx: SHA256_CTX, data: input data, len: length of input data +inline void sha256_update(SHA256_CTX* ctx, const uint8_t data[], size_t len) { + for (size_t i = 0; i < len; i++) { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + + if (ctx->datalen == 64) { + sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +// ctx: SHA256_CTX, hash: output buffer (32 bytes) +inline void sha256_final(SHA256_CTX* ctx, uint8_t hash[]) { + size_t i = ctx->datalen; + + if (ctx->datalen < 56) { + ctx->data[i++] = 0x80; + + while (i < 56) { + ctx->data[i++] = 0x00; + } + } else { + ctx->data[i++] = 0x80; + + while (i < 64) { + ctx->data[i++] = 0x00; + } + + sha256_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + ctx->bitlen += ctx->datalen * 8; + uint64_to_be(ctx->bitlen, ctx->data + 56); + + sha256_transform(ctx, ctx->data); + + for (i = 0; i < 8; i++) { + uint32_to_be(ctx->state[i], hash + i * 4); + } +} + +inline std::string sha256(const std::string& input) { + SHA256_CTX ctx; + uint8_t hash[32]; std::stringstream ss; - for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + + sha256_init(&ctx); + sha256_update(&ctx, (uint8_t*)input.c_str(), input.length()); + sha256_final(&ctx, hash); + + for (int i = 0; i < 32; i++) { ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; } + return ss.str(); } -inline std::string md5(const std::string& input) { - unsigned char hash[MD5_DIGEST_LENGTH]; - MD5_CTX md5; +static std::string sha256_from_cstr(const char* input) { + return sha256(std::string(input)); +} + +/* -------------------------MD5--------------------------- */ + +static const uint8_t MD5_S[64] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 +}; + +static const uint32_t MD5_K[64] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +}; + +static const uint32_t MD5_H0[4] = { + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 +}; + +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F((b), (c), (d)) + (x) + (ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ +} +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G((b), (c), (d)) + (x) + (ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ +} +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H((b), (c), (d)) + (x) + (ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ +} +#define II(a, b, c, d, x, s, ac) { \ + (a) += I((b), (c), (d)) + (x) + (ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ +} + +typedef struct { + uint8_t data[64]; + uint32_t datalen; + uint64_t bitlen; + uint32_t state[4]; +} MD5_CTX; + +static inline void uint32_to_le(uint32_t value, uint8_t* bytes) { + bytes[0] = value & 0xFF; + bytes[1] = (value >> 8) & 0xFF; + bytes[2] = (value >> 16) & 0xFF; + bytes[3] = (value >> 24) & 0xFF; +} + +static inline void uint64_to_le(uint64_t value, uint8_t* bytes) { + for (int i = 0; i < 8; i++) { + bytes[i] = value & 0xFF; + value >>= 8; + } +} + +inline void md5_init(MD5_CTX* ctx) { + ctx->datalen = 0; + ctx->bitlen = 0; + memcpy(ctx->state, MD5_H0, sizeof(ctx->state)); +} + +static void md5_transform(MD5_CTX* ctx, const uint8_t data[]) { + uint32_t a, b, c, d, i; + uint32_t x[16]; + + for (i = 0; i < 16; i++) { + x[i] = data[i*4] | (data[i*4+1] << 8) | + (data[i*4+2] << 16) | (data[i*4+3] << 24); + } + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + + for (i = 0; i < 16; i++) { + FF(a, b, c, d, x[i], MD5_S[i], MD5_K[i]); + uint32_t temp = d; d = c; c = b; b = a; a = temp; + } + + for (i = 16; i < 32; i++) { + GG(a, b, c, d, x[(5*i + 1) % 16], MD5_S[i], MD5_K[i]); + uint32_t temp = d; d = c; c = b; b = a; a = temp; + } - MD5_Init(&md5); - MD5_Update(&md5, input.c_str(), input.length()); - MD5_Final(hash, &md5); + for (i = 32; i < 48; i++) { + HH(a, b, c, d, x[(3*i + 5) % 16], MD5_S[i], MD5_K[i]); + uint32_t temp = d; d = c; c = b; b = a; a = temp; + } + + for (i = 48; i < 64; i++) { + II(a, b, c, d, x[(7*i) % 16], MD5_S[i], MD5_K[i]); + uint32_t temp = d; d = c; c = b; b = a; a = temp; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; +} + +inline void md5_update(MD5_CTX* ctx, const uint8_t data[], size_t len) { + for (size_t i = 0; i < len; i++) { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + + if (ctx->datalen == 64) { + md5_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +inline void md5_final(MD5_CTX* ctx, uint8_t hash[]) { + size_t i = ctx->datalen; + + if (ctx->datalen < 56) { + ctx->data[i++] = 0x80; + + while (i < 56) { + ctx->data[i++] = 0x00; + } + } else { + ctx->data[i++] = 0x80; + + while (i < 64) { + ctx->data[i++] = 0x00; + } + + md5_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + ctx->bitlen += ctx->datalen * 8; + uint64_to_le(ctx->bitlen, ctx->data + 56); + + md5_transform(ctx, ctx->data); + + for (i = 0; i < 4; i++) { + uint32_to_le(ctx->state[i], hash + i * 4); + } +} + +inline std::string md5(const std::string& input) { + MD5_CTX ctx; + uint8_t hash[16]; std::stringstream ss; - for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { + + md5_init(&ctx); + md5_update(&ctx, (uint8_t*)input.c_str(), input.length()); + md5_final(&ctx, hash); + + for (int i = 0; i < 16; i++) { ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i]; } + return ss.str(); } + +static int is_little_endian() { + uint32_t test = 1; + return *(uint8_t*)&test == 1; +} #endif #ifdef _WIN32 From cde386fa92fa037e628b368bc4b09192a4ed6765 Mon Sep 17 00:00:00 2001 From: generin0 Date: Sat, 21 Feb 2026 05:24:19 +0200 Subject: [PATCH 4/9] added random.h and included it in lavina.h, also added platform-specific defines for random number generation in core.h --- runtime/lavina.h | 1 + runtime/liblavina/core.h | 6 +- runtime/liblavina/random.h | 430 +++++++++++++++++++++++++++++++++++++ 3 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 runtime/liblavina/random.h diff --git a/runtime/lavina.h b/runtime/lavina.h index 9ab415e..c133cd4 100644 --- a/runtime/lavina.h +++ b/runtime/lavina.h @@ -14,3 +14,4 @@ #include "liblavina/os.h" #include "liblavina/util.h" #include "liblavina/math.h" +#include "liblavina/random.h" \ No newline at end of file diff --git a/runtime/liblavina/core.h b/runtime/liblavina/core.h index 77ea7d0..5b5349b 100644 --- a/runtime/liblavina/core.h +++ b/runtime/liblavina/core.h @@ -25,16 +25,18 @@ #include #include #if defined(__unix__) || defined(__APPLE__) +#define RAND_PLATFORM_UNIX #include #include #include -#include -#include #endif #if defined(__APPLE__) +#define RAND_PLATFORM_MACOS #include #endif #if defined(_WIN32) +#define RAND_PLATFORM_WINDOWS #include +#include #include #endif \ No newline at end of file diff --git a/runtime/liblavina/random.h b/runtime/liblavina/random.h new file mode 100644 index 0000000..162909b --- /dev/null +++ b/runtime/liblavina/random.h @@ -0,0 +1,430 @@ +#ifndef LAVINA_RANDOM_H +#define LAVINA_RANDOM_H + +#include "core.h" + +#pragma comment(lib, "bcrypt.lib") + +#ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// PRNG state structure (xoshiro256**) +typedef struct RandPRNG { + uint64_t state[4]; +} RandPRNG; + +// UUID structure +typedef struct RandUUID { + uint8_t bytes[16]; +} RandUUID; + +static inline uint64_t _rand_prng_rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +static inline uint64_t _rand_prng_next(RandPRNG *rng) { + const uint64_t result = _rand_prng_rotl(rng->state[1] * 5, 7) * 9; + const uint64_t t = rng->state[1] << 17; + + rng->state[2] ^= rng->state[0]; + rng->state[3] ^= rng->state[1]; + rng->state[1] ^= rng->state[2]; + rng->state[0] ^= rng->state[3]; + + rng->state[2] ^= t; + rng->state[3] = _rand_prng_rotl(rng->state[3], 45); + + return result; +} + +// SplitMix64 for seeding +static inline uint64_t _rand_splitmix64(uint64_t *state) { + uint64_t z = (*state += 0x9e3779b97f4a7c15ULL); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; + z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; + return z ^ (z >> 31); +} + +// Global PRNG instance and auto-initialization flag +static RandPRNG _rand_global_prng = {{0, 0, 0, 0}}; +static bool _rand_global_prng_initialized = false; + +// Seed the global PRNG +inline void rand_prng_seed(uint64_t seed) { + uint64_t smstate = seed; + _rand_global_prng.state[0] = _rand_splitmix64(&smstate); + _rand_global_prng.state[1] = _rand_splitmix64(&smstate); + _rand_global_prng.state[2] = _rand_splitmix64(&smstate); + _rand_global_prng.state[3] = _rand_splitmix64(&smstate); + _rand_global_prng_initialized = true; +} + +// Auto-initialize global PRNG with time-based seed +static inline void _rand_prng_auto_init(void) { + if (_rand_global_prng_initialized) return; + + uint64_t seed = (uint64_t)time(NULL); + seed ^= (uint64_t)(uintptr_t)&seed; + + #ifdef RAND_PLATFORM_WINDOWS + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + seed ^= (uint64_t)counter.QuadPart; + #else + struct timespec ts; + #ifdef CLOCK_MONOTONIC + clock_gettime(CLOCK_MONOTONIC, &ts); + seed ^= (uint64_t)ts.tv_nsec; + #endif + #endif + + rand_prng_seed(seed); +} + +// Generate pseudo-random uint32_t +inline uint32_t rand_prng_u32(void) { + _rand_prng_auto_init(); + return (uint32_t)(_rand_prng_next(&_rand_global_prng) >> 32); +} + +// Generate pseudo-random uint64_t +inline uint64_t rand_prng_u64(void) { + _rand_prng_auto_init(); + return _rand_prng_next(&_rand_global_prng); +} + +// Generate pseudo-random int in range [min, max] (inclusive) +inline int rand_prng_range(int min, int max) { + if (min > max) { + int tmp = min; + min = max; + max = tmp; + } + if (min == max) return min; + + uint32_t range = (uint32_t)(max - min + 1); + uint32_t limit = UINT32_MAX - (UINT32_MAX % range); + uint32_t value; + + do { + value = rand_prng_u32(); + } while (value >= limit); + + return min + (int)(value % range); +} + +// Generate pseudo-random double in range [0.0, 1.0) +inline double rand_prng_double(void) { + _rand_prng_auto_init(); + uint64_t x = _rand_prng_next(&_rand_global_prng) >> 11; + return (double)x / (double)(1ULL << 53); +} + +// Generate pseudo-random float in range [0.0f, 1.0f) +inline float rand_prng_float(void) { + uint32_t x = rand_prng_u32() >> 8; + return (float)x / (float)(1U << 24); +} + +// Generate pseudo-random bool (50% probability) +inline bool rand_prng_bool(void) { + return (rand_prng_u32() & 1) != 0; +} + +// Fill buffer with pseudo-random bytes +inline void rand_prng_bytes(void *buf, size_t len) { + if (!buf || len == 0) return; + + _rand_prng_auto_init(); + uint8_t *bytes = (uint8_t *)buf; + size_t i = 0; + + while (i + 8 <= len) { + uint64_t val = _rand_prng_next(&_rand_global_prng); + memcpy(bytes + i, &val, 8); + i += 8; + } + + if (i < len) { + uint64_t val = _rand_prng_next(&_rand_global_prng); + memcpy(bytes + i, &val, len - i); + } +} + +// Create new PRNG instance with seed +inline RandPRNG* rand_prng_create(uint64_t seed) { + RandPRNG *rng = (RandPRNG *)malloc(sizeof(RandPRNG)); + if (!rng) return NULL; + + uint64_t smstate = seed; + rng->state[0] = _rand_splitmix64(&smstate); + rng->state[1] = _rand_splitmix64(&smstate); + rng->state[2] = _rand_splitmix64(&smstate); + rng->state[3] = _rand_splitmix64(&smstate); + + return rng; +} + +// Destroy PRNG instance +inline void rand_prng_destroy(RandPRNG *rng) { + if (rng) { + memset(rng, 0, sizeof(RandPRNG)); + free(rng); + } +} + +// Generate uint32_t from specific PRNG instance +inline uint32_t rand_prng_next_u32(RandPRNG *rng) { + if (!rng) return 0; + return (uint32_t)(_rand_prng_next(rng) >> 32); +} + +// Generate uint64_t from specific PRNG instance +inline uint64_t rand_prng_next_u64(RandPRNG *rng) { + if (!rng) return 0; + return _rand_prng_next(rng); +} + +// Generate int in range [min, max] from specific PRNG instance +inline int rand_prng_next_range(RandPRNG *rng, int min, int max) { + if (!rng) return min; + if (min > max) { + int tmp = min; + min = max; + max = tmp; + } + if (min == max) return min; + + uint32_t range = (uint32_t)(max - min + 1); + uint32_t limit = UINT32_MAX - (UINT32_MAX % range); + uint32_t value; + + do { + value = rand_prng_next_u32(rng); + } while (value >= limit); + + return min + (int)(value % range); +} + +// Generate double [0.0, 1.0) from specific PRNG instance +inline double rand_prng_next_double(RandPRNG *rng) { + if (!rng) return 0.0; + uint64_t x = _rand_prng_next(rng) >> 11; + return (double)x / (double)(1ULL << 53); +} + +// Fill buffer from specific PRNG instance +inline void rand_prng_next_bytes(RandPRNG *rng, void *buf, size_t len) { + if (!rng || !buf || len == 0) return; + + uint8_t *bytes = (uint8_t *)buf; + size_t i = 0; + + while (i + 8 <= len) { + uint64_t val = _rand_prng_next(rng); + memcpy(bytes + i, &val, 8); + i += 8; + } + + if (i < len) { + uint64_t val = _rand_prng_next(rng); + memcpy(bytes + i, &val, len - i); + } +} + +// Fill buffer with true random bytes from OS +inline bool rand_trng_bytes(void *buf, size_t len) { + if (!buf || len == 0) return false; + +#ifdef RAND_PLATFORM_WINDOWS + // Windows: BCryptGenRandom (Vista+) + NTSTATUS status = BCryptGenRandom( + NULL, + (PUCHAR)buf, + (ULONG)len, + BCRYPT_USE_SYSTEM_PREFERRED_RNG + ); + return status == 0; + +#elif defined(RAND_PLATFORM_MACOS) + // macOS: arc4random_buf (never fails) + arc4random_buf(buf, len); + return true; + +#elif defined(RAND_PLATFORM_LINUX) || defined(RAND_PLATFORM_UNIX) + // Linux/Unix: /dev/urandom + int fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) return false; + + size_t total = 0; + while (total < len) { + ssize_t n = read(fd, (uint8_t *)buf + total, len - total); + if (n <= 0) { + close(fd); + return false; + } + total += (size_t)n; + } + + close(fd); + return true; + +#else + return false; +#endif +} + +// Generate true random uint32_t +inline uint32_t rand_trng_u32(void) { + uint32_t val = 0; + rand_trng_bytes(&val, sizeof(val)); + return val; +} + +// Generate true random uint64_t +inline uint64_t rand_trng_u64(void) { + uint64_t val = 0; + rand_trng_bytes(&val, sizeof(val)); + return val; +} + +// Generate true random int in range [min, max] +inline int rand_trng_range(int min, int max) { + if (min > max) { + int tmp = min; + min = max; + max = tmp; + } + if (min == max) return min; + + uint32_t range = (uint32_t)(max - min + 1); + uint32_t limit = UINT32_MAX - (UINT32_MAX % range); + uint32_t value; + + do { + value = rand_trng_u32(); + } while (value >= limit); + + return min + (int)(value % range); +} + +// Generate true random double [0.0, 1.0) +inline double rand_trng_double(void) { + uint64_t x = rand_trng_u64() >> 11; + return (double)x / (double)(1ULL << 53); +} + +// Fisher-Yates shuffle +inline void rand_prng_shuffle(void *arr, size_t count, size_t elem_size) { + if (!arr || count <= 1 || elem_size == 0) return; + + _rand_prng_auto_init(); + + uint8_t *array = (uint8_t *)arr; + uint8_t *temp = (uint8_t *)malloc(elem_size); + if (!temp) return; + + for (size_t i = count - 1; i > 0; i--) { + size_t j = (size_t)rand_prng_range(0, (int)i); + + memcpy(temp, array + i * elem_size, elem_size); + memcpy(array + i * elem_size, array + j * elem_size, elem_size); + memcpy(array + j * elem_size, temp, elem_size); + } + + free(temp); +} + +// Choose random element from array +inline void* rand_prng_choice(void *arr, size_t count, size_t elem_size) { + if (!arr || count == 0 || elem_size == 0) return NULL; + + size_t idx = (size_t)rand_prng_range(0, (int)(count - 1)); + return (uint8_t *)arr + idx * elem_size; +} + +// Coin flip with probability [0.0, 1.0] +inline bool rand_prng_chance(double probability) { + if (probability <= 0.0) return false; + if (probability >= 1.0) return true; + return rand_prng_double() < probability; +} + +// Normal distribution (Box-Muller transform) +inline double rand_prng_normal(double mean, double stddev) { + double u1 = rand_prng_double(); + double u2 = rand_prng_double(); + + if (u1 < 1e-10) u1 = 1e-10; + + double z0 = sqrt(-2.0 * log(u1)) * cos(2.0 * 3.14159265358979323846 * u2); + return mean + z0 * stddev; +} + +// Exponential distribution +inline double rand_prng_exponential(double lambda) { + if (lambda <= 0.0) return 0.0; + double u = rand_prng_double(); + if (u < 1e-10) u = 1e-10; + return -log(u) / lambda; +} + +// Generate UUID v4 using TRNG +inline RandUUID rand_trng_uuid(void) { + RandUUID uuid; + rand_trng_bytes(uuid.bytes, 16); + + uuid.bytes[6] = (uuid.bytes[6] & 0x0F) | 0x40; + uuid.bytes[8] = (uuid.bytes[8] & 0x3F) | 0x80; + + return uuid; +} + +// Convert UUID to string "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +inline void rand_uuid_to_string(RandUUID uuid, char *out) { + static const char hex[] = "0123456789abcdef"; + int pos = 0; + + for (int i = 0; i < 16; i++) { + if (i == 4 || i == 6 || i == 8 || i == 10) { + out[pos++] = '-'; + } + out[pos++] = hex[(uuid.bytes[i] >> 4) & 0xF]; + out[pos++] = hex[uuid.bytes[i] & 0xF]; + } + out[pos] = '\0'; +} + +// ============================================================================ +// Convenience Aliases (shorter names) +// ============================================================================ + +#define rand_seed(s) rand_prng_seed(s) +#define rand_u32() rand_prng_u32() +#define rand_u64() rand_prng_u64() +#define rand_int(a, b) rand_prng_range(a, b) +#define rand_range(a, b) rand_prng_range(a, b) +#define rand_double() rand_prng_double() +#define rand_float() rand_prng_float() +#define rand_bool() rand_prng_bool() +#define rand_bytes(b, l) rand_prng_bytes(b, l) +#define rand_shuffle(a, c, s) rand_prng_shuffle(a, c, s) +#define rand_choice(a, c, s) rand_prng_choice(a, c, s) +#define rand_chance(p) rand_prng_chance(p) +#define rand_normal(m, s) rand_prng_normal(m, s) + +#define rand_secure_bytes(b, l) rand_trng_bytes(b, l) +#define rand_secure_u32() rand_trng_u32() +#define rand_secure_u64() rand_trng_u64() +#define rand_uuid() rand_trng_uuid() + +#ifdef __cplusplus +} +#endif + +#endif // LAVINA_RANDOM_H From 4259baea330f43b99075ccd4ca13b803a39df6c5 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Wed, 11 Mar 2026 14:31:55 +0300 Subject: [PATCH 5/9] feat: new runtime headers, updated std, commit from Generin0 --- .github/workflows/ci.yml | 9 +- DOCUMENTATION.md | 109 +++++++++++++++++++ README.md | 18 +++- runtime/liblavina/base64.h | 41 +++++++- runtime/liblavina/crypto.h | 16 ++- runtime/liblavina/random.h | 87 ++++++++++++++- runtime/liblavina/time.h | 209 +++++++++++++++++++++++++++++++------ runtime/std/base64.lv | 22 ++++ runtime/std/chrono.lv | 147 ++++++++++++++++++++++++++ runtime/std/crypto.lv | 7 ++ runtime/std/rng.lv | 49 +++++++++ tests/test_std_base64.lv | 20 ++++ tests/test_std_crypto.lv | 6 ++ tests/test_std_net.lv | 75 +++++++++---- tests/test_std_random.lv | 39 +++++++ tests/test_std_time.lv | 47 +++++++++ 16 files changed, 831 insertions(+), 70 deletions(-) create mode 100644 runtime/std/base64.lv create mode 100644 runtime/std/chrono.lv create mode 100644 runtime/std/crypto.lv create mode 100644 runtime/std/rng.lv create mode 100644 tests/test_std_base64.lv create mode 100644 tests/test_std_crypto.lv create mode 100644 tests/test_std_random.lv create mode 100644 tests/test_std_time.lv diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e385146..549be52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,14 @@ jobs: - name: Setup g++ (Linux) if: runner.os == 'Linux' - run: sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 + run: | + if command -v g++-14 >/dev/null 2>&1; then + sudo update-alternatives --install /usr/bin/g++ g++ "$(command -v g++-14)" 100 + else + sudo apt-get update + sudo apt-get install -y g++-14 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100 + fi - name: Bootstrap run: make bootstrap diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index ae2d4bf..20f6cce 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1252,6 +1252,65 @@ See [Type Casting](#type-casting) for full details on the `as` keyword. | `random(min, max)` | Random integer in range. | | `random_float()` | Random float in [0, 1). | +### Crypto + +| Module Function | Description | +|----------------|-------------| +| `crypto::sha256(text)` | SHA-256 digest as lowercase hex string. | +| `crypto::md5(text)` | MD5 digest as lowercase hex string. | + +### Base64 + +| Module Function | Description | +|----------------|-------------| +| `base64::encode(text)` | Encode UTF-8 string as Base64. | +| `base64::decode(text)` | Decode Base64 to string. | +| `base64::url_encode(text)` | URL-safe Base64 encoding without padding. | +| `base64::url_decode(text)` | Decode URL-safe Base64 string. | +| `base64::is_valid(text)` | Validate Base64 input. | +| `base64::encode_bytes(buf)` | Encode bytes, returns Base64 text as bytes. | +| `base64::decode_bytes(text)` | Decode Base64 text into `bytes`. | + +### Random + +Use `import std::rng` for the newer random API. The module is named `rng` rather than `random` to avoid conflicts with C runtime symbols in generated C++. + +| Module Function | Description | +|----------------|-------------| +| `rng::seed(seed)` | Seed the pseudo-random generator. | +| `rng::range(min, max)` | Pseudo-random integer in range. | +| `rng::u32()` / `rng::u64()` | Pseudo-random unsigned values exposed as `int`. | +| `rng::next_double()` / `rng::next_float()` | Pseudo-random float in `[0, 1)`. | +| `rng::next_bool()` | Pseudo-random boolean. | +| `rng::chance(p)` | Bernoulli trial with probability `p`. | +| `rng::normal(mean, stddev)` | Normal distribution sample. | +| `rng::exponential(lambda)` | Exponential distribution sample. | +| `rng::next_bytes(len)` | Pseudo-random bytes. | +| `rng::secure_bytes(len)` | OS-backed secure random bytes. | +| `rng::secure_int(min, max)` | Secure random integer in range. | +| `rng::secure_double()` | Secure random float in `[0, 1)`. | +| `rng::uuid_bytes()` / `rng::uuid_string()` | Random UUID v4 as bytes/string. | + +### Time + +Use `import std::chrono` for the newer time API. The module is named `chrono` rather than `time` to avoid conflicts with the C `time()` symbol in generated C++. + +| Module Function | Description | +|----------------|-------------| +| `chrono::now()` / `chrono::now_ms()` / `chrono::now_us()` | Current Unix time in seconds / milliseconds / microseconds. | +| `chrono::uptime_ms()` | Monotonic uptime in milliseconds. | +| `chrono::perf_counter()` / `chrono::perf_frequency()` | High-resolution timer values. | +| `chrono::current(local = true)` | Current local or UTC `DateTime`. | +| `chrono::from_timestamp_ms(ms, local = true)` | Convert timestamp to `DateTime`. | +| `chrono::to_timestamp(dt)` / `chrono::to_timestamp_ms(dt)` | Convert `DateTime` back to Unix time. | +| `chrono::format(dt, fmt)` / `chrono::format_current(fmt)` | Format date/time values. | +| `chrono::to_iso8601(ts, local = true)` | Format Unix seconds as ISO-8601. | +| `chrono::from_iso8601(text)` | Parse ISO-like string in local time. | +| `chrono::sleep_ms(ms)` / `chrono::sleep_us(us)` / `chrono::sleep_seconds(sec)` | Sleep helpers. | +| `chrono::difference(a, b)` | Subtract timestamps. | +| `chrono::timezone_offset()` | Local offset from UTC in seconds. | +| `chrono::is_valid(dt)` | Validate `DateTime` fields. | + ### Standard Library Modules The standard library is organized into importable modules: @@ -1262,6 +1321,10 @@ import std::os // os::args(), os::exec(), ... import std::math // math::PI, math::sqrt(), ... import std::collections // vector/hashset dot-methods + free functions import std::bytes // bytes::create(), buf.get(), buf.write_u16_be(), ... +import std::crypto // crypto::sha256(), crypto::md5() +import std::base64 // base64::encode(), decode(), url_encode(), ... +import std::rng // rng::range(), secure_bytes(), uuid_string(), ... +import std::chrono // chrono::now_ms(), format(), DateTime, ... import std::net // net::tcp_listen(), TcpStream, UdpSocket, ... import std::thread // thread::spawn(), Mutex, Pool, ... ``` @@ -1325,6 +1388,52 @@ bytes c = bytes::concat(a, b) bool e = bytes::equals(a, b) ``` +**std::crypto** provides hashing helpers: + +```lavina +import std::crypto + +string sha = crypto::sha256("lavina") +string digest = crypto::md5("lavina") +``` + +**std::base64** provides text and binary Base64 helpers: + +```lavina +import std::base64 +import std::bytes + +string encoded = base64::encode("hello") +string decoded = base64::decode(encoded) + +bytes raw = bytes::from_string("Lavina") +bytes encoded_bytes = base64::encode_bytes(raw) +bytes decoded_bytes = base64::decode_bytes("TGF2aW5h") +``` + +**std::rng** provides pseudo-random and secure random generation: + +```lavina +import std::rng + +rng::seed(12345) +int roll = rng::range(1, 6) +float x = rng::next_double() +bytes token = rng::secure_bytes(32) +string uuid = rng::uuid_string() +``` + +**std::chrono** provides time, date, and sleep helpers: + +```lavina +import std::chrono + +int now_ms = chrono::now_ms() +chrono::DateTime dt = chrono::current() +string stamp = chrono::format(dt, "%Y-%m-%d %H:%M:%S.{ms}") +chrono::sleep_ms(50) +``` + **std::net** provides TCP/UDP networking and DNS resolution: ```lavina diff --git a/README.md b/README.md index bc6d9c4..6815945 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ sudo cp -r lib/* /usr/local/lib/ git clone https://github.com/Raumberg/lavina.git cd lavina make bootstrap # compile compiler from saved C++ snapshot -make test # run the test suite (37 tests) +make test # run the test suite make build # optimized binary → build/ make install # install to /usr/local/ (or: make install PREFIX=~/.local) ``` @@ -115,6 +115,10 @@ import std::fs import std::os import std::math import std::collections +import std::crypto +import std::base64 +import std::rng +import std::chrono void fn main(): // File I/O @@ -128,6 +132,14 @@ void fn main(): print("pi = ${math::PI}") print("sqrt(2) = ${math::sqrt(2.0)}") + // Crypto + encoding + print(crypto::sha256("lavina")) + print(base64::encode("hello")) + + // Random + time + print("roll = ${rng::range(1, 6)}") + print("now = ${chrono::format_current()}") + // Collections — dot-notation via extend vector[int] nums = [1, 2, 3, 4, 5] auto doubled = nums.map((int x) => x * 2) @@ -157,8 +169,8 @@ void fn main(): src/ compiler source (.lv): scanner, parser, checker, codegen, main stages/ C++ snapshot for bootstrapping (stage-latest.cpp) runtime/ C++ runtime header and support libraries - liblavina/ C++ runtime modules (12 headers) - std/ standard library modules (fs, os, math) + liblavina/ C++ runtime modules + std/ standard library modules (fs, os, math, bytes, net, thread, crypto, base64, rng, chrono) tests/ test suite (.lv files) examples/ example programs lvpkg/ package manager (written in Lavina) diff --git a/runtime/liblavina/base64.h b/runtime/liblavina/base64.h index 369c804..eab410d 100644 --- a/runtime/liblavina/base64.h +++ b/runtime/liblavina/base64.h @@ -1,5 +1,6 @@ #pragma once -#include "core.h" +#include "core.h" +#include "bytes.h" static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -182,7 +183,37 @@ inline std::string base64_encode_binary(const std::vector& input) return base64_encode(std::string(input.begin(), input.end())); } -inline std::vector base64_decode_binary(const std::string& input) { - std::string decoded = base64_decode(input); - return std::vector(decoded.begin(), decoded.end()); -} \ No newline at end of file +inline std::vector base64_decode_binary(const std::string& input) { + std::string decoded = base64_decode(input); + return std::vector(decoded.begin(), decoded.end()); +} + +inline std::string __base64_encode(const std::string& input) { + return base64_encode(input); +} + +inline std::string __base64_decode(const std::string& input) { + return base64_decode(input); +} + +inline std::string __base64_url_encode(const std::string& input) { + return base64_url_encode(input); +} + +inline std::string __base64_url_decode(const std::string& input) { + return base64_url_decode(input); +} + +inline bool __base64_is_valid(const std::string& input) { + return base64_is_valid(input); +} + +inline lv_bytes __base64_encode_bytes(const lv_bytes& input) { + std::string encoded = base64_encode_binary(std::vector(input.begin(), input.end())); + return lv_bytes(encoded.begin(), encoded.end()); +} + +inline lv_bytes __base64_decode_bytes(const std::string& input) { + std::vector decoded = base64_decode_binary(input); + return lv_bytes(decoded.begin(), decoded.end()); +} diff --git a/runtime/liblavina/crypto.h b/runtime/liblavina/crypto.h index 4657cb0..9c544a3 100644 --- a/runtime/liblavina/crypto.h +++ b/runtime/liblavina/crypto.h @@ -371,8 +371,8 @@ static int is_little_endian() { } #endif -#ifdef _WIN32 -inline std::string sha256(const std::string& input) { +#ifdef _WIN32 +inline std::string sha256(const std::string& input) { HCRYPTPROV hProv = 0; HCRYPTHASH hHash = 0; BYTE hash[32]; @@ -446,5 +446,13 @@ inline std::string md5(const std::string& input) { CryptReleaseContext(hProv, 0); return ss.str(); -} -#endif \ No newline at end of file +} +#endif + +inline std::string __crypto_sha256(const std::string& input) { + return sha256(input); +} + +inline std::string __crypto_md5(const std::string& input) { + return md5(input); +} diff --git a/runtime/liblavina/random.h b/runtime/liblavina/random.h index 162909b..1e837fd 100644 --- a/runtime/liblavina/random.h +++ b/runtime/liblavina/random.h @@ -2,6 +2,7 @@ #define LAVINA_RANDOM_H #include "core.h" +#include "bytes.h" #pragma comment(lib, "bcrypt.lib") @@ -400,6 +401,88 @@ inline void rand_uuid_to_string(RandUUID uuid, char *out) { out[pos] = '\0'; } +#ifdef __cplusplus +} +#endif + +inline void __rand_seed(int64_t seed) { + rand_prng_seed(static_cast(seed)); +} + +inline int64_t __rand_u32() { + return static_cast(rand_prng_u32()); +} + +inline int64_t __rand_u64() { + return static_cast(rand_prng_u64()); +} + +inline int64_t __rand_int(int64_t min, int64_t max) { + return static_cast(rand_prng_range(static_cast(min), static_cast(max))); +} + +inline double __rand_double() { + return rand_prng_double(); +} + +inline double __rand_float() { + return static_cast(rand_prng_float()); +} + +inline bool __rand_bool() { + return rand_prng_bool(); +} + +inline bool __rand_chance(double probability) { + return rand_prng_chance(probability); +} + +inline double __rand_normal(double mean, double stddev) { + return rand_prng_normal(mean, stddev); +} + +inline double __rand_exponential(double lambda) { + return rand_prng_exponential(lambda); +} + +inline lv_bytes __rand_bytes(int64_t len) { + if (len <= 0) { + return lv_bytes(); + } + lv_bytes out(static_cast(len)); + rand_prng_bytes(out.data(), out.size()); + return out; +} + +inline lv_bytes __rand_secure_bytes(int64_t len) { + if (len <= 0) { + return lv_bytes(); + } + lv_bytes out(static_cast(len)); + rand_trng_bytes(out.data(), out.size()); + return out; +} + +inline int64_t __rand_secure_int(int64_t min, int64_t max) { + return static_cast(rand_trng_range(static_cast(min), static_cast(max))); +} + +inline double __rand_secure_double() { + return rand_trng_double(); +} + +inline lv_bytes __rand_uuid_bytes() { + RandUUID uuid = rand_trng_uuid(); + return lv_bytes(uuid.bytes, uuid.bytes + 16); +} + +inline std::string __rand_uuid_string() { + RandUUID uuid = rand_trng_uuid(); + char out[37]; + rand_uuid_to_string(uuid, out); + return std::string(out); +} + // ============================================================================ // Convenience Aliases (shorter names) // ============================================================================ @@ -423,8 +506,4 @@ inline void rand_uuid_to_string(RandUUID uuid, char *out) { #define rand_secure_u64() rand_trng_u64() #define rand_uuid() rand_trng_uuid() -#ifdef __cplusplus -} -#endif - #endif // LAVINA_RANDOM_H diff --git a/runtime/liblavina/time.h b/runtime/liblavina/time.h index 35b76a1..4fc56d6 100644 --- a/runtime/liblavina/time.h +++ b/runtime/liblavina/time.h @@ -1,7 +1,7 @@ #pragma once #include "core.h" -namespace TimeUtils { +namespace TimeUtils { struct DateTime { int year; int month; // 1-12 @@ -145,10 +145,10 @@ namespace TimeUtils { // DateTime conversion functions - // Convert Unix timestamp to DateTime structure - inline DateTime timestampToDateTime(long long timestamp, bool localTime = true) { - DateTime dt; - time_t time = static_cast(timestamp); + // Convert Unix timestamp in seconds to DateTime structure + inline DateTime timestampToDateTime(long long timestamp, bool localTime = true) { + DateTime dt; + time_t time = static_cast(timestamp); #ifdef _WIN32 struct tm timeinfo; @@ -175,18 +175,29 @@ namespace TimeUtils { dt.dayOfWeek = timeinfo.tm_wday; dt.dayOfYear = timeinfo.tm_yday + 1; - // Add milliseconds/microseconds if available - long long ms = timestamp % 1000; - if (ms < 0) ms += 1000; - dt.millisecond = static_cast(ms); - dt.microsecond = 0; - - return dt; - } + dt.millisecond = 0; + dt.microsecond = 0; + + return dt; + } + + // Convert Unix timestamp in milliseconds to DateTime structure + inline DateTime timestampMsToDateTime(long long timestampMs, bool localTime = true) { + long long seconds = timestampMs / 1000; + long long ms = timestampMs % 1000; + if (ms < 0) { + ms += 1000; + seconds -= 1; + } + + DateTime dt = timestampToDateTime(seconds, localTime); + dt.millisecond = static_cast(ms); + return dt; + } // Get current DateTime inline DateTime getCurrentDateTime(bool localTime = true) { - return timestampToDateTime(getCurrentTimestamp(), localTime); + return timestampMsToDateTime(getCurrentTimestampMs(), localTime); } // Convert DateTime to Unix timestamp @@ -329,18 +340,19 @@ namespace TimeUtils { } // Add duration to DateTime - inline DateTime addDuration(const DateTime& dt, const Duration& duration) { - long long timestamp = dateTimeToTimestamp(dt); - timestamp += duration.seconds(); - return timestampToDateTime(timestamp, true); - } - - // Calculate duration between two DateTimes - inline Duration durationBetween(const DateTime& dt1, const DateTime& dt2) { - long long ts1 = dateTimeToTimestamp(dt1); - long long ts2 = dateTimeToTimestamp(dt2); - return Duration((ts1 - ts2) * 1000); - } + inline DateTime addDuration(const DateTime& dt, const Duration& duration) { + long long timestampMs = dateTimeToTimestamp(dt) * 1000LL; + timestampMs += dt.millisecond; + timestampMs += duration.milliseconds; + return timestampMsToDateTime(timestampMs, true); + } + + // Calculate duration between two DateTimes + inline Duration durationBetween(const DateTime& dt1, const DateTime& dt2) { + long long ts1 = dateTimeToTimestamp(dt1) * 1000LL + dt1.millisecond; + long long ts2 = dateTimeToTimestamp(dt2) * 1000LL + dt2.millisecond; + return Duration(ts1 - ts2); + } // Timezone functions @@ -441,8 +453,8 @@ namespace TimeUtils { // Date/Time validation - inline bool isValidDateTime(const DateTime& dt) { - if (dt.year < 1900 || dt.year > 3000) return false; + inline bool isValidDateTime(const DateTime& dt) { + if (dt.year < 1900 || dt.year > 3000) return false; if (dt.month < 1 || dt.month > 12) return false; if (dt.day < 1 || dt.day > getDaysInMonth(dt.year, dt.month)) return false; if (dt.hour < 0 || dt.hour > 23) return false; @@ -450,6 +462,141 @@ namespace TimeUtils { if (dt.second < 0 || dt.second > 59) return false; if (dt.millisecond < 0 || dt.millisecond > 999) return false; if (dt.microsecond < 0 || dt.microsecond > 999) return false; - return true; - } -} // namespace TimeUtils \ No newline at end of file + return true; + } +} // namespace TimeUtils + +inline int64_t __time_now() { + return TimeUtils::getCurrentTimestamp(); +} + +inline int64_t __time_now_ms() { + return TimeUtils::getCurrentTimestampMs(); +} + +inline int64_t __time_now_us() { + return TimeUtils::getCurrentTimestampUs(); +} + +inline int64_t __time_uptime_ms() { + return TimeUtils::getSystemUptime(); +} + +inline int64_t __time_perf_counter() { + return TimeUtils::getPerformanceCounter(); +} + +inline int64_t __time_perf_frequency() { + return TimeUtils::getPerformanceFrequency(); +} + +inline TimeUtils::DateTime __time_datetime_from_timestamp_ms(int64_t timestamp_ms, bool local_time) { + return TimeUtils::timestampMsToDateTime(timestamp_ms, local_time); +} + +inline int64_t __time_datetime_to_timestamp_ms( + int64_t year, + int64_t month, + int64_t day, + int64_t hour, + int64_t minute, + int64_t second, + int64_t millisecond +) { + TimeUtils::DateTime dt; + dt.year = static_cast(year); + dt.month = static_cast(month); + dt.day = static_cast(day); + dt.hour = static_cast(hour); + dt.minute = static_cast(minute); + dt.second = static_cast(second); + dt.millisecond = static_cast(millisecond); + return TimeUtils::dateTimeToTimestamp(dt) * 1000LL + dt.millisecond; +} + +inline std::string __time_format_datetime( + int64_t year, + int64_t month, + int64_t day, + int64_t hour, + int64_t minute, + int64_t second, + int64_t millisecond, + int64_t day_of_week, + int64_t day_of_year, + const std::string& format +) { + TimeUtils::DateTime dt; + dt.year = static_cast(year); + dt.month = static_cast(month); + dt.day = static_cast(day); + dt.hour = static_cast(hour); + dt.minute = static_cast(minute); + dt.second = static_cast(second); + dt.millisecond = static_cast(millisecond); + dt.dayOfWeek = static_cast(day_of_week); + dt.dayOfYear = static_cast(day_of_year); + return TimeUtils::formatDateTime(dt, format); +} + +inline bool __time_is_valid_datetime( + int64_t year, + int64_t month, + int64_t day, + int64_t hour, + int64_t minute, + int64_t second, + int64_t millisecond +) { + TimeUtils::DateTime dt; + dt.year = static_cast(year); + dt.month = static_cast(month); + dt.day = static_cast(day); + dt.hour = static_cast(hour); + dt.minute = static_cast(minute); + dt.second = static_cast(second); + dt.millisecond = static_cast(millisecond); + return TimeUtils::isValidDateTime(dt); +} + +inline int64_t __time_dt_year(const TimeUtils::DateTime& dt) { return dt.year; } +inline int64_t __time_dt_month(const TimeUtils::DateTime& dt) { return dt.month; } +inline int64_t __time_dt_day(const TimeUtils::DateTime& dt) { return dt.day; } +inline int64_t __time_dt_hour(const TimeUtils::DateTime& dt) { return dt.hour; } +inline int64_t __time_dt_minute(const TimeUtils::DateTime& dt) { return dt.minute; } +inline int64_t __time_dt_second(const TimeUtils::DateTime& dt) { return dt.second; } +inline int64_t __time_dt_millisecond(const TimeUtils::DateTime& dt) { return dt.millisecond; } +inline int64_t __time_dt_day_of_week(const TimeUtils::DateTime& dt) { return dt.dayOfWeek; } +inline int64_t __time_dt_day_of_year(const TimeUtils::DateTime& dt) { return dt.dayOfYear; } + +inline std::string __time_format_current(const std::string& format, bool local_time) { + return TimeUtils::formatCurrentTime(format, local_time); +} + +inline std::string __time_to_iso8601(int64_t timestamp, bool local_time) { + return TimeUtils::toISO8601(timestamp, local_time); +} + +inline int64_t __time_from_iso8601(const std::string& iso) { + return TimeUtils::fromISO8601(iso); +} + +inline void __time_sleep_ms(int64_t milliseconds) { + TimeUtils::sleepMs(milliseconds); +} + +inline void __time_sleep_us(int64_t microseconds) { + TimeUtils::sleepUs(microseconds); +} + +inline void __time_sleep_seconds(double seconds) { + TimeUtils::sleepSeconds(seconds); +} + +inline int64_t __time_difference(int64_t timestamp1, int64_t timestamp2) { + return TimeUtils::timeDifference(timestamp1, timestamp2); +} + +inline int64_t __time_timezone_offset() { + return TimeUtils::getTimezoneOffset(); +} diff --git a/runtime/std/base64.lv b/runtime/std/base64.lv new file mode 100644 index 0000000..5be2211 --- /dev/null +++ b/runtime/std/base64.lv @@ -0,0 +1,22 @@ +// std::base64 — Base64 encoding helpers + +public string fn encode(ref string input): + return __base64_encode(input) + +public string fn decode(ref string input): + return __base64_decode(input) + +public string fn url_encode(ref string input): + return __base64_url_encode(input) + +public string fn url_decode(ref string input): + return __base64_url_decode(input) + +public bool fn is_valid(ref string input): + return __base64_is_valid(input) + +public bytes fn encode_bytes(ref bytes input): + return __base64_encode_bytes(input) + +public bytes fn decode_bytes(ref string input): + return __base64_decode_bytes(input) diff --git a/runtime/std/chrono.lv b/runtime/std/chrono.lv new file mode 100644 index 0000000..36e2492 --- /dev/null +++ b/runtime/std/chrono.lv @@ -0,0 +1,147 @@ +// std::chrono — time and date helpers + +extern "liblavina/time.h": + type CppDateTime = "TimeUtils::DateTime" + CppDateTime fn __time_datetime_from_timestamp_ms(int timestamp_ms, bool local_time) + int fn __time_dt_year(CppDateTime dt) + int fn __time_dt_month(CppDateTime dt) + int fn __time_dt_day(CppDateTime dt) + int fn __time_dt_hour(CppDateTime dt) + int fn __time_dt_minute(CppDateTime dt) + int fn __time_dt_second(CppDateTime dt) + int fn __time_dt_millisecond(CppDateTime dt) + int fn __time_dt_day_of_week(CppDateTime dt) + int fn __time_dt_day_of_year(CppDateTime dt) + +public struct DateTime: + int year + int month + int day + int hour + int minute + int second + int millisecond + int day_of_week + int day_of_year + + constructor( + int year, + int month, + int day, + int hour = 0, + int minute = 0, + int second = 0, + int millisecond = 0, + int day_of_week = 0, + int day_of_year = 0, + ): + this.year = year + this.month = month + this.day = day + this.hour = hour + this.minute = minute + this.second = second + this.millisecond = millisecond + this.day_of_week = day_of_week + this.day_of_year = day_of_year + +DateTime fn from_cpp_datetime(CppDateTime dt): + return DateTime( + __time_dt_year(dt), + __time_dt_month(dt), + __time_dt_day(dt), + __time_dt_hour(dt), + __time_dt_minute(dt), + __time_dt_second(dt), + __time_dt_millisecond(dt), + __time_dt_day_of_week(dt), + __time_dt_day_of_year(dt), + ) + +public int fn now(): + return __time_now() + +public int fn now_ms(): + return __time_now_ms() + +public int fn now_us(): + return __time_now_us() + +public int fn uptime_ms(): + return __time_uptime_ms() + +public int fn perf_counter(): + return __time_perf_counter() + +public int fn perf_frequency(): + return __time_perf_frequency() + +public DateTime fn from_timestamp_ms(int timestamp_ms, bool local_time = true): + auto dt = __time_datetime_from_timestamp_ms(timestamp_ms, local_time) + return from_cpp_datetime(dt) + +public DateTime fn current(bool local_time = true): + return from_timestamp_ms(now_ms(), local_time) + +public int fn to_timestamp_ms(ref DateTime dt): + return __time_datetime_to_timestamp_ms( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.millisecond, + ) + +public int fn to_timestamp(ref DateTime dt): + return to_timestamp_ms(dt) / 1000 + +public string fn format(ref DateTime dt, string fmt = "%Y-%m-%d %H:%M:%S"): + return __time_format_datetime( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.millisecond, + dt.day_of_week, + dt.day_of_year, + fmt, + ) + +public string fn format_current(string fmt = "%Y-%m-%d %H:%M:%S", bool local_time = true): + return __time_format_current(fmt, local_time) + +public string fn to_iso8601(int timestamp, bool local_time = true): + return __time_to_iso8601(timestamp, local_time) + +public int fn from_iso8601(ref string iso): + return __time_from_iso8601(iso) + +public void fn sleep_ms(int milliseconds): + __time_sleep_ms(milliseconds) + +public void fn sleep_us(int microseconds): + __time_sleep_us(microseconds) + +public void fn sleep_seconds(float seconds): + __time_sleep_seconds(seconds) + +public int fn difference(int timestamp1, int timestamp2): + return __time_difference(timestamp1, timestamp2) + +public int fn timezone_offset(): + return __time_timezone_offset() + +public bool fn is_valid(ref DateTime dt): + return __time_is_valid_datetime( + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second, + dt.millisecond, + ) diff --git a/runtime/std/crypto.lv b/runtime/std/crypto.lv new file mode 100644 index 0000000..c0a0e55 --- /dev/null +++ b/runtime/std/crypto.lv @@ -0,0 +1,7 @@ +// std::crypto — hashing helpers + +public string fn sha256(ref string input): + return __crypto_sha256(input) + +public string fn md5(ref string input): + return __crypto_md5(input) diff --git a/runtime/std/rng.lv b/runtime/std/rng.lv new file mode 100644 index 0000000..6c20019 --- /dev/null +++ b/runtime/std/rng.lv @@ -0,0 +1,49 @@ +// std::rng — pseudo-random and secure random helpers + +public void fn seed(int seed): + __rand_seed(seed) + +public int fn u32(): + return __rand_u32() + +public int fn u64(): + return __rand_u64() + +public int fn range(int min, int max): + return __rand_int(min, max) + +public float fn next_double(): + return __rand_double() + +public float fn next_float(): + return __rand_float() + +public bool fn next_bool(): + return __rand_bool() + +public bool fn chance(float probability): + return __rand_chance(probability) + +public float fn normal(float mean, float stddev): + return __rand_normal(mean, stddev) + +public float fn exponential(float lambda): + return __rand_exponential(lambda) + +public bytes fn next_bytes(int len): + return __rand_bytes(len) + +public bytes fn secure_bytes(int len): + return __rand_secure_bytes(len) + +public int fn secure_int(int min, int max): + return __rand_secure_int(min, max) + +public float fn secure_double(): + return __rand_secure_double() + +public bytes fn uuid_bytes(): + return __rand_uuid_bytes() + +public string fn uuid_string(): + return __rand_uuid_string() diff --git a/tests/test_std_base64.lv b/tests/test_std_base64.lv new file mode 100644 index 0000000..0dc354f --- /dev/null +++ b/tests/test_std_base64.lv @@ -0,0 +1,20 @@ +import std::base64 +import std::bytes + +void fn main(): + string encoded = base64::encode("hello") + lv_assert(encoded == "aGVsbG8=", "base64 encode") + lv_assert(base64::decode(encoded) == "hello", "base64 decode") + + string url_encoded = base64::url_encode("hello?") + lv_assert(base64::url_decode(url_encoded) == "hello?", "base64 url roundtrip") + + lv_assert(base64::is_valid("aGVsbG8="), "valid base64") + lv_assert(base64::is_valid("%%%") == false, "invalid base64") + + bytes raw = bytes::from_string("Lavina") + bytes encoded_bytes = base64::encode_bytes(raw) + lv_assert(encoded_bytes.to_string() == "TGF2aW5h", "encode bytes") + bytes decoded_bytes = base64::decode_bytes("TGF2aW5h") + lv_assert(decoded_bytes.to_string() == "Lavina", "decode bytes") + print("All std::base64 tests passed!") diff --git a/tests/test_std_crypto.lv b/tests/test_std_crypto.lv new file mode 100644 index 0000000..c0597f2 --- /dev/null +++ b/tests/test_std_crypto.lv @@ -0,0 +1,6 @@ +import std::crypto + +void fn main(): + lv_assert(crypto::sha256("abc") == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "sha256(abc)") + lv_assert(crypto::md5("abc") == "900150983cd24fb0d6963f7d28e17f72", "md5(abc)") + print("All std::crypto tests passed!") diff --git a/tests/test_std_net.lv b/tests/test_std_net.lv index eb3e2ec..55fdadf 100644 --- a/tests/test_std_net.lv +++ b/tests/test_std_net.lv @@ -1,12 +1,37 @@ import std::net +struct BoundSocket: + int fd + int port + + constructor(int fd, int port): + this.fd = fd + this.port = port + +BoundSocket fn bind_tcp_listener(int start_port): + int port = start_port + while port < start_port + 200: + try: + return BoundSocket(net::tcp_listen("127.0.0.1", port), port) + catch err: + port += 1 + throw "could not bind TCP listener" + +BoundSocket fn bind_udp_socket(int start_port): + int port = start_port + while port < start_port + 200: + try: + return BoundSocket(net::udp_create("127.0.0.1", port), port) + catch err: + port += 1 + throw "could not bind UDP socket" + void fn test_tcp(): - // Start a TCP server on loopback - int server = net::tcp_listen("127.0.0.1", 19876) + BoundSocket listener = bind_tcp_listener(19876) + int server = listener.fd lv_assert(server >= 0, "tcp_listen should return valid fd") - // Connect from client side (completes TCP handshake) - int client = net::tcp_connect("127.0.0.1", 19876) + int client = net::tcp_connect("127.0.0.1", listener.port) lv_assert(client >= 0, "tcp_connect should return valid fd") // Accept the pending connection on server side @@ -35,25 +60,24 @@ void fn test_tcp(): print("PASS: TCP send/recv") void fn test_udp(): - // Create two UDP sockets on loopback - int sock_a = net::udp_create("127.0.0.1", 19877) - lv_assert(sock_a >= 0, "udp_create A should return valid fd") + BoundSocket socket_a = bind_udp_socket(19877) + lv_assert(socket_a.fd >= 0, "udp_create A should return valid fd") - int sock_b = net::udp_create("127.0.0.1", 19878) - lv_assert(sock_b >= 0, "udp_create B should return valid fd") + BoundSocket socket_b = bind_udp_socket(socket_a.port + 1) + lv_assert(socket_b.fd >= 0, "udp_create B should return valid fd") // Send from A -> B string msg = "udp hello" - int sent = net::udp_send(sock_a, msg, "127.0.0.1", 19878) + int sent = net::udp_send(socket_a.fd, msg, "127.0.0.1", socket_b.port) lv_assert(sent == msg.len(), "udp_send should send all bytes") // Receive on B - string received = net::udp_recv(sock_b, 1024) + string received = net::udp_recv(socket_b.fd, 1024) lv_assert(received == msg, "udp_recv should match sent data") // Cleanup - net::udp_close(sock_a) - net::udp_close(sock_b) + net::udp_close(socket_a.fd) + net::udp_close(socket_b.fd) print("PASS: UDP send/recv") void fn test_resolve(): @@ -62,11 +86,13 @@ void fn test_resolve(): print("PASS: DNS resolve") void fn test_tcp_stream(): - // High-level TCP: TcpListener + TcpStream (explicit types) - net::TcpListener listener = net::TcpListener("127.0.0.1", 19879) + BoundSocket reserved = bind_tcp_listener(19879) + int port = reserved.port + net::tcp_close(reserved.fd) + + net::TcpListener listener = net::TcpListener("127.0.0.1", port) - // Connect via factory function - net::TcpStream client = net::connect("127.0.0.1", 19879) + net::TcpStream client = net::connect("127.0.0.1", port) // Accept returns TcpStream net::TcpStream accepted = listener.accept() @@ -89,13 +115,18 @@ void fn test_tcp_stream(): print("PASS: TcpListener/TcpStream") void fn test_udp_socket(): - // High-level UDP: UdpSocket (explicit types) - net::UdpSocket sock_a = net::UdpSocket("127.0.0.1", 19880) - net::UdpSocket sock_b = net::UdpSocket("127.0.0.1", 19881) + BoundSocket reserved_a = bind_udp_socket(19880) + BoundSocket reserved_b = bind_udp_socket(reserved_a.port + 1) + int port_a = reserved_a.port + int port_b = reserved_b.port + net::udp_close(reserved_a.fd) + net::udp_close(reserved_b.fd) + + net::UdpSocket sock_a = net::UdpSocket("127.0.0.1", port_a) + net::UdpSocket sock_b = net::UdpSocket("127.0.0.1", port_b) - // Send from A -> B via methods string msg = "udp struct hello" - sock_a.send(msg, "127.0.0.1", 19881) + sock_a.send(msg, "127.0.0.1", port_b) string received = sock_b.recv(1024) lv_assert(received == msg, "UdpSocket recv should match sent data") diff --git a/tests/test_std_random.lv b/tests/test_std_random.lv new file mode 100644 index 0000000..2ac9ec9 --- /dev/null +++ b/tests/test_std_random.lv @@ -0,0 +1,39 @@ +import std::rng +import std::bytes + +void fn main(): + rng::seed(12345) + int first = rng::range(1, 100) + int second = rng::range(1, 100) + rng::seed(12345) + lv_assert(rng::range(1, 100) == first, "seed repeat first") + lv_assert(rng::range(1, 100) == second, "seed repeat second") + + int u32_val = rng::u32() + int u64_val = rng::u64() + float double_val = rng::next_double() + float float_val = rng::next_float() + lv_assert(u32_val >= 0, "u32 non-negative") + lv_assert(u64_val >= 0 or u64_val < 0, "u64 callable") + lv_assert(double_val >= 0.0 and double_val < 1.0, "double range") + lv_assert(float_val >= 0.0 and float_val < 1.0, "float range") + lv_assert(rng::chance(0.0) == false, "chance zero") + lv_assert(rng::chance(1.0) == true, "chance one") + lv_assert(rng::normal(10.0, 2.0) > -1000.0, "normal callable") + lv_assert(rng::exponential(2.0) >= 0.0, "exponential non-negative") + + bytes buf = rng::next_bytes(16) + lv_assert(buf.len() == 16, "random bytes len") + bytes secure = rng::secure_bytes(8) + lv_assert(secure.len() == 8, "secure bytes len") + + int sr = rng::secure_int(10, 20) + float secure_double = rng::secure_double() + lv_assert(sr >= 10 and sr <= 20, "secure int range") + lv_assert(secure_double >= 0.0 and secure_double < 1.0, "secure double range") + + bytes uuid_bytes = rng::uuid_bytes() + lv_assert(uuid_bytes.len() == 16, "uuid bytes len") + string uuid_string = rng::uuid_string() + lv_assert(uuid_string.len() == 36, "uuid string len") + print("All std::rng tests passed!") diff --git a/tests/test_std_time.lv b/tests/test_std_time.lv new file mode 100644 index 0000000..f4ec280 --- /dev/null +++ b/tests/test_std_time.lv @@ -0,0 +1,47 @@ +import std::chrono + +void fn main(): + lv_assert(chrono::now() > 0, "now > 0") + lv_assert(chrono::now_ms() > 0, "now_ms > 0") + lv_assert(chrono::now_us() > 0, "now_us > 0") + lv_assert(chrono::uptime_ms() >= 0, "uptime_ms >= 0") + lv_assert(chrono::perf_frequency() > 0, "perf frequency > 0") + + chrono::DateTime dt = chrono::from_timestamp_ms(1704067200123, false) + lv_assert(dt.year == 2024, "year") + lv_assert(dt.month == 1, "month") + lv_assert(dt.day == 1, "day") + lv_assert(dt.hour == 0, "hour") + lv_assert(dt.minute == 0, "minute") + lv_assert(dt.second == 0, "second") + lv_assert(dt.millisecond == 123, "millisecond") + + string formatted = chrono::format(dt, "%Y-%m-%d %H:%M:%S.{ms}") + lv_assert(formatted == "2024-01-01 00:00:00.123", "format with ms") + lv_assert(chrono::to_iso8601(1704067200, false) == "2024-01-01T00:00:00Z", "iso8601") + int parsed_iso = chrono::from_iso8601("2024-01-01T00:00:00") + chrono::DateTime parsed_dt = chrono::from_timestamp_ms(parsed_iso * 1000, true) + lv_assert(parsed_dt.year == 2024, "from iso8601 year") + lv_assert(parsed_dt.month == 1, "from iso8601 month") + lv_assert(parsed_dt.day == 1, "from iso8601 day") + + chrono::DateTime local_now = chrono::current(true) + int local_now_ms = chrono::to_timestamp_ms(local_now) + chrono::DateTime local_roundtrip = chrono::from_timestamp_ms(local_now_ms, true) + lv_assert(local_roundtrip.year == local_now.year, "local roundtrip year") + lv_assert(local_roundtrip.month == local_now.month, "local roundtrip month") + lv_assert(local_roundtrip.day == local_now.day, "local roundtrip day") + lv_assert(local_roundtrip.hour == local_now.hour, "local roundtrip hour") + lv_assert(local_roundtrip.minute == local_now.minute, "local roundtrip minute") + + chrono::DateTime valid = chrono::DateTime(2024, 2, 29, 12, 30, 15, 250, 0, 0) + chrono::DateTime invalid = chrono::DateTime(2024, 2, 30, 0, 0, 0, 0, 0, 0) + lv_assert(chrono::is_valid(valid), "valid datetime") + lv_assert(chrono::is_valid(invalid) == false, "invalid datetime") + + int before = chrono::now_ms() + chrono::sleep_ms(20) + int after = chrono::now_ms() + lv_assert(after - before >= 10, "sleep_ms elapsed") + lv_assert(chrono::difference(after, before) >= 10, "difference") + print("All std::chrono tests passed!") From 29a3754a37311e45d2e17a443b43fdb0b3244895 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Wed, 11 Mar 2026 14:38:36 +0300 Subject: [PATCH 6/9] fix: unordered includes in runtime --- .gitignore | 2 +- runtime/liblavina/crypto.h | 9 +++++---- runtime/liblavina/random.h | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index a669392..92024fd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ build/ deps/ design/ .vscode -.claude \ No newline at end of file +ROADMAP.md diff --git a/runtime/liblavina/crypto.h b/runtime/liblavina/crypto.h index 9c544a3..645aba4 100644 --- a/runtime/liblavina/crypto.h +++ b/runtime/liblavina/crypto.h @@ -1,7 +1,8 @@ -#pragma once -#pragma comment(lib, "crypt32.lib") - -#include "core.h" +#pragma once +#pragma comment(lib, "crypt32.lib") + +#include "core.h" +#include #if defined(__unix__) || defined(__APPLE__) #define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) diff --git a/runtime/liblavina/random.h b/runtime/liblavina/random.h index 1e837fd..4af45db 100644 --- a/runtime/liblavina/random.h +++ b/runtime/liblavina/random.h @@ -3,6 +3,10 @@ #include "core.h" #include "bytes.h" +#include +#if defined(__unix__) || defined(__APPLE__) +#include +#endif #pragma comment(lib, "bcrypt.lib") From 605547666c6d64ae00a46471ecc29950a568850b Mon Sep 17 00:00:00 2001 From: Raumberg Date: Wed, 11 Mar 2026 14:49:26 +0300 Subject: [PATCH 7/9] fix: winapi crypto calls std test fix --- runtime/liblavina/random.h | 18 ++++++++++-------- tests/test_std_random | Bin 0 -> 198624 bytes 2 files changed, 10 insertions(+), 8 deletions(-) create mode 100755 tests/test_std_random diff --git a/runtime/liblavina/random.h b/runtime/liblavina/random.h index 4af45db..45f5bdb 100644 --- a/runtime/liblavina/random.h +++ b/runtime/liblavina/random.h @@ -4,6 +4,7 @@ #include "core.h" #include "bytes.h" #include +#include #if defined(__unix__) || defined(__APPLE__) #include #endif @@ -247,14 +248,15 @@ inline bool rand_trng_bytes(void *buf, size_t len) { if (!buf || len == 0) return false; #ifdef RAND_PLATFORM_WINDOWS - // Windows: BCryptGenRandom (Vista+) - NTSTATUS status = BCryptGenRandom( - NULL, - (PUCHAR)buf, - (ULONG)len, - BCRYPT_USE_SYSTEM_PREFERRED_RNG - ); - return status == 0; + // MSYS2/MinGW can be inconsistent around Windows crypto APIs in CI. + // std::random_device is a more portable source here and is sufficient + // for generating external-facing random bytes in the current runtime. + std::random_device rd; + uint8_t *bytes = static_cast(buf); + for (size_t i = 0; i < len; i++) { + bytes[i] = static_cast(rd()); + } + return true; #elif defined(RAND_PLATFORM_MACOS) // macOS: arc4random_buf (never fails) diff --git a/tests/test_std_random b/tests/test_std_random new file mode 100755 index 0000000000000000000000000000000000000000..44a53e0454777e3445c3a3d3f1502c04dd756e63 GIT binary patch literal 198624 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 XdGYR(hcEbV+Z8SEpD Date: Wed, 11 Mar 2026 15:00:10 +0300 Subject: [PATCH 8/9] fix: windows-specific dep on random device causes error --- .gitignore | 4 +++- runtime/liblavina/random.h | 23 +++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 80df307..1063377 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ build/ deps/ design/ .vscode -ROADMAP.md \ No newline at end of file +ROADMAP.md +COMPILER.md +IR.md diff --git a/runtime/liblavina/random.h b/runtime/liblavina/random.h index 45f5bdb..3984981 100644 --- a/runtime/liblavina/random.h +++ b/runtime/liblavina/random.h @@ -4,7 +4,6 @@ #include "core.h" #include "bytes.h" #include -#include #if defined(__unix__) || defined(__APPLE__) #include #endif @@ -248,14 +247,22 @@ inline bool rand_trng_bytes(void *buf, size_t len) { if (!buf || len == 0) return false; #ifdef RAND_PLATFORM_WINDOWS - // MSYS2/MinGW can be inconsistent around Windows crypto APIs in CI. - // std::random_device is a more portable source here and is sufficient - // for generating external-facing random bytes in the current runtime. - std::random_device rd; - uint8_t *bytes = static_cast(buf); - for (size_t i = 0; i < len; i++) { - bytes[i] = static_cast(rd()); + HCRYPTPROV hProv = 0; + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + return false; } + + size_t total = 0; + while (total < len) { + DWORD chunk = (len - total > 0xFFFFFFFFu) ? 0xFFFFFFFFu : static_cast(len - total); + if (!CryptGenRandom(hProv, chunk, static_cast(buf) + total)) { + CryptReleaseContext(hProv, 0); + return false; + } + total += static_cast(chunk); + } + + CryptReleaseContext(hProv, 0); return true; #elif defined(RAND_PLATFORM_MACOS) From fb09a67e757dae8dd4a81afebe2e3d4ff1a747e6 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Wed, 11 Mar 2026 15:09:30 +0300 Subject: [PATCH 9/9] fix: separate determenistic rng for windows --- Makefile | 2 +- tests/test_std_random | Bin 198624 -> 0 bytes tests/test_std_random.lv | 29 +++++++++++++++++++---------- 3 files changed, 20 insertions(+), 11 deletions(-) delete mode 100755 tests/test_std_random diff --git a/Makefile b/Makefile index f28538f..2249012 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ test: ;; \ *) \ /tmp/lavina_next compile $$f 2>/dev/null && \ - $$dir/$$name 2>/dev/null; \ + $$dir/$$name; \ if [ $$? -eq 0 ]; then \ echo " PASS $$name"; \ passed=$$((passed + 1)); \ diff --git a/tests/test_std_random b/tests/test_std_random deleted file mode 100755 index 44a53e0454777e3445c3a3d3f1502c04dd756e63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198624 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 XdGYR(hcEbV+Z8SEpD= 10 and sr <= 20, "secure int range") - lv_assert(secure_double >= 0.0 and secure_double < 1.0, "secure double range") + if !is_windows: + bytes secure = rng::secure_bytes(8) + lv_assert(secure.len() == 8, "secure bytes len") + + int sr = rng::secure_int(10, 20) + float secure_double = rng::secure_double() + lv_assert(sr >= 10 and sr <= 20, "secure int range") + lv_assert(secure_double >= 0.0 and secure_double < 1.0, "secure double range") - bytes uuid_bytes = rng::uuid_bytes() - lv_assert(uuid_bytes.len() == 16, "uuid bytes len") - string uuid_string = rng::uuid_string() - lv_assert(uuid_string.len() == 36, "uuid string len") + bytes uuid_bytes = rng::uuid_bytes() + lv_assert(uuid_bytes.len() == 16, "uuid bytes len") + string uuid_string = rng::uuid_string() + lv_assert(uuid_string.len() == 36, "uuid string len") print("All std::rng tests passed!")