-- |
-- 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 :: 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 :: cipher -> a
notBlockSized = cipher -> a
forall a. HasCallStack => a
undefined

withDest :: BlockCipherIO cipher
         => cipher
         -> ByteString
         -> (PtrDest -> PtrSource -> BufferLength -> IO ())
         -> ByteString
withDest :: 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
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 :: 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 :: 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')
-}