    -- |
    -- Module      : Crypto.Cipher.Types.Modes
    -- License     : BSD-style
    -- Maintainer  : Vincent Hanquez <vincent@snarc.org>
    -- Stability   : Stable
    -- Portability : Excellent
    --
    -- block cipher modes immutable interfaces
    --
    module Crypto.Cipher.Types.OfIO
        (
        -- * ECB
          ecbEncryptOfIO
        , ecbDecryptOfIO
    {-
        -- * CBC
        , cbcEncryptOfIO
        , cbcDecryptOfIO
        -- * CFB
        , cfbEncryptOfIO
        , cfbDecryptOfIO
        , cfb8EncryptOfIO
        , cfb8DecryptOfIO
        -- * CTR
        , ctrCombineOfIO
        -- * XTS
        , xtsEncryptOfIO
        , xtsDecryptOfIO
    -}
        ) where

    import Data.ByteString (ByteString)
    import qualified Data.ByteString as B
    import qualified Data.ByteString.Internal as B
    import Data.Byteable
    --import Crypto.Cipher.Types.Base
    import Crypto.Cipher.Types.Block
    import Crypto.Cipher.Types.BlockIO
    --import Foreign.Storable (poke)
    --import Foreign.Ptr

    isBlockSized :: (BlockCipher cipher, BlockCipherIO cipher) => cipher -> Int -> Bool
    isBlockSized :: forall cipher.
    (BlockCipher cipher, BlockCipherIO cipher) =>
    cipher -> Int -> Bool
    isBlockSized cipher
    cipher Int
    bsLen = (Int
    bsLen Int -> Int -> Int
    forall a. Integral a => a -> a -> a
    `mod` cipher -> Int
    forall cipher. BlockCipher cipher => cipher -> Int
    blockSize cipher
    cipher) Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    0

    notBlockSized :: (BlockCipher cipher, BlockCipherIO cipher) => cipher -> a
    notBlockSized :: forall cipher a.
    (BlockCipher cipher, BlockCipherIO cipher) =>
    cipher -> a
    notBlockSized = cipher -> a
    forall a. HasCallStack => a
    undefined

    withDest :: BlockCipherIO cipher
             => cipher
             -> ByteString
             -> (PtrDest -> PtrSource -> BufferLength -> IO ())
             -> ByteString
    withDest :: forall cipher.
    BlockCipherIO cipher =>
    cipher
    -> ByteString
    -> (PtrDest -> PtrDest -> BufferLength -> IO ())
    -> ByteString
    withDest cipher
    cipher ByteString
    bs PtrDest -> PtrDest -> BufferLength -> IO ()
    f
        | ByteString -> Bool
    B.null ByteString
    bs                     = ByteString
    B.empty
        | Bool -> Bool
    not (cipher -> Int -> Bool
    forall cipher.
    (BlockCipher cipher, BlockCipherIO cipher) =>
    cipher -> Int -> Bool
    isBlockSized cipher
    cipher Int
    len) = cipher -> ByteString
    forall cipher a.
    (BlockCipher cipher, BlockCipherIO cipher) =>
    cipher -> a
    notBlockSized cipher
    cipher
        | Bool
    otherwise                     =
            Int -> (PtrDest -> IO ()) -> ByteString
    B.unsafeCreate Int
    len ((PtrDest -> IO ()) -> ByteString)
    -> (PtrDest -> IO ()) -> ByteString
    forall a b. (a -> b) -> a -> b
    $ \PtrDest
    dst ->
            ByteString -> (PtrDest -> IO ()) -> IO ()
    forall a b. Byteable a => a -> (PtrDest -> IO b) -> IO b
    forall b. ByteString -> (PtrDest -> IO b) -> IO b
    withBytePtr ByteString
    bs     ((PtrDest -> IO ()) -> IO ()) -> (PtrDest -> IO ()) -> IO ()
    forall a b. (a -> b) -> a -> b
    $ \PtrDest
    src ->
            PtrDest -> PtrDest -> BufferLength -> IO ()
    f PtrDest
    dst PtrDest
    src (Int -> BufferLength
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral Int
    len)
      where len :: Int
    len = ByteString -> Int
    B.length ByteString
    bs

    {-
    withDestIV :: BlockCipherIO cipher
               => cipher
               -> IV cipher
               -> ByteString
               -> (PtrIV -> PtrDest -> PtrSource -> BufferLength -> IO ())
               -> ByteString
    withDestIV cipher (IV iv) bs f
        | B.null bs                     = B.empty
        | not (isBlockSized cipher len) = notBlockSized cipher
        | otherwise                     =
            B.unsafeCreate len $ \dst   ->
            withBytePtr iv     $ \ivPtr ->
            withBytePtr bs     $ \src   ->
            f ivPtr dst src (fromIntegral len)
      where len = B.length bs

    withDestIVAnySize :: BlockCipherIO cipher
                      => IV cipher
                      -> ByteString
                      -> (PtrIV -> PtrDest -> PtrSource -> BufferLength -> IO ())
                      -> ByteString
    withDestIVAnySize (IV iv) bs f
        | B.null bs = B.empty
        | otherwise =
            B.unsafeCreate len $ \dst   ->
            withBytePtr iv     $ \ivPtr ->
            withBytePtr bs     $ \src   ->
            f ivPtr dst src (fromIntegral len)
      where len = B.length bs
    -}

    -- | Encrypt using the ECB mode.
    --
    -- input need to be a multiple of the blocksize
    ecbEncryptOfIO :: BlockCipherIO cipher => cipher -> ByteString -> ByteString
    ecbEncryptOfIO :: forall cipher.
    BlockCipherIO cipher =>
    cipher -> ByteString -> ByteString
    ecbEncryptOfIO cipher
    cipher ByteString
    bs = cipher
    -> ByteString
    -> (PtrDest -> PtrDest -> BufferLength -> IO ())
    -> ByteString
    forall cipher.
    BlockCipherIO cipher =>
    cipher
    -> ByteString
    -> (PtrDest -> PtrDest -> BufferLength -> IO ())
    -> ByteString
    withDest cipher
    cipher ByteString
    bs ((PtrDest -> PtrDest -> BufferLength -> IO ()) -> ByteString)
    -> (PtrDest -> PtrDest -> BufferLength -> IO ()) -> ByteString
    forall a b. (a -> b) -> a -> b
    $ cipher -> PtrDest -> PtrDest -> BufferLength -> IO ()
    forall cipher.
    BlockCipherIO cipher =>
    cipher -> PtrDest -> PtrDest -> BufferLength -> IO ()
    ecbEncryptMutable cipher
    cipher

    -- | Decrypt using the ECB mode.
    --
    -- input need to be a multiple of the blocksize
    ecbDecryptOfIO :: BlockCipherIO cipher => cipher -> ByteString -> ByteString
    ecbDecryptOfIO :: forall cipher.
    BlockCipherIO cipher =>
    cipher -> ByteString -> ByteString
    ecbDecryptOfIO cipher
    cipher ByteString
    bs = cipher
    -> ByteString
    -> (PtrDest -> PtrDest -> BufferLength -> IO ())
    -> ByteString
    forall cipher.
    BlockCipherIO cipher =>
    cipher
    -> ByteString
    -> (PtrDest -> PtrDest -> BufferLength -> IO ())
    -> ByteString
    withDest cipher
    cipher ByteString
    bs ((PtrDest -> PtrDest -> BufferLength -> IO ()) -> ByteString)
    -> (PtrDest -> PtrDest -> BufferLength -> IO ()) -> ByteString
    forall a b. (a -> b) -> a -> b
    $ cipher -> PtrDest -> PtrDest -> BufferLength -> IO ()
    forall cipher.
    BlockCipherIO cipher =>
    cipher -> PtrDest -> PtrDest -> BufferLength -> IO ()
    ecbEncryptMutable cipher
    cipher

    {-
    -- | encrypt using the CBC mode.
    --
    -- input need to be a multiple of the blocksize
    cbcEncryptOfIO :: BlockCipherIO cipher => cipher -> IV cipher -> ByteString -> ByteString
    cbcEncryptOfIO cipher iv bs = withDestIV cipher iv bs $ cbcEncryptMutable cipher

    -- | decrypt using the CBC mode.
    --
    -- input need to be a multiple of the blocksize
    cbcDecryptOfIO :: BlockCipherIO cipher => cipher -> IV cipher -> ByteString -> ByteString
    cbcDecryptOfIO cipher iv bs = withDestIV cipher iv bs $ cbcDecryptMutable cipher

    -- | encrypt using the CFB mode.
    --
    -- input need to be a multiple of the blocksize
    cfbEncryptOfIO :: BlockCipherIO cipher => cipher -> IV cipher -> ByteString -> ByteString
    cfbEncryptOfIO cipher iv bs = withDestIV cipher iv bs $ cfbEncryptMutable cipher

    -- | decrypt using the CFB mode.
    --
    -- input need to be a multiple of the blocksize
    cfbDecryptOfIO :: BlockCipherIO cipher => cipher -> IV cipher -> ByteString -> ByteString
    cfbDecryptOfIO cipher iv bs = withDestIV cipher iv bs $ cfbDecryptMutable cipher

    -- | combine using the CTR mode.
    --
    -- CTR mode produce a stream of randomized data that is combined
    -- (by XOR operation) with the input stream.
    --
    -- encryption and decryption are the same operation.
    --
    -- input can be of any size
    ctrCombineOfIO :: BlockCipherIO cipher => cipher -> IV cipher -> ByteString -> ByteString
    ctrCombineOfIO cipher iv bs = withDestIVAnySize iv bs $ cfbDecryptMutable cipher

    -- | encrypt using the XTS mode.
    --
    -- input need to be a multiple of the blocksize
    xtsEncryptOfIO :: BlockCipherIO cipher => (cipher, cipher) -> IV cipher -> DataUnitOffset -> ByteString -> ByteString
    xtsEncryptOfIO ciphers@(c1,_) iv ofs bs = withDestIV c1 iv bs $ \ivPtr -> xtsEncryptMutable ciphers ivPtr ofs

    -- | decrypt using the XTS mode.
    --
    -- input need to be a multiple of the blocksize
    xtsDecryptOfIO :: BlockCipherIO cipher => (cipher, cipher) -> IV cipher -> DataUnitOffset -> ByteString -> ByteString
    xtsDecryptOfIO ciphers@(c1,_) iv ofs bs = withDestIV c1 iv bs $ \ivPtr -> xtsDecryptMutable ciphers ivPtr ofs

    -- | Encrypt using CFB mode in 8 bit output
    --
    -- Effectively turn a Block cipher in CFB mode into a Stream cipher
    cfb8EncryptOfIO :: BlockCipherIO a => a -> IV a -> B.ByteString -> B.ByteString
    cfb8EncryptOfIO ctx origIv msg = B.unsafeCreate (B.length msg) $ \dst -> loop dst origIv msg
      where loop d iv@(IV i) m
                | B.null m  = return ()
                | otherwise = poke d out >> loop (d `plusPtr` 1) ni (B.drop 1 m)
              where m'  = if B.length m < blockSize ctx
                                then m `B.append` B.replicate (blockSize ctx - B.length m) 0
                                else B.take (blockSize ctx) m
                    r   = cfbEncryptOfIO ctx iv m'
                    out = B.head r
                    ni  = IV (B.drop 1 i `B.snoc` out)

    -- | Decrypt using CFB mode in 8 bit output
    --
    -- Effectively turn a Block cipher in CFB mode into a Stream cipher
    cfb8DecryptOfIO :: BlockCipherIO a => a -> IV a -> B.ByteString -> B.ByteString
    cfb8DecryptOfIO ctx origIv msg = B.unsafeCreate (B.length msg) $ \dst -> loop dst origIv msg
      where loop d iv@(IV i) m
                | B.null m  = return ()
                | otherwise = poke d out >> loop (d `plusPtr` 1) ni (B.drop 1 m)
              where m'  = if B.length m < blockSize ctx
                                then m `B.append` B.replicate (blockSize ctx - B.length m) 0
                                else B.take (blockSize ctx) m
                    r   = cfbDecryptOfIO ctx iv m'
                    out = B.head r
                    ni  = IV (B.drop 1 i `B.snoc` B.head m')
    -}