packages feed

mysql-haskell-1.5.0: src/Database/MySQL/BinLogProtocol/CRC32.hs

{-|
Module      : Database.MySQL.BinLogProtocol.CRC32
Description : The checksum of binlog events
License     : BSD
Maintainer  : hi@jappie.me
Stability   : experimental
Portability : PORTABLE

The CRC32 that MySQL and MariaDB end each binlog event with when
@binlog_checksum@ is @CRC32@.
-}

module Database.MySQL.BinLogProtocol.CRC32
    ( crc32
    ) where

import           Data.Array.Unboxed    (UArray, listArray, (!))
import           Data.Bits             (complement, shiftR, xor, (.&.))
import qualified Data.ByteString.Lazy  as L
import           Data.Word             (Word32, Word8)
import qualified Unwitch.Convert.Word8 as Word8

-- Decision: our own CRC32, as no dependency has one: crypton and ram offer
-- cryptographic and FNV/SipHash digests only, and zlib, whose Haskell binding
-- exports no crc32 anyway, is not a dependency. The digest package would add
-- a C library for these few lines.

-- | The CRC-32 of IEEE 802.3 and zlib (reflected polynomial 0xEDB88320,
-- initial and final XOR 0xFFFFFFFF): @crc32 "123456789" == 0xCBF43926@.
--
-- @since 1.5.0
crc32 :: L.ByteString -> Word32
crc32 bytes = complement (L.foldl' crc32Step 0xFFFFFFFF bytes)

crc32Step :: Word32 -> Word8 -> Word32
crc32Step crc byte =
    (crc32Table ! ((crc `xor` Word8.toWord32 byte) .&. 0xFF)) `xor` (crc `shiftR` 8)

-- Decision: a UArray indexed by Word32, so a lookup takes the masked CRC as
-- it is. A Vector needs an Int index, and unwitch converts Word32 to Int only
-- as a Maybe, whose Nothing could not happen here but would need handling.

-- | The CRC of each byte value, so that a byte costs one lookup instead of
-- eight shifts.
crc32Table :: UArray Word32 Word32
crc32Table = listArray (0, 255) (map crc32OfByte [0 .. 255])

crc32OfByte :: Word32 -> Word32
crc32OfByte byte = iterate reflectedShift byte !! 8

reflectedShift :: Word32 -> Word32
reflectedShift crc =
    if crc .&. 1 /= 0 then 0xEDB88320 `xor` (crc `shiftR` 1) else crc `shiftR` 1