raaz-0.1.0: Raaz/Core/Transfer.hs
-- | Module to reading from and writing into buffers.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Raaz.Core.Transfer
( -- * Transfer actions.
-- $transfer$
-- ** Read action
ReadM, ReadIO, bytesToRead, unsafeRead
, readBytes, readInto
-- ** Write action.
, WriteM, WriteIO, bytesToWrite, unsafeWrite
, write, writeStorable, writeVector, writeStorableVector
, writeFrom, writeBytes
, padWrite, prependWrite, glueWrites
, writeByteString, skipWrite
) where
import Control.Monad.IO.Class
import Data.ByteString (ByteString)
import Data.String
import Data.ByteString.Internal (unsafeCreate)
import Data.Monoid
import qualified Data.Vector.Generic as G
import Data.Word (Word8)
import Foreign.Ptr (castPtr, Ptr)
import Foreign.Storable ( Storable, poke )
import Raaz.Core.MonoidalAction
import Raaz.Core.Types.Copying
import Raaz.Core.Types.Endian
import Raaz.Core.Types.Pointer
import Raaz.Core.Util.ByteString as BU
import Raaz.Core.Encode
-- $transfer$
--
-- Low level buffer operations are problematic portions of any
-- crypto-library. Buffers are usually represented by the starting
-- pointer and one needs to keep track of the buffer sizes
-- carefully. An operation that writes into a buffer, if it writes
-- beyond the actual size of the buffer, can lead to a possible remote
-- code execution. On the other hand, when reading from a buffer, if
-- we read beyond the buffer it can leak private data to the attacker
-- (as in the case of Heart bleed bug). This module is indented to
-- give a relatively high level interface to this problem. We expose
-- two types, the `ReadM` and the `WriteM` type which deals with these
-- two aspects. Both these actions keep track of the number of bytes
-- that they transfer.
-- Complex reads and writes can be constructed using the monoid
-- instance of these types.
-- | This monoid captures a transfer action.
newtype TransferM m = TransferM { unTransferM :: m () }
instance Monad m => Monoid (TransferM m) where
mempty = TransferM $ return ()
{-# INLINE mempty #-}
mappend wa wb = TransferM $ unTransferM wa >> unTransferM wb
{-# INLINE mappend #-}
mconcat = TransferM . mapM_ unTransferM
{-# INLINE mconcat #-}
-- | A action that transfers bytes from its input pointer. Transfer
-- could either be writing or reading.
type TransferAction m = Pointer -> TransferM m
instance LAction (BYTES Int) (TransferAction m) where
offset <.> action = action . (offset<.>)
{-# INLINE (<.>) #-}
instance Monad m => Distributive (BYTES Int) (TransferAction m)
-- | Byte transfers that keep track of the number of bytes that were
-- transferred (from/into) its input buffer.
type Transfer m = SemiR (TransferAction m) (BYTES Int)
-- | Make an explicit transfer action given.
makeTransfer :: LengthUnit u => u -> (Pointer -> m ()) -> Transfer m
{-# INLINE makeTransfer #-}
makeTransfer sz action = SemiR (TransferM . action) $ inBytes sz
-------------------------- Monoid for writing stuff --------------------------------------
-- | An element of type `WriteM m` is an action which when executed transfers bytes
-- /into/ its input buffer. The type @`WriteM` m@ forms a monoid and
-- hence can be concatnated using the `<>` operator.
newtype WriteM m = WriteM { unWriteM :: Transfer m } deriving Monoid
-- | A write io-action.
type WriteIO = WriteM IO
-- | Returns the bytes that will be written when the write action is performed.
bytesToWrite :: WriteM m -> BYTES Int
bytesToWrite = semiRMonoid . unWriteM
-- | Perform the write action without any checks of the buffer
unsafeWrite :: WriteM m
-> Pointer -- ^ The pointer for the buffer to be written into.
-> m ()
unsafeWrite wr = unTransferM . semiRSpace (unWriteM wr)
-- | Function that explicitly constructs a write action.
makeWrite :: LengthUnit u => u -> (Pointer -> m ()) -> WriteM m
makeWrite sz = WriteM . makeTransfer sz
-- | The expression @`writeStorable` a@ gives a write action that
-- stores a value @a@ in machine endian. The type of the value @a@ has
-- to be an instance of `Storable`. This should be used when we want
-- to talk with C functions and not when talking to the outside world
-- (otherwise this could lead to endian confusion). To take care of
-- endianness use the `write` combinator.
writeStorable :: (MonadIO m, Storable a) => a -> WriteM m
writeStorable a = WriteM $ makeTransfer (sizeOf a) pokeIt
where pokeIt = liftIO . flip poke a . castPtr
-- | The expression @`write` a@ gives a write action that stores a
-- value @a@. One needs the type of the value @a@ to be an instance of
-- `EndianStore`. Proper endian conversion is done irrespective of
-- what the machine endianness is. The man use of this write is to
-- serialize data for the consumption of the outside world.
write :: (MonadIO m, EndianStore a) => a -> WriteM m
write a = makeWrite (sizeOf a) $ liftIO . flip (store . castPtr) a
-- | Write many elements from the given buffer
writeFrom :: (MonadIO m, EndianStore a) => Int -> Src (Ptr a) -> WriteM m
writeFrom n src = makeWrite (sz undefined src)
$ \ ptr -> liftIO $ copyToBytes (destination ptr) src n
where sz :: Storable a => a -> Src (Ptr a) -> BYTES Int
sz a _ = toEnum n * sizeOf a
-- | The vector version of `writeStorable`.
writeStorableVector :: (Storable a, G.Vector v a, MonadIO m) => v a -> WriteM m
{-# INLINE writeStorableVector #-}
writeStorableVector = G.foldl' foldFunc mempty
where foldFunc w a = w <> writeStorable a
{-
TODO: This function can be slow due to the fact that each time we use
the semi-direct product, we incur a cost due to the lambda being not
lifted.
-}
-- | The vector version of `write`.
writeVector :: (EndianStore a, G.Vector v a, MonadIO m) => v a -> WriteM m
{-# INLINE writeVector #-}
{- TODO: improve this using the fact that the size is known -}
writeVector = G.foldl' foldFunc mempty
where foldFunc w a = w <> write a
{- TODO: Same as in writeStorableVector -}
-- | The combinator @writeBytes n b@ writes @b@ as the next @n@
-- consecutive bytes.
writeBytes :: (LengthUnit n, MonadIO m) => Word8 -> n -> WriteM m
writeBytes w8 n = makeWrite n memsetIt
where memsetIt cptr = liftIO $ memset cptr w8 n
{-
-- | The write action @padWriteTo w n wr@ is wr padded with the byte @w@ so that the total length
-- is n. If the total bytes written by @wr@ is greater than @n@ then this throws an error.
padWriteTo :: ( LengthUnit n, MonadIO m)
=> Word8 -- ^ the padding byte to use
-> n -- ^ the total length to pad to
-> WriteM m -- ^ the write that needs padding
-> WriteM m
padWriteTo w8 n wrm | pl < 0 = error "padToLength: padding length smaller than total length"
| otherwise = wrm <> writeBytes w8 n
where pl = inBytes n - bytesToWrite wrm
-}
-- | The combinator @glueWrites w n hdr ftr@ is equivalent to
-- @hdr <> glue <> ftr@ where the write @glue@ writes as many bytes
-- @w@ so that the total length is aligned to the boundary @n@.
glueWrites :: ( LengthUnit n, MonadIO m)
=> Word8 -- ^ The bytes to use in the glue
-> n -- ^ The length boundary to align to.
-> WriteM m -- ^ The header write
-> WriteM m -- ^ The footer write
-> WriteM m
glueWrites w8 n hdr ftr = hdr <> writeBytes w8 lglue <> ftr
where lhead = bytesToWrite hdr
lfoot = bytesToWrite ftr
lexceed = (lhead + lfoot) `rem` nBytes -- bytes exceeding the boundary.
lglue = nBytes - lexceed
nBytes = inBytes n
-- | The write action @prependWrite w n wr@ is wr pre-pended with the byte @w@ so that the total length
-- ends at a multiple of @n@.
prependWrite :: ( LengthUnit n, MonadIO m)
=> Word8 -- ^ the byte to pre-pend with.
-> n -- ^ the length to align the message to
-> WriteM m -- ^ the message that needs pre-pending
-> WriteM m
prependWrite w8 n = glueWrites w8 n mempty
-- | The write action @padWrite w n wr@ is wr padded with the byte @w@ so that the total length
-- ends at a multiple of @n@.
padWrite :: ( LengthUnit n, MonadIO m)
=> Word8 -- ^ the padding byte to use
-> n -- ^ the length to align message to
-> WriteM m -- ^ the message that needs padding
-> WriteM m
padWrite w8 n = flip (glueWrites w8 n) mempty
-- | Writes a strict bytestring.
writeByteString :: MonadIO m => ByteString -> WriteM m
writeByteString bs = makeWrite (BU.length bs) $ liftIO . BU.unsafeCopyToPointer bs
-- | A write action that just skips over the given bytes.
skipWrite :: (LengthUnit u, Monad m) => u -> WriteM m
skipWrite = flip makeWrite $ const $ return ()
instance MonadIO m => IsString (WriteM m) where
fromString = writeByteString . fromString
instance Encodable (WriteM IO) where
{-# INLINE toByteString #-}
toByteString w = unsafeCreate n $ unsafeWrite w . castPtr
where BYTES n = bytesToWrite w
{-# INLINE unsafeFromByteString #-}
unsafeFromByteString = writeByteString
{-# INLINE fromByteString #-}
fromByteString = Just . writeByteString
------------------------ Read action ----------------------------
-- | The `ReadM` is the type that captures the act of reading from a buffer
-- and possibly doing some action on the bytes read. Although
-- inaccurate, it is helpful to think of elements of `ReadM` as action
-- that on an input buffer transfers data from it to some unspecified
-- source.
--
-- Read actions form a monoid with the following semantics: if @r1@
-- and @r2@ are two read actions then @r1 `<>` r2@ first reads the
-- data associated from @r1@ and then the read associated with the
-- data @r2@.
newtype ReadM m = ReadM { unReadM :: Transfer m} deriving Monoid
-- | A read io-action.
type ReadIO = ReadM IO
-- | Function that explicitly constructs a write action.
makeRead :: LengthUnit u => u -> (Pointer -> m ()) -> ReadM m
makeRead sz = ReadM . makeTransfer sz
-- | The expression @bytesToRead r@ gives the total number of bytes that
-- would be read from the input buffer if the action @r@ is performed.
bytesToRead :: ReadM m -> BYTES Int
bytesToRead = semiRMonoid . unReadM
-- | The action @unsafeRead r ptr@ results in reading @bytesToRead r@
-- bytes from the buffer pointed by @ptr@. This action is unsafe as it
-- will not (and cannot) check if the action reads beyond what is
-- legally stored at @ptr@.
unsafeRead :: ReadM m
-> Pointer -- ^ The pointer for the buffer to be written into.
-> m ()
unsafeRead rd = unTransferM . semiRSpace (unReadM rd)
-- | The action @readBytes sz dptr@ gives a read action, which if run on
-- an input buffer, will transfers @sz@ to the destination buffer
-- pointed by @dptr@. Note that it is the responsibility of the user
-- to make sure that @dptr@ has enough space to receive @sz@ units of
-- data if and when the read action is executed.
readBytes :: ( LengthUnit sz, MonadIO m)
=> sz -- ^ how much to read.
-> Dest Pointer -- ^ buffer to read the bytes into
-> ReadM m
readBytes sz dest = makeRead sz
$ \ ptr -> liftIO $ memcpy dest (source ptr) sz
-- | The action @readInto n dptr@ gives a read action which if run on an
-- input buffer, will transfers @n@ elements of type @a@ into the
-- buffer pointed by @dptr@. In particular, the read action @readInto n
-- dptr@ is the same as @readBytes (fromIntegral n :: BYTES Int) dptr@
-- when the type @a@ is `Word8`.
readInto :: (EndianStore a, MonadIO m)
=> Int -- ^ how many elements to read.
-> Dest (Ptr a) -- ^ buffer to read the elements into
-> ReadM m
readInto n dest = makeRead (sz undefined dest)
$ \ ptr -> liftIO $ copyFromBytes dest (source ptr) n
where sz :: Storable a => a -> Dest (Ptr a) -> BYTES Int
sz a _ = toEnum n * sizeOf a