{-# OPTIONS_HADDOCK hide #-} module SecondTransfer.MainLoop.Framer( readNextChunk ,readNextChunkAndContinue ,readLengthFromUntamed ,Framer ,LengthCallback ) where import Control.Monad.Trans.Class (lift) import Control.Monad.IO.Class (MonadIO -- , liftIO ) import qualified Data.ByteString as B import qualified Data.ByteString.Builder as Bu import qualified Data.ByteString.Lazy as LB import Data.Conduit -- import Debug.Trace (trace) #ifndef IMPLICIT_MONOID import Data.Monoid #endif type Framer m = LB.ByteString -- Input left overs -> m B.ByteString -- Generator -> Maybe Int -- Length to read, if we know now -> m (LB.ByteString, LB.ByteString) -- To yield, left-overs... -- * Doing it by parts type LengthCallback = B.ByteString -> Maybe Int readNextChunk :: Monad m => LengthCallback -- ^ How to know if we can split somewhere -> B.ByteString -- ^ Input left-overs -> m B.ByteString -- ^ Generator action -> Source m B.ByteString -- ^ Packet and leftovers, if we could get them readNextChunk length_callback input_leftovers gen = do let maybe_length = length_callback input_leftovers case maybe_length of Just the_length -> do -- Just need to read the rest .... (package_bytes, newnewleftovers) <- lift $ readUpTo gen input_leftovers the_length yield package_bytes readNextChunk length_callback newnewleftovers gen Nothing -> do -- Read a bit more new_fragment <- lift gen let new_leftovers = input_leftovers `mappend` new_fragment readNextChunk length_callback new_leftovers gen -- readNextChunkAndContinue :: Monad m => LengthCallback -- ^ How to know if we can split somewhere -> B.ByteString -- ^ Input left-overs -> m B.ByteString -- ^ Generator action -> m (B.ByteString, B.ByteString) -- ^ Packet bytes and left-overs. readNextChunkAndContinue length_callback input_leftovers gen = do let maybe_length = length_callback input_leftovers case maybe_length of Just the_length -> do -- Just need to read the rest .... (package_bytes, newnewleftovers) <- readUpTo gen input_leftovers the_length return (package_bytes, newnewleftovers) Nothing -> do -- Read a bit more new_fragment <- gen let new_leftovers = input_leftovers `mappend` new_fragment readNextChunkAndContinue length_callback new_leftovers gen readUpTo :: Monad m => m B.ByteString -> B.ByteString -> Int -> m (B.ByteString, B.ByteString) readUpTo gen input_leftovers the_length = let initial_length = B.length input_leftovers bu = Bu.byteString input_leftovers go lo readsofar_length | readsofar_length >= the_length = return $ B.splitAt the_length $ LB.toStrict . Bu.toLazyByteString $ lo | otherwise = do frag <- gen go (lo `mappend` Bu.byteString frag) (readsofar_length + B.length frag) in go bu initial_length -- Some protocols, e.g., http/2, have the client transmit a fixed-length -- prefix. This function reads both that prefix and returns whatever get's -- trapped up there.... readLengthFromUntamed :: MonadIO m => Int -> m B.ByteString -> m (B.ByteString, B.ByteString) readLengthFromUntamed the_length gen = readUpTo_ mempty where readUpTo_ lo | B.length lo >= the_length = -- liftIO $ putStrLn "Full read" return $ B.splitAt the_length lo | otherwise = do -- liftIO $ putStrLn $ "fragment read " ++ (show lo) frag <- gen readUpTo_ (lo `mappend` frag)