{- |
Module      :  OpenTelemetry.Id.Generator
Copyright   :  (c) Ian Duncan, 2021
License     :  BSD-3
Description :  ID generation strategies for trace and span identifiers
Maintainer  :  Ian Duncan
Stability   :  experimental
Portability :  non-portable (GHC extensions)

Pluggable ID generation for creating Trace and Span ID bytes.

The default generator ('DefaultIdGenerator') uses a thread-local
xoshiro256++ PRNG seeded from the platform's native random source
(arc4random_buf on macOS, getrandom on Linux, BCryptGenRandom on
Windows). It is not cryptographically secure but is significantly
faster than a CSPRNG due to zero contention and no syscalls after
initial seeding. This matches the approach used by the Java OTel SDK
(@ThreadLocalRandom@). The OTel spec requires IDs to be "randomly
generated" but does not mandate cryptographic strength.

For fully custom generation (e.g. deterministic testing), use
'customIdGenerator' to provide your own functions.
-}
module OpenTelemetry.Trace.Id.Generator (
  IdGenerator (..),
  customIdGenerator,
) where

import Data.ByteString.Short (ShortByteString)


{- | Strategy for generating trace and span IDs.

Pattern-matching on known constructors in the hot path lets GHC
inline the generation functions directly, eliminating the indirect
call overhead of a record-of-functions approach.

 @since 0.0.1.0
-}
data IdGenerator
  = {- | Thread-local xoshiro256++ PRNG seeded from the platform CSPRNG.

    Each OS thread gets its own PRNG state, seeded once from the
    platform CSPRNG. Subsequent ID generation is pure arithmetic
    with no syscalls, no shared state, and no atomic operations.

    xoshiro256++ passes BigCrush and PractRand with a period of
    2^256−1. Fork-safe: child processes reseed automatically via
    @pthread_atfork@.

    @since 0.1.0.0
    -}
    DefaultIdGenerator
  | {- | Fully custom generation.

    The first action must produce exactly 8 random bytes (span ID),
    the second exactly 16 bytes (trace ID).

    @since 0.0.1.0
    -}
    CustomIdGenerator
      !(IO ShortByteString)
      !(IO ShortByteString)


{- | Construct a custom 'IdGenerator' from two actions producing raw ID bytes.

@
deterministicGen :: IORef Word64 -> IdGenerator
deterministicGen ref = customIdGenerator
  (genBytes ref 8)   -- span ID: 8 bytes
  (genBytes ref 16)  -- trace ID: 16 bytes
@

 @since 0.0.1.0
-}
customIdGenerator
  :: IO ShortByteString
  -- ^ Generate 8 bytes for span ID
  -> IO ShortByteString
  -- ^ Generate 16 bytes for trace ID
  -> IdGenerator
customIdGenerator :: IO ShortByteString -> IO ShortByteString -> IdGenerator
customIdGenerator = IO ShortByteString -> IO ShortByteString -> IdGenerator
CustomIdGenerator