ulid (empty) → 0.1.0.0
raw patch · 10 files changed
+365/−0 lines, 10 filesdep +basedep +binarydep +bytestringsetup-changed
Dependencies added: base, binary, bytestring, crockford, crypto-api, hspec, random, time, ulid
Files
- LICENSE +30/−0
- README.md +74/−0
- Setup.hs +2/−0
- app/Main.hs +34/−0
- src/Data/ULID.hs +43/−0
- src/Data/ULID/Crockford.hs +19/−0
- src/Data/ULID/Random.hs +62/−0
- src/Data/ULID/TimeStamp.hs +30/−0
- test/Spec.hs +3/−0
- ulid.cabal +68/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Steve Kollmansberger (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Steve Kollmansberger nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,74 @@+# Ulid implementation in Haskell++Lexicographically sortable, 128-bit identifier with 48-bit timestamp and 80 random bits.+Canonically encoded as a 26 character string, as opposed to the 36 character UUID.++Original implementation and spec: https://github.com/alizain/ulid/+++# Universally Unique Lexicographically Sortable Identifier++UUID can be suboptimal for many uses-cases because:++- It isn't the most character efficient way of encoding 128 bits of randomness+- UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address+- UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures+- UUID v4 provides no other information than randomness which can cause fragmentation in many data structures++Instead, herein is proposed ULID:++- 128-bit compatibility with UUID+- 1.21e+24 unique ULIDs per millisecond+- Lexicographically sortable!+- Canonically encoded as a 26 character string, as opposed to the 36 character UUID+- Uses Crockford's base32 for better efficiency and readability (5 bits per character)+- Case insensitive+- No special characters (URL safe)+++## Usage++A simple usage example:++````haskell+module Main where++import Data.ULID++main :: IO ()+main = do+ -- Derive a ULID using the current time and default random number generator+ ulid1 <- getULID+ print ulid1++ -- Derive a ULID using a specified time and default random number generator+ ulid2 <- getULIDTime 1469918176.385 -- POSIX Time, specified to the millisecond+ print ulid2+````++As per the spec, it is also possible to use a cryptographically-secure random number generator to contribute the randomness. However, the programmer must manage the generator on their own. Example:+++````haskell+module Main where++import Data.ULID++import qualified Crypto.Random as CR+import qualified Data.ULID.Random as UR+import qualified Data.ULID.TimeStamp as TS++main :: IO ()+main = do + -- This default instantiation may not be sufficiently secure, see the docs + -- https://hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html+ g <- (CR.newGenIO :: IO CR.SystemRandom)++ -- Generate time stamp from current time+ t <- TS.getULIDTimeStamp+ + let ulid3 = case UR.mkCryptoULIDRandom g of+ Left err -> error $ show err+ Right (rnd, g2) -> ULID t rnd -- use g2, etc, to continue generating secure ULIDs+ print ulid3+````
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,34 @@+module Main where++import Data.ULID++-- These imports only needed for cryptographically secure ULID+import qualified Crypto.Random as CR+import qualified Data.ULID.Random as UR+import qualified Data.ULID.TimeStamp as TS++main :: IO ()+main = do+ -- Derive a ULID using the current time and default random number generator+ ulid1 <- getULID+ print ulid1++ -- Derive a ULID using a specified time and default random number generator+ ulid2 <- getULIDTime 1469918176.385 -- POSIX Time, specified to the millisecond+ print ulid2+++ -- Below only for crypographically secure ULID example++ -- This default instantiation may not be sufficiently secure, see the docs + -- https://hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html+ g <- (CR.newGenIO :: IO CR.SystemRandom)++ -- Generate time stamp from current time+ t <- TS.getULIDTimeStamp++ let ulid3 = case UR.mkCryptoULIDRandom g of+ Left err -> error $ show err+ Right (rnd, g2) -> ULID t rnd -- use g2, etc, to continue generating secure ULIDs+ print ulid3+
+ src/Data/ULID.hs view
@@ -0,0 +1,43 @@+module Data.ULID ( + ULID(..), + getULIDTime, + getULID +) where + +import Data.Time.Clock.POSIX + +import Data.ULID.Random +import Data.ULID.TimeStamp + +data ULID = ULID { + timeStamp :: !ULIDTimeStamp, + random :: !ULIDRandom + } + deriving (Eq) + +-- | Derive a ULID using a specified time and default random number generator +getULIDTime :: POSIXTime -- ^ The specified UNIX time (seconds) to millisecond precision, e.g. 1469918176.385 + -> IO ULID +getULIDTime t = do + let t' = mkULIDTimeStamp t + r <- getULIDRandom + return $ ULID t' r + +-- | Derive a ULID using the current time and default random number generator +getULID :: IO ULID +getULID = do + t <- getULIDTimeStamp + r <- getULIDRandom + return $ ULID t r + +instance Ord ULID where + compare (ULID ts1 _) (ULID ts2 _) = compare ts1 ts2 + +instance Show ULID where + show (ULID ts bytes) = (show ts) ++ (show bytes) + +instance Read ULID where + readsPrec _ str = do + (ts, str2) <- reads str + (rn, str3) <- reads str2 + return (ULID ts rn, str3)
+ src/Data/ULID/Crockford.hs view
@@ -0,0 +1,19 @@+module Data.ULID.Crockford (encode, decode) where + +import qualified Codec.Crockford as CR +import Text.Read + +-- source: https://stackoverflow.com/a/29153602 +leftpad m xs = replicate (m - length ys) '0' ++ ys + where ys = take m xs + +encode :: Int -> Integer -> String +encode width = (leftpad width).(CR.encode) + +decode :: Int -> ReadS Integer +decode width str | length str >= width = let + (crock, remainder) = splitAt width str + in case CR.decode crock of + Nothing -> [] + Just c -> [(c, remainder)] + | otherwise = []
+ src/Data/ULID/Random.hs view
@@ -0,0 +1,62 @@+module Data.ULID.Random ( + ULIDRandom, + mkCryptoULIDRandom, + mkULIDRandom, + getULIDRandom +) where + +import Crypto.Random +import Data.Binary +import Data.Bits +import qualified Data.ByteString as BS +import Data.List (foldl', unfoldr) +import Data.Word +import System.Random + + +import qualified Data.ULID.Crockford as CR + + +newtype ULIDRandom = ULIDRandom BS.ByteString + deriving (Eq) + +-- | Generate a ULID Random based on a cryptographically secure random number generator. +-- | see: https://hackage.haskell.org/package/crypto-api-0.13.2/docs/Crypto-Random.html +mkCryptoULIDRandom :: CryptoRandomGen g => g -> Either GenError (ULIDRandom, g) +mkCryptoULIDRandom g = do + (b, g2) <- genBytes 10 g + return (ULIDRandom b, g2) + +-- | Generate a ULID Random based on a standard random number generator. +-- | see: https://hackage.haskell.org/package/random-1.1/docs/System-Random.html +mkULIDRandom :: RandomGen g => g -> (ULIDRandom, g) +mkULIDRandom g = let + (g1, g2) = split g + genbytes = (BS.pack) . take 10 . randoms + in (ULIDRandom $ genbytes g, g2) + +-- | Generate a ULID Random based on the global random number generator. +getULIDRandom :: IO ULIDRandom +getULIDRandom = fst <$> mkULIDRandom <$> newStdGen -- Note: the call to newStdGen splits the generator, so this is safe to call multiple times + + +-- source: http://hackage.haskell.org/package/binary-0.8.5.1/docs/src/Data-Binary-Class.html#line-311 +-- +-- Fold and unfold an Integer to and from a list of its bytes +-- +unroll :: Integer -> [Word8] +unroll = unfoldr step + where + step 0 = Nothing + step i = Just (fromIntegral i, i `shiftR` 8) + +roll :: [Word8] -> Integer +roll = foldl' unstep 0 . reverse + where + unstep a b = a `shiftL` 8 .|. fromIntegral b + +instance Show ULIDRandom where + show (ULIDRandom r) = (CR.encode) 16.roll.(BS.unpack) $ r + +instance Read ULIDRandom where + readsPrec _ = map (\(c,r)->(ULIDRandom $ (BS.pack) $ unroll c, r)) . (CR.decode) 16
+ src/Data/ULID/TimeStamp.hs view
@@ -0,0 +1,30 @@+module Data.ULID.TimeStamp ( + ULIDTimeStamp, + mkULIDTimeStamp, + getULIDTimeStamp +) where + +import Data.Time.Clock +import Data.Time.Clock.POSIX + +import Data.ULID.Crockford + +-- UNIX time in milliseconds +newtype ULIDTimeStamp = ULIDTimeStamp Integer + deriving (Eq, Ord) + +-- | Generate a ULID Timestamp based on a specified time +mkULIDTimeStamp :: POSIXTime -- ^ The specified UNIX time (seconds) to millisecond precision, e.g. 1469918176.385 + -> ULIDTimeStamp +mkULIDTimeStamp = ULIDTimeStamp . round . (*1000) + +-- | Generate a ULID Timestamp based on current system UNIX time +getULIDTimeStamp :: IO ULIDTimeStamp +getULIDTimeStamp = mkULIDTimeStamp <$> getPOSIXTime + +instance Show ULIDTimeStamp where + show (ULIDTimeStamp i) = encode 10 i + +instance Read ULIDTimeStamp where + readsPrec _ = map (\(c,r)->(ULIDTimeStamp c, r)) . decode 10 +
+ test/Spec.hs view
@@ -0,0 +1,3 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}+-- see https://github.com/hspec/hspec-example/blob/master/strip.cabal+-- runhaskell -isrc -itest test/Spec.hs
+ ulid.cabal view
@@ -0,0 +1,68 @@+name: ulid+version: 0.1.0.0+synopsis: Implementation of ULID, lexicographically sortable unique identifiers+description: Implementation of alizain's ULID identifier (https://github.com/alizain/ulid ).++ UUID can be suboptimal for many uses-cases because:++ It isn't the most character efficient way of encoding 128 bits of randomness+ UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address+ UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures+ UUID v4 provides no other information than randomness which can cause fragmentation in many data structures+ Instead, herein is proposed ULID:++ 128-bit compatibility with UUID+ 1.21e+24 unique ULIDs per millisecond+ Lexicographically sortable!+ Canonically encoded as a 26 character string, as opposed to the 36 character UUID+ Uses Crockford's base32 for better efficiency and readability (5 bits per character)+ Case insensitive+ No special characters (URL safe)+homepage: https://github.com/steven777400/ulid+license: BSD3+license-file: LICENSE+author: Steve Kollmansberger+maintainer: steve@kolls.net+copyright: 2017 Steve Kollmansberger+category: Data+build-type: Simple+extra-source-files: README.md+cabal-version: >=1.10++library+ hs-source-dirs: src+ exposed-modules: Data.ULID+ , Data.ULID.Crockford+ , Data.ULID.Random+ , Data.ULID.TimeStamp+ build-depends: base >= 4.7 && < 5+ , crockford+ , time+ , crypto-api+ , random+ , bytestring+ , binary+ default-language: Haskell2010++executable ulid-exe+ hs-source-dirs: app+ main-is: Main.hs+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends: base+ , ulid+ , crypto-api+ default-language: Haskell2010++test-suite ulid-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ build-depends: base+ , hspec+ , ulid+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/steven777400/ulid.git