packages feed

heptapod-1.1.0.0: source/library/Heptapod.hs

-- | This module provides functions for building and generating version 7 UUIDs
-- as defined by section 5.7 of RFC 9562.
--
-- <https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7>
module Heptapod where

import qualified Control.Monad.IO.Class as IO
import qualified Data.Bits as Bits
import qualified Data.ByteString as ByteString
import qualified Data.Int as Int
import qualified Data.Time.Clock.System as Time
import qualified Data.UUID.Types as UUID
import qualified Data.Word as Word
import qualified System.Entropy as Entropy

-- | Generates a UUIDv7 using the current time (from 'Time.getSystemTime') and
-- random data (from 'Entropy.getEntropy').
generate :: (IO.MonadIO m) => m UUID.UUID
generate = IO.liftIO $ do
  t <- Time.getSystemTime
  -- Note that we only need 74 bits (12 + 62) of randomness. That's a little
  -- more than 9 bytes (72 bits), so we have to request 10 bytes (80 bits) of
  -- entropy. The extra 6 bits are discarded.
  b <- Entropy.getEntropy 10
  pure $
    let u8_u64 = fromIntegral :: Word.Word8 -> Word.Word64
        f = Bits.shift . u8_u64 . ByteString.index b
        r = f 0 0 + f 1 8
        s = f 2 0 + f 3 8 + f 4 16 + f 5 24 + f 6 32 + f 7 40 + f 8 48 + f 9 56
     in build t r s

-- | Builds a UUIDv7 using the provided fields. Typically you will want to use
-- the 'generate' function instead.
build ::
  -- | Corresponds to the @unix_ts_ms@ field.
  Time.SystemTime ->
  -- | Corresponds to the @rand_a@ field. Only the low 12 bits are used.
  Word.Word64 ->
  -- | Corresponds to the @rand_b@ field. Only the low 62 bits are used.
  Word.Word64 ->
  UUID.UUID
build t r s =
  let i64_u64 = fromIntegral :: Int.Int64 -> Word.Word64
      u32_u64 = fromIntegral :: Word.Word32 -> Word.Word64
      unix_ts_ms =
        Bits.shift
          ( (i64_u64 (Time.systemSeconds t) * 1000)
              + u32_u64 (div (Time.systemNanoseconds t) 1000000)
          )
          16
      ver = Bits.shift 0x7 12 :: Word.Word64
      rand_a = r Bits..&. 0x0fff
      var = Bits.shift 0x2 62 :: Word.Word64
      rand_b = s Bits..&. 0x3fffffffffffffff
   in UUID.fromWords64 (unix_ts_ms + ver + rand_a) (var + rand_b)