-- Copyright : (C) 2009 Corey O'Connor -- License : BSD-style (see the file LICENSE) {-# LANGUAGE MagicHash #-} module Bind.Marshal.StdLib.Dynamic.ByteString.Lazy.Des ( decode , decode_ , with_bytestring_provider ) where import Bind.Marshal.Prelude import Bind.Marshal.Action import Bind.Marshal.DesAction import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Internal as L import qualified Data.ByteString as S import qualified Data.ByteString.Internal as S import Data.IORef import System.IO.Unsafe ( unsafePerformIO ) import Foreign.Marshal.Array import Foreign.Marshal.Alloc import Foreign.Ptr import GHC.Exts import GHC.ForeignPtr ( ForeignPtr(..) , mallocPlainForeignPtrBytes , unsafeForeignPtrToPtr , touchForeignPtr ) import GHC.Prim import System.IO {- Deserialization from a lazy bytestring either goes through a slow path or fast path: - fast path is when the required_size to gen_region is less than the remaining bytes in the head - chunk. - Then the buffer region provided by gen_region points directly into the head chunk. - slow path is when the required_size to gen_region is larger than the remaining bytes in the head - chunk. - Then the buffer region provided by gen_region is to a newly allocated buffer containing the - remaining bytes from the head chunk and sufficient bytes from the next chunk to satisfy the - requirement. - - XXX: The fast path is NOT implemented -} data LazyBSDes = LazyBSDes { bs :: !L.ByteString , buffer :: {-# UNPACK #-} !(Ptr Word8) , buffer_size :: {-# UNPACK #-} !Size } defaultChunkSize = L.defaultChunkSize with_bytestring_provider :: forall a . L.ByteString -> ( LazyBSDes -> IO (a, LazyBSDes) ) -> IO (a, L.ByteString) with_bytestring_provider in_bs f = do buffer <- mallocBytes defaultChunkSize let bd = LazyBSDes in_bs buffer defaultChunkSize (a, bd') <- f bd returnM ( a, bs bd' ) :: IO ( a, L.ByteString ) instance BufferDelegate LazyBSDes where gen_region !required_size !bd = case required_size > buffer_size bd of True -> do buffer'@(Ptr start_addr) <- reallocBytes (buffer bd) required_size -- The actual bytestring may have fewer bytes than required_size -- In which case bytes is shorter than required_size. -- This is OK so long as the deserialization doesn't actually read the junk bytes -- that will be in the buffer. Otherwise bad stuff may happen. let bytes = {-# SCC "lazy_bs_unpack_0" #-} L.unpack $! L.take (fromIntegral required_size) $! bs bd pokeArray buffer' bytes returnM $! BDIter required_size 0 ( bd { buffer = buffer', buffer_size = required_size } ) start_addr start_addr :: IO (BDIter LazyBSDes) False -> do let !(Ptr start_addr) = buffer bd bytes = {-# SCC "lazy_bs_unpack_1" #-} L.unpack $! L.take (fromIntegral $! buffer_size bd) $! bs bd pokeArray (buffer bd) bytes returnM $! BDIter (buffer_size bd) 0 bd start_addr start_addr :: IO (BDIter LazyBSDes) finalize_region bd_iter = case minusAddr# (curr_addr bd_iter) (start_addr bd_iter) of consumed_byte_count -> let !bd = buffer_delegate bd_iter !bs' = {-# SCC "lazy_bs_drop" #-} L.drop (fromIntegral $! I# consumed_byte_count) (bs bd) in returnM $! bd { bs = bs' } :: IO LazyBSDes {-# NOINLINE decode #-} decode :: forall a . DynamicDesAction Sealed Sealed Sealed LazyBSDes a -> L.ByteString -> (a, L.ByteString) decode des_action b = unsafePerformIO ( do with_bytestring_provider b (des_from_buffer_delegate des_action) ) {-# NOINLINE decode_ #-} decode_ :: forall a . DynamicDesAction Sealed Sealed Sealed LazyBSDes a -> L.ByteString -> a decode_ des_action b = unsafePerformIO ( do ( a, _ ) <- with_bytestring_provider b (des_from_buffer_delegate des_action) returnM a :: IO a )