forked from haskell-unordered-containers/hashable
/
SipHash.hs
159 lines (142 loc) · 4.84 KB
/
SipHash.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
{-# LANGUAGE BangPatterns, CPP, GeneralizedNewtypeDeriving, RecordWildCards #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module Data.Hashable.SipHash
(
LE64
, Sip
, fromWord64
, fullBlock
, lastBlock
, finalize
, hashByteString
) where
#include "MachDeps.h"
import Data.Bits ((.|.), (.&.), rotateL, shiftL, xor)
#if MIN_VERSION_base(4,5,0)
import Data.Bits (unsafeShiftL)
#endif
import Data.Word (Word8, Word64)
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Ptr (Ptr, castPtr, plusPtr)
import Data.ByteString.Internal (ByteString(PS), inlinePerformIO)
import Foreign.Storable (peek)
import Numeric (showHex)
newtype LE64 = LE64 { fromLE64 :: Word64 }
deriving (Eq)
instance Show LE64 where
show (LE64 !v) = let s = showHex v ""
in "0x" ++ replicate (16 - length s) '0' ++ s
data Sip = Sip {
v0 :: {-# UNPACK #-} !Word64, v1 :: {-# UNPACK #-} !Word64
, v2 :: {-# UNPACK #-} !Word64, v3 :: {-# UNPACK #-} !Word64
}
fromWord64 :: Word64 -> LE64
#ifndef WORDS_BIGENDIAN
fromWord64 = LE64
#else
#error big endian support TBD
#endif
initState :: (Sip -> r) -> Word64 -> Word64 -> r
initState k k0 k1 = k (Sip s0 s1 s2 s3)
where !s0 = (k0 `xor` 0x736f6d6570736575)
!s1 = (k1 `xor` 0x646f72616e646f6d)
!s2 = (k0 `xor` 0x6c7967656e657261)
!s3 = (k1 `xor` 0x7465646279746573)
sipRound :: (Sip -> r) -> Sip -> r
sipRound k Sip{..} = k (Sip v0_c v1_d v2_c v3_d)
where v0_a = v0 + v1
v2_a = v2 + v3
v1_a = v1 `rotateL` 13
v3_a = v3 `rotateL` 16
v1_b = v1_a `xor` v0_a
v3_b = v3_a `xor` v2_a
v0_b = v0_a `rotateL` 32
v2_b = v2_a + v1_b
v0_c = v0_b + v3_b
v1_c = v1_b `rotateL` 17
v3_c = v3_b `rotateL` 21
v1_d = v1_c `xor` v2_b
v3_d = v3_c `xor` v0_c
v2_c = v2_b `rotateL` 32
fullBlock :: Int -> LE64 -> (Sip -> r) -> Sip -> r
fullBlock c m k st@Sip{..}
| c == 2 = sipRound (sipRound k') st'
| otherwise = runRounds c k' st'
where k' st1@Sip{..} = k st1{ v0 = v0 `xor` fromLE64 m }
st' = st{ v3 = v3 `xor` fromLE64 m }
{-# INLINE fullBlock #-}
runRounds :: Int -> (Sip -> r) -> Sip -> r
runRounds c k = go 0
where go i st
| i < c = sipRound (go (i+1)) st
| otherwise = k st
{-# INLINE runRounds #-}
lastBlock :: Int -> Int -> LE64 -> (Sip -> r) -> Sip -> r
lastBlock !c !len !m k st =
#ifndef WORDS_BIGENDIAN
fullBlock c (LE64 m') k st
#else
#error big endian support TBD
#endif
where m' = fromLE64 m .|. ((fromIntegral len .&. 0xff) `shiftL` 56)
{-# INLINE lastBlock #-}
finalize :: Int -> (Word64 -> r) -> Sip -> r
finalize d k st@Sip{..}
| d == 4 = sipRound (sipRound (sipRound (sipRound k'))) st'
| otherwise = runRounds d k' st'
where k' Sip{..} = k $! v0 `xor` v1 `xor` v2 `xor` v3
st' = st{ v2 = v2 `xor` 0xff }
{-# INLINE finalize #-}
hashByteString :: Int -> Int -> Word64 -> Word64 -> ByteString -> Word64
hashByteString !c !d k0 k1 (PS fp off len) =
inlinePerformIO . withForeignPtr fp $ \basePtr ->
let ptr0 = basePtr `plusPtr` off
scant = len .&. 7
endBlocks = ptr0 `plusPtr` (len - scant)
go !ptr st
| ptr == endBlocks = readLast ptr
| otherwise = do
m <- peekLE64 ptr
fullBlock c m (go (ptr `plusPtr` 8)) st
where
zero !m _ _ = lastBlock c len (LE64 m) (finalize d return) st
one k m p s = do
w <- fromIntegral `fmap` peekByte p
k (m .|. (w `unsafeShiftL` s)) (p `plusPtr` 1) (s+8)
readLast p =
case scant of
0 -> zero 0 p (0::Int)
1 -> one zero 0 p 0
2 -> one (one zero) 0 p 0
3 -> one (one (one zero)) 0 p 0
4 -> one (one (one (one zero))) 0 p 0
5 -> one (one (one (one (one zero)))) 0 p 0
6 -> one (one (one (one (one (one zero))))) 0 p 0
_ -> one (one (one (one (one (one (one zero)))))) 0 p 0
in initState (go ptr0) k0 k1
peekByte :: Ptr Word8 -> IO Word8
peekByte = peek
peekLE64 :: Ptr Word8 -> IO LE64
#if defined(x86_64_HOST_ARCH) || defined(i386_HOST_ARCH)
-- platforms on which unaligned loads are legal and usually fast
peekLE64 p = LE64 `fmap` peek (castPtr p)
#else
peekLE64 p = do
let peek8 d = fromIntegral `fmap` peekByte (p `plusPtr` d)
b0 <- peek8 0
b1 <- peek8 1
b2 <- peek8 2
b3 <- peek8 3
b4 <- peek8 4
b5 <- peek8 5
b6 <- peek8 6
b7 <- peek8 7
let !w = (b7 `shiftL` 56) .|. (b6 `shiftL` 48) .|. (b5 `shiftL` 40) .|.
(b4 `shiftL` 32) .|. (b3 `shiftL` 24) .|. (b2 `shiftL` 16) .|.
(b1 `shiftL` 8) .|. b0
return (fromWord64 w)
#endif
#if !MIN_VERSION_base(4,5,0)
unsafeShiftL :: Word64 -> Int -> Word64
unsafeShiftL = shiftL
#endif