packages feed

mono-hash-0.1.0.0: src/Data/Hash/Mono/HalfSipHash.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE TypeApplications #-}

-- | HalfSipHash-@c@-@d@ for any 'MonoFoldable' container.
module Data.Hash.Mono.HalfSipHash (
    HalfSipHashData,
    HalfSipHashResult,
    halfSipHash,
)
where

import Data.Bits (Bits (rotateL, shiftL, shiftR, xor, (.&.), (.|.)))
import Data.Hash.Mono.Internal (byteSwap32)
import Data.Hash.Mono.Internal.ByteFoldable (ByteFoldable (..))
import Data.MonoTraversable (Element, MonoFoldable (ofoldl'))
import Data.Word (Word32, Word64, Word8)

-- | Result types that 'halfSipHash' can produce.
class HalfSipHashResult a where
    -- | Adjust the initial state for the selected result type.
    initialize :: HalfSipData -> HalfSipData

    -- | Run the finalization rounds and return the hash.
    finalization :: Word -> HalfSipData -> a

instance HalfSipHashResult Word32 where
    initialize :: HalfSipData -> HalfSipData
    initialize = id

    finalization :: Word -> HalfSipData -> Word32
    finalization d (HalfSipData !v0 !v1 !v2 !v3) =
        let v2' = v2 `xor` 0xff
            (HalfSipData !_ v1' _ !v3') = halfSipRounds d (HalfSipData v0 v1 v2' v3)
         in v1' `xor` v3'

instance HalfSipHashResult Word64 where
    initialize :: HalfSipData -> HalfSipData
    initialize (HalfSipData !v0 !v1 !v2 !v3) = HalfSipData v0 (v1 `xor` 0xee) v2 v3

    finalization :: Word -> HalfSipData -> Word64
    finalization d (HalfSipData !v0 !v1 !v2 !v3) =
        let v2' = v2 `xor` 0xee
            (HalfSipData !v0' !v1' !v2'' !v3') = halfSipRounds d (HalfSipData v0 v1 v2' v3)
            word0 = v1' `xor` v3'
            v1'' = v1' `xor` 0xdd
            (HalfSipData _ !v1''' _ !v3'') = halfSipRounds d (HalfSipData v0' v1'' v2'' v3')
            word1 = v1''' `xor` v3''
         in (fromIntegral word1 `shiftL` 32) + fromIntegral word0

-- | Constraint for containers hashed with HalfSipHash.
type HalfSipHashData a = (MonoFoldable a, ByteFoldable (Element a))

-- | HalfSipHash internal state, consisting of four 32-bit words.
data HalfSipData = HalfSipData
    { hv0 :: {-# UNPACK #-} !Word32
    , hv1 :: {-# UNPACK #-} !Word32
    , hv2 :: {-# UNPACK #-} !Word32
    , hv3 :: {-# UNPACK #-} !Word32
    }

-- | Internal HalfSipHash accumulator.
data HalfSipStream = HalfSipStream
    { hsData :: {-# UNPACK #-} !HalfSipData
    -- ^ HalfSipHash state.
    , hsWord :: {-# UNPACK #-} !Word32
    -- ^ Input bytes buffered so far, in little-endian order.
    , hsCount :: {-# UNPACK #-} !Int
    -- ^ Number of bytes buffered in 'hsWord' (0-3).
    , hsLen :: {-# UNPACK #-} !Word8
    -- ^ Total number of input bytes, modulo 256.
    }

{- | Compute HalfSipHash-@c@-@d@ of a monomorphic container.

@c@ and @d@ are the compression and finalization round counts;
HalfSipHash-2-4 is the standard variant.

Byte order: the 'Word64' key holds the 8 reference key bytes in
big-endian order, and each 32-bit half is then read as a little-endian
word. The result is the reference implementation's output word: a
'Word32' result is the 32-bit tag. A 'Word64' result packs the first
32-bit tag word in the low half and the second in the high half; its
little-endian serialization is the 8-byte reference digest.

Example:
@
halfSipHash \@'Word32' 2 4 0xc0ffee \"hello\"
@
-}
halfSipHash :: forall result mono. (HalfSipHashData mono, HalfSipHashResult result) => Word -> Word -> Word64 -> mono -> result
halfSipHash c d key mono =
    case ofoldl' (foldlBytes (step c)) initStream mono of
        HalfSipStream{hsData = state, hsWord = word, hsLen = len} ->
            finalization d (compress c state (word .|. (fromIntegral len `shiftL` 24)))
  where
    initData :: HalfSipData
    initData =
        HalfSipData
            { hv0 = k0
            , hv1 = k1
            , hv2 = k0 `xor` 0x6c796765
            , hv3 = k1 `xor` 0x74656462
            }
      where
        k0 :: Word32
        k0 = byteSwap32 . fromIntegral $ key `shiftR` 32

        k1 :: Word32
        k1 = byteSwap32 . fromIntegral $ key .&. 0xffffffff

    initStream :: HalfSipStream
    initStream =
        HalfSipStream
            { hsData = initialize @result initData
            , hsWord = 0
            , hsCount = 0
            , hsLen = 0
            }
{-# INLINEABLE halfSipHash #-}

step :: Word -> HalfSipStream -> Word8 -> HalfSipStream
step c HalfSipStream{hsData = d, hsWord = word, hsCount = n, hsLen = len} !b =
    let word' = word .|. (fromIntegral b `shiftL` (8 * n))
        len' = len + 1
     in if n == 3
            then HalfSipStream{hsData = compress c d word', hsWord = 0, hsCount = 0, hsLen = len'}
            else HalfSipStream{hsData = d, hsWord = word', hsCount = n + 1, hsLen = len'}
{-# INLINE step #-}

compress :: Word -> HalfSipData -> Word32 -> HalfSipData
compress c (HalfSipData !v0 !v1 !v2 !v3) m =
    let v3' = v3 `xor` m
        (HalfSipData !v0' !v1' !v2' !v3'') = halfSipRounds c (HalfSipData v0 v1 v2 v3')
        v0'' = v0' `xor` m
     in HalfSipData v0'' v1' v2' v3''
{-# INLINE compress #-}

halfSipRound :: HalfSipData -> HalfSipData
halfSipRound (HalfSipData !v0 !v1 !v2 !v3) =
    let v0' = v0 + v1
        v1' = v1 `rotateL` 5
        v1'' = v1' `xor` v0'
        v0'' = v0' `rotateL` 16
        v2' = v2 + v3
        v3' = v3 `rotateL` 8
        v3'' = v3' `xor` v2'
        v0''' = v0'' + v3''
        v3''' = v3'' `rotateL` 7
        v3'''' = v3''' `xor` v0'''
        v2'' = v2' + v1''
        v1''' = v1'' `rotateL` 13
        v1'''' = v1''' `xor` v2''
        v2''' = v2'' `rotateL` 16
     in HalfSipData v0''' v1'''' v2''' v3''''
{-# INLINE halfSipRound #-}

halfSipRoundN :: Word -> HalfSipData -> HalfSipData
halfSipRoundN 0 !x = x
halfSipRoundN n !x = halfSipRoundN (n - 1) $! halfSipRound x

-- | Unrolled variant of 'halfSipRoundN' for the small counts used in practice.
halfSipRounds :: Word -> HalfSipData -> HalfSipData
halfSipRounds 1 !x = halfSipRound x
halfSipRounds 2 !x = halfSipRound (halfSipRound x)
halfSipRounds 3 !x = halfSipRound (halfSipRound (halfSipRound x))
halfSipRounds 4 !x = halfSipRound (halfSipRound (halfSipRound (halfSipRound x)))
halfSipRounds n !x = halfSipRoundN n x
{-# INLINE halfSipRounds #-}