diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c4cca7..549be52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,12 +19,16 @@ 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 + 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/.gitignore b/.gitignore index 315e7bd..1063377 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ build/ deps/ design/ .vscode +ROADMAP.md +COMPILER.md +IR.md 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/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/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/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..eab410d --- /dev/null +++ b/runtime/liblavina/base64.h @@ -0,0 +1,219 @@ +#pragma once +#include "core.h" +#include "bytes.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()); +} + +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/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..645aba4 --- /dev/null +++ b/runtime/liblavina/crypto.h @@ -0,0 +1,459 @@ +#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)))) +#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 + +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 new file mode 100644 index 0000000..3984981 --- /dev/null +++ b/runtime/liblavina/random.h @@ -0,0 +1,522 @@ +#ifndef LAVINA_RANDOM_H +#define LAVINA_RANDOM_H + +#include "core.h" +#include "bytes.h" +#include +#if defined(__unix__) || defined(__APPLE__) +#include +#endif + +#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 + 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) + // 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'; +} + +#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) +// ============================================================================ + +#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() + +#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..4fc56d6 --- /dev/null +++ b/runtime/liblavina/time.h @@ -0,0 +1,602 @@ +#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 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; + 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; + + 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 timestampMsToDateTime(getCurrentTimestampMs(), 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 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 + + // 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 + +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..e252cba --- /dev/null +++ b/tests/test_std_random.lv @@ -0,0 +1,48 @@ +import std::rng +import std::bytes + +void fn main(): + bool is_windows = false + cpp { + #if defined(_WIN32) + is_windows = true; + #endif + } + + 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") + + 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") + 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!")