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 diff --git a/runtime/lavina.h b/runtime/lavina.h index 2a60eb5..574251b 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" @@ -11,6 +14,7 @@ #include "liblavina/os.h" #include "liblavina/util.h" #include "liblavina/math.h" +#include "liblavina/random.h" #include "liblavina/bytes.h" #include "liblavina/uuid.h" #include "liblavina/net.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 ca5a0c6..a7d8f8c 100644 --- a/runtime/liblavina/core.h +++ b/runtime/liblavina/core.h @@ -20,7 +20,12 @@ #include #include #include +#include +#include +#include +#include #if defined(__unix__) || defined(__APPLE__) +#define RAND_PLATFORM_UNIX #include #include #include @@ -30,8 +35,12 @@ #include #endif #if defined(__APPLE__) +#define RAND_PLATFORM_MACOS #include #endif #if defined(_WIN32) +#define RAND_PLATFORM_WINDOWS #include -#endif +#include +#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..4657cb0 --- /dev/null +++ b/runtime/liblavina/crypto.h @@ -0,0 +1,450 @@ +#pragma once +#pragma comment(lib, "crypt32.lib") + +#include "core.h" + +#if defined(__unix__) || defined(__APPLE__) +#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; + + 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; + + 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(); +} + +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; + } + + 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; + + 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 +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/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 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