-- | Intended for qualified import:
--
-- > import Codec.Compression.SnappyC.Internal.Checksum (Checksum)
-- > import Codec.Compression.SnappyC.Internal.Checksum qualified as Checksum

module Codec.Compression.SnappyC.Internal.Checksum
  ( -- * Type
    Checksum -- Opaque

    -- ** Calculating checksums
  , calculate

    -- ** Encoding and decoding checksums
  , encode
  , decode
  ) where

import Data.ByteString qualified as Strict (ByteString)
import Data.ByteString qualified as BS.Strict

import Data.Bits
import Data.Digest.CRC32C
import Data.Word

-- | Masked CRC32C checksum
newtype Checksum = Checksum { Checksum -> Word32
getChecksum :: Word32 }
  deriving newtype (Int -> Checksum -> ShowS
[Checksum] -> ShowS
Checksum -> String
(Int -> Checksum -> ShowS)
-> (Checksum -> String) -> ([Checksum] -> ShowS) -> Show Checksum
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Checksum -> ShowS
showsPrec :: Int -> Checksum -> ShowS
$cshow :: Checksum -> String
show :: Checksum -> String
$cshowList :: [Checksum] -> ShowS
showList :: [Checksum] -> ShowS
Show, Checksum -> Checksum -> Bool
(Checksum -> Checksum -> Bool)
-> (Checksum -> Checksum -> Bool) -> Eq Checksum
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Checksum -> Checksum -> Bool
== :: Checksum -> Checksum -> Bool
$c/= :: Checksum -> Checksum -> Bool
/= :: Checksum -> Checksum -> Bool
Eq)

-- | Calculate
calculate :: Strict.ByteString -> Checksum
calculate :: ByteString -> Checksum
calculate =
    Word32 -> Checksum
Checksum (Word32 -> Checksum)
-> (ByteString -> Word32) -> ByteString -> Checksum
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Word32
maskChecksum (Word32 -> Word32)
-> (ByteString -> Word32) -> ByteString -> Word32
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Word32
forall a. CRC32C a => a -> Word32
crc32c
  where
    maskChecksum :: Word32 -> Word32
    maskChecksum :: Word32 -> Word32
maskChecksum = (Word32 -> Word32 -> Word32
forall a. Num a => a -> a -> a
+ Word32
0xa282ead8) (Word32 -> Word32) -> (Word32 -> Word32) -> Word32 -> Word32
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`rotateR` Int
15)

-- | Encode
encode :: Checksum -> Strict.ByteString
encode :: Checksum -> ByteString
encode =
    [Word8] -> ByteString
BS.Strict.pack ([Word8] -> ByteString)
-> (Checksum -> [Word8]) -> Checksum -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> [Word8]
toLittleEndian (Word32 -> [Word8]) -> (Checksum -> Word32) -> Checksum -> [Word8]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Checksum -> Word32
getChecksum
  where
    toLittleEndian :: Word32 -> [Word8]
    toLittleEndian :: Word32 -> [Word8]
toLittleEndian Word32
s =
      [ Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral          (Word32
s Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.&. Word32
0x000000ff)
      , Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Word8) -> Word32 -> Word8
forall a b. (a -> b) -> a -> b
$ Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
shiftR (Word32
s Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.&. Word32
0x0000ff00) Int
8
      , Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Word8) -> Word32 -> Word8
forall a b. (a -> b) -> a -> b
$ Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
shiftR (Word32
s Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.&. Word32
0x00ff0000) Int
16
      , Word32 -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Word8) -> Word32 -> Word8
forall a b. (a -> b) -> a -> b
$ Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
shiftR (Word32
s Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.&. Word32
0xff000000) Int
24
      ]

-- | Decode
--
-- __Precondition:__ Input must be exactly 4 bytes.
decode :: Strict.ByteString -> Checksum
decode :: ByteString -> Checksum
decode =
    Word32 -> Checksum
Checksum (Word32 -> Checksum)
-> (ByteString -> Word32) -> ByteString -> Checksum
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Word8] -> Word32
fromLittleEndian ([Word8] -> Word32)
-> (ByteString -> [Word8]) -> ByteString -> Word32
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> [Word8]
BS.Strict.unpack
  where
    fromLittleEndian :: [Word8] -> Word32
    fromLittleEndian :: [Word8] -> Word32
fromLittleEndian [Word8
b1, Word8
b2, Word8
b3, Word8
b4] =
            Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
b1
        Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
b2 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftL` Int
8
        Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
b3 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftL` Int
16
        Word32 -> Word32 -> Word32
forall a. Bits a => a -> a -> a
.|. Word8 -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
b4 Word32 -> Int -> Word32
forall a. Bits a => a -> Int -> a
`shiftL` Int
24
    fromLittleEndian [Word8]
_ = String -> Word32
forall a. HasCallStack => String -> a
error String
"Checksum.decode: precondition violated"