gearhash-1.0.0: src/Data/Digest/GearHash/Types.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
module Data.Digest.GearHash.Types where
import Prelude
import GHC.Generics (Generic)
import Data.Typeable (Typeable)
import Data.Array (Array)
import Data.Array.IArray (array, (!))
import Data.Word
import Data.Bits
import Data.BitVector.LittleEndian (BitVector)
import Data.Ratio ((%))
import qualified Data.BitVector.LittleEndian as BitVector
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import Instances.TH.Lift ()
import Language.Haskell.TH.Syntax
import Data.Maybe
import Data.Monoid (Endo(..))
import Crypto.Random
newtype GearHashTable
= GearHashTable { unGearHashTable :: Array Word8 BitVector }
deriving (Eq, Ord, Generic, Typeable)
instance Show GearHashTable where
showsPrec d (GearHashTable _) = showParen (d > 10) $ showString "GearHashTable _"
instance Lift GearHashTable where
liftTyped tbl = [||unsafeGearHashTableFromByteString $$(liftTyped $ unsafeGearHashTableHashLength tbl) $$(liftTyped $ gearHashTableToByteString tbl)||]
data GearHashState = GearHashState
{ gearHashTable :: GearHashTable
, gearHashCurrent :: {-# UNPACK #-} !BitVector
} deriving (Eq, Ord, Show, Generic, Typeable)
mkGearHashTable :: (Word8 -> BitVector) -> GearHashTable
mkGearHashTable f = GearHashTable $! array (minBound, maxBound) [(w, f w) | w <- [minBound..maxBound]]
{-# NOINLINE defaultGearHashTableFor #-}
defaultGearHashTableFor :: Int -> Maybe GearHashTable
defaultGearHashTableFor l = gearHashTableFromByteString l . fst . randomBytesGenerate (32 * l') $ drgNewTest (fromIntegral l, 0, 0, 0, 0)
where l' = 8 * ceiling (l % 8)
gearHashTableHashLength :: GearHashTable -> Maybe Int
gearHashTableHashLength (GearHashTable arr) = flip appEndo Nothing $ foldMap (Endo . max . Just . finiteBitSize) arr
unsafeGearHashTableHashLength :: GearHashTable -> Int
unsafeGearHashTableHashLength = fromMaybe (error "Could not determine hash length of GearHashTable") . gearHashTableHashLength
unsafeGearHashTableFromByteString :: Int -> ByteString -> GearHashTable
unsafeGearHashTableFromByteString l = fromMaybe (error "Could not convert GearHashTable from ByteString") . gearHashTableFromByteString l
gearHashTableFromByteString :: Int -> ByteString -> Maybe GearHashTable
gearHashTableFromByteString l _ | l <= 0 = Nothing
gearHashTableFromByteString l inpBS = fmap mkGearHashTable $ go 0 (\ix -> error $ "No GearHashTable value available for " ++ show ix) inpBS
where
go :: Int -> (Word8 -> BitVector) -> ByteString -> Maybe (Word8 -> BitVector)
go ix acc bs | ix > 255
, ByteString.null bs
= Just acc
| ix > 255
= Nothing
go ix acc bs = case ByteString.splitAt (ceiling $ l % 8) bs of
(ws, bs')
| ByteString.length ws == ceiling (l % 8)
-> let res = foldr (\(s, w) wacc -> wacc .|. BitVector.fromNumber (fromIntegral l) w `shiftL` s) (BitVector.fromNumber (fromIntegral l) (0 :: Integer)) . zip [l' - 8,l' - 16..] $ ByteString.unpack ws
acc' inp
| inp == fromIntegral ix = res
| otherwise = acc inp
in go (succ ix) acc' bs'
_other -> Nothing
l' = 8 * ceiling (l % 8)
gearHashTableToByteString :: GearHashTable -> ByteString
gearHashTableToByteString inpTbl = fst $ ByteString.unfoldrN (32 * l') build (0, [], inpTbl)
where
build :: (Int, [Word8], GearHashTable) -> Maybe (Word8, (Int, [Word8], GearHashTable))
build (ix, [], _) | ix > 255 = Nothing
build (ix, w:ws, tbl) = Just (w, (ix, ws, tbl))
build (ix, [], tbl) = Just (w1, (succ ix, ws, tbl))
where
w = unGearHashTable tbl ! fromIntegral ix
(w1:ws) = [ BitVector.toUnsignedNumber $ w `shiftR` s
| s <- [l' - 8,l' - 16..0]
]
l' = 8 * ceiling (l % 8)
l = unsafeGearHashTableHashLength inpTbl