packages feed

hs-opentelemetry-api-1.0.0.0: cbits/hs_otel_rng.c

/*
 * RNG for OpenTelemetry trace and span ID generation.
 *
 * xoshiro256++ per OS thread, seeded once from the platform CSPRNG.
 * ~2-3x faster than a CSPRNG pool path because there is no shared
 * state, no atomics, and no syscalls after initial seed.
 * Fork-safe via pthread_atfork (reseeds in child).
 *
 * Platform CSPRNG (used only for seeding):
 *   macOS/BSD  — arc4random_buf  (userspace ChaCha20, lock-free)
 *   Linux      — getrandom(2)    (kernel CSPRNG, no fd needed)
 *   Windows    — BCryptGenRandom (CNG, no provider handle needed)
 *   fallback   — /dev/urandom
 */

#include <stdint.h>
#include <stddef.h>
#include <string.h>

#if defined(_WIN32) || defined(_WIN64)
  #define USE_BCRYPT 1
  #define WIN32_LEAN_AND_MEAN
  #include <windows.h>
  #include <bcrypt.h>
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
  #define USE_ARC4RANDOM 1
  #include <stdlib.h>
#elif defined(__linux__)
  #define USE_GETRANDOM 1
  #include <sys/random.h>
  #include <errno.h>
#else
  #define USE_DEVURANDOM 1
  #include <stdio.h>
#endif

#if defined(_MSC_VER)
  #define THREAD_LOCAL __declspec(thread)
#else
  #define THREAD_LOCAL __thread
  #include <pthread.h>
#endif

/* ── Platform CSPRNG (seed source only) ──────────────────────────────── */

static int fill_random_platform(uint8_t *buf, size_t n) {
#if defined(USE_BCRYPT)
    NTSTATUS status = BCryptGenRandom(
        NULL, buf, (ULONG)n, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
    return BCRYPT_SUCCESS(status) ? 0 : -1;
#elif defined(USE_ARC4RANDOM)
    arc4random_buf(buf, n);
    return 0;
#elif defined(USE_GETRANDOM)
    while (n > 0) {
        ssize_t got = getrandom(buf, n, 0);
        if (got < 0) {
            if (errno == EINTR) continue;
            return -1;
        }
        buf += got;
        n   -= (size_t)got;
    }
    return 0;
#elif defined(USE_DEVURANDOM)
    FILE *f = fopen("/dev/urandom", "rb");
    if (!f) return -1;
    size_t r = fread(buf, 1, n, f);
    fclose(f);
    return (r == n) ? 0 : -1;
#endif
}

/* ── xoshiro256++ (thread-local fast PRNG) ───────────────────────────── */
/*
 * Reference: https://prng.di.unimi.it/xoshiro256plusplus.c
 * by David Blackman and Sebastiano Vigna (vigna@acm.org), public domain.
 *
 * 256-bit state, 64-bit output, period 2^256-1.  Passes BigCrush and
 * PractRand.  Each OS thread gets its own state seeded from the platform
 * CSPRNG, so there is zero contention between threads.
 */

static inline uint64_t rotl64(uint64_t x, int k) {
    return (x << k) | (x >> (64 - k));
}

static THREAD_LOCAL uint64_t xoshiro_s[4];
static THREAD_LOCAL int       xoshiro_init;

static void xoshiro_seed(void) {
    fill_random_platform((uint8_t *)xoshiro_s, sizeof(xoshiro_s));
    if (xoshiro_s[0] == 0 && xoshiro_s[1] == 0 &&
        xoshiro_s[2] == 0 && xoshiro_s[3] == 0) {
        xoshiro_s[0] = 1;
    }
    xoshiro_init = 1;
}

static inline uint64_t xoshiro256pp(void) {
    if (__builtin_expect(!xoshiro_init, 0)) xoshiro_seed();

    const uint64_t result = rotl64(xoshiro_s[0] + xoshiro_s[3], 23) + xoshiro_s[0];
    const uint64_t t = xoshiro_s[1] << 17;

    xoshiro_s[2] ^= xoshiro_s[0];
    xoshiro_s[3] ^= xoshiro_s[1];
    xoshiro_s[1] ^= xoshiro_s[2];
    xoshiro_s[0] ^= xoshiro_s[3];
    xoshiro_s[2] ^= t;
    xoshiro_s[3]  = rotl64(xoshiro_s[3], 45);

    return result;
}

/* Fork safety: force reseed in child process so parent and child don't
 * share PRNG state (which would produce duplicate IDs). */
#if !defined(_WIN32) && !defined(_WIN64)
static void xoshiro_atfork_child(void) {
    xoshiro_init = 0;
}

__attribute__((constructor))
static void xoshiro_register_atfork(void) {
    pthread_atfork(NULL, NULL, xoshiro_atfork_child);
}
#endif

uint64_t hs_otel_xoshiro_next(void) {
    return xoshiro256pp();
}

/* Generate TraceId (2 words) + SpanId (1 word) in a single FFI call.
 * Used for root spans where both IDs need generating. Saves 2 FFI
 * round-trips vs 3 separate hs_otel_xoshiro_next calls. */
void hs_otel_xoshiro_trace_and_span(uint64_t *trace_hi, uint64_t *trace_lo, uint64_t *span_id) {
    *trace_hi = xoshiro256pp();
    *trace_lo = xoshiro256pp();
    *span_id  = xoshiro256pp();
}