module Hans.Checksum(
computeChecksum,
Checksum(..),
PartialChecksum(),
emptyPartialChecksum,
finalizeChecksum,
stepChecksum,
Pair8(..),
) where
import Data.Bits (Bits(shiftL,shiftR,complement,clearBit,(.&.)))
import Data.List (foldl')
import Data.Word (Word8,Word16,Word32)
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Short as Sh
import qualified Data.ByteString.Unsafe as S
data PartialChecksum = PartialChecksum { pcAccum :: !Word32
, pcCarry :: !(Maybe Word8)
} deriving (Eq,Show)
emptyPartialChecksum :: PartialChecksum
emptyPartialChecksum = PartialChecksum
{ pcAccum = 0
, pcCarry = Nothing
}
finalizeChecksum :: PartialChecksum -> Word16
finalizeChecksum pc = complement (fromIntegral (fold32 (fold32 result)))
where
fold32 :: Word32 -> Word32
fold32 x = (x .&. 0xFFFF) + (x `shiftR` 16)
result = case pcCarry pc of
Nothing -> pcAccum pc
Just prev -> stepChecksum (pcAccum pc) prev 0
computeChecksum :: Checksum a => a -> Word16
computeChecksum a = finalizeChecksum (extendChecksum a emptyPartialChecksum)
class Checksum a where
extendChecksum :: a -> PartialChecksum -> PartialChecksum
data Pair8 = Pair8 !Word8 !Word8
instance Checksum Pair8 where
extendChecksum (Pair8 hi lo) = \ PartialChecksum { .. } ->
case pcCarry of
Nothing -> PartialChecksum { pcAccum = stepChecksum pcAccum hi lo
, pcCarry = Nothing }
Just c -> PartialChecksum { pcAccum = stepChecksum pcAccum c hi
, pcCarry = Just lo }
instance Checksum Word16 where
extendChecksum w = \pc -> extendChecksum (Pair8 hi lo) pc
where
lo = fromIntegral w
hi = fromIntegral (w `shiftR` 8)
instance Checksum Word32 where
extendChecksum w = \pc ->
extendChecksum (fromIntegral w :: Word16) $
extendChecksum (fromIntegral (w `shiftR` 16) :: Word16) pc
instance Checksum a => Checksum [a] where
extendChecksum as = \pc -> foldl' (flip extendChecksum) pc as
instance Checksum L.ByteString where
extendChecksum lbs = \pc -> extendChecksum (L.toChunks lbs) pc
instance Checksum Sh.ShortByteString where
extendChecksum shb = \ pc -> extendChecksum (Sh.fromShort shb) pc
instance Checksum S.ByteString where
extendChecksum b pc
| S.null b = pc
| otherwise = case pcCarry pc of
Nothing -> result
Just prev -> extendChecksum (S.tail b) PartialChecksum
{ pcCarry = Nothing
, pcAccum = stepChecksum (pcAccum pc) prev (S.unsafeIndex b 0)
}
where
n' = S.length b
n = clearBit n' 0
result = PartialChecksum
{ pcAccum = loop (pcAccum pc) 0
, pcCarry = carry
}
carry
| odd n' = Just $! S.unsafeIndex b n
| otherwise = Nothing
loop !acc off
| off < n = loop (stepChecksum acc hi lo) (off + 2)
| otherwise = acc
where hi = S.unsafeIndex b off
lo = S.unsafeIndex b (off+1)
stepChecksum :: Word32 -> Word8 -> Word8 -> Word32
stepChecksum acc hi lo = acc + fromIntegral hi `shiftL` 8 + fromIntegral lo