raaz-0.3.0: core/Raaz/Core/Transfer.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
-- |
--
-- Module : Raaz.Core.Transfer
-- Description : Type safe transfer of bytes.
-- Copyright : (c) Piyush P Kurur, 2019
-- License : Apache-2.0 OR BSD-3-Clause
-- Maintainer : Piyush P Kurur <ppk@iitpkd.ac.in>
-- Stability : experimental
--
module Raaz.Core.Transfer
( -- * Transfer actions.
-- $transfer$
Transfer, ReadFrom, WriteTo
, consume, consumeStorable, consumeParse
, writeEncodable
, write, writeStorable, writeVector, writeStorableVector
, writeBytes
, padWrite, prependWrite, glueWrites
, writeByteString
, transferSize
, skip
) where
import qualified Data.Vector.Generic as G
import Foreign.Storable ( Storable, poke )
import Raaz.Core.Transfer.Unsafe
import Raaz.Core.Prelude
import Raaz.Core.Parse.Unsafe
import Raaz.Core.Parse hiding (skip)
import Raaz.Core.Types.Endian
import Raaz.Core.Types.Pointer
import Raaz.Core.Encode
-- | The transfer @skip l@ skip ahead by an offset @l@. If it is a
-- read, it does not read the next @l@ positions. If it is a write it
-- does not mutate the next @l@ positions.
skip :: LengthUnit l => l -> Transfer t
skip = flip unsafeMakeTransfer doNothing
where doNothing = const $ return ()
-------------------------- Monoids for consuming stuff ------------------------------------
-- | Given a parser @p :: Parser a@ for parsing @a@ and @act :: a -> m
-- b@ consuming a, @consumeParse p act@, gives a reader that parses a
-- from the input buffer passing it to the action act.
consumeParse :: Parser a -> (a -> IO b) -> ReadFrom
consumeParse p action = unsafeMakeTransfer (parseWidth p) $
unsafeRunParser p >=> void . action
-- | Reads @a@ from the buffer and supplies it to the action. The
-- value read is independent of the endianness of the underlying.
consume :: EndianStore a
=> (a -> IO b)
-> ReadFrom
consume = consumeParse parse
-- | Similar to @consume@ but does not take care of adjusting for
-- endianness. Use therefore limited to internal buffers.
consumeStorable :: Storable a
=> (a -> IO b)
-> ReadFrom
consumeStorable = consumeParse parseStorable
-------------------------- Monoid for writing stuff --------------------------------------
-- | 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 :: Storable a => a -> WriteTo
writeStorable a = unsafeMakeTransfer (sizeOf $ pure a) pokeIt
where pokeIt = flip poke a . castPointer
-- | 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 :: EndianStore a => a -> WriteTo
write a = unsafeMakeTransfer (sizeOf $ pure a) $ flip (store . castPointer) a
-- | Write any encodable elements
writeEncodable :: Encodable a => a -> WriteTo
writeEncodable = writeByteString . toByteString
-- | The vector version of `writeStorable`.
writeStorableVector :: (Storable a, G.Vector v a) => v a -> WriteTo
{-# 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) => v a -> WriteTo
{-# 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 b n@ writes @b@ as the next @n@
-- consecutive bytes.
writeBytes :: LengthUnit n
=> Word8 -- ^ Byte to write
-> n -- ^ How much to write
-> WriteTo
writeBytes w8 n = unsafeMakeTransfer n memsetIt
where memsetIt cptr = memset cptr w8 n
-- | The combinator @glueWrites w n hdr ftr@ is equivalent to @hdr <>
-- glue <> ftr@ where the write @glue@ writes just enough bytes @w@ so
-- that the total length is aligned to the boundary @n@.
glueWrites :: LengthUnit n
=> Word8 -- ^ The bytes to use in the glue
-> n -- ^ The length boundary to align to.
-> WriteTo -- ^ The header write
-> WriteTo -- ^ The footer write
-> WriteTo
glueWrites w8 n hdr ftr = hdr <> writeBytes w8 lglue <> ftr
where lhead = transferSize hdr
lfoot = transferSize ftr
lexceed = (lhead + lfoot) `rem` nBytes -- bytes exceeding the boundary.
lglue = if lexceed > 0 then nBytes - lexceed else 0
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
=> Word8 -- ^ the byte to pre-pend with.
-> n -- ^ the length to align the message to
-> WriteTo -- ^ the message that needs pre-pending
-> WriteTo
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
=> Word8 -- ^ the padding byte to use
-> n -- ^ the length to align message to
-> WriteTo -- ^ the message that needs padding
-> WriteTo
padWrite w8 n = flip (glueWrites w8 n) mempty
------------- Reading stuff -----------------------------------