{-# LANGUAGE RecordWildCards    #-}
{-# LANGUAGE RankNTypes         #-}
{-# LANGUAGE OverloadedStrings  #-}
module Network.HTTP2.Client (
    
      runHttp2Client
    , newHttp2Client
    , withHttp2Stream
    , headers
    , trailers
    , sendData
    
    , Http2Client(..)
    , PushPromiseHandler
    
    , StreamDefinition(..)
    , StreamStarter
    , TooMuchConcurrency(..)
    , StreamThread
    , Http2Stream(..)
    
    , IncomingFlowControl(..)
    , OutgoingFlowControl(..)
    
    , linkAsyncs
    , RemoteSentGoAwayFrame(..)
    , GoAwayHandler
    , defaultGoAwayHandler
    
    , FallBackFrameHandler
    , ignoreFallbackHandler
    , FlagSetter
    , Http2ClientAsyncs(..)
    , _gtfo
    
    , StreamEvent(..)
    , module Network.HTTP2.Client.FrameConnection
    , module Network.Socket
    , module Network.TLS
    ) where
import           Control.Concurrent.Async (Async, async, race, withAsync, link)
import           Control.Exception (bracket, throwIO, SomeException, catch)
import           Control.Concurrent.MVar (newEmptyMVar, newMVar, putMVar, takeMVar, tryPutMVar)
import           Control.Concurrent (threadDelay)
import           Control.Monad (forever, void, when, forM_)
import           Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import           Data.IORef (newIORef, atomicModifyIORef', readIORef)
import           Data.Maybe (fromMaybe)
import           Network.HPACK as HPACK
import           Network.HTTP2 as HTTP2
import           Network.Socket (HostName, PortNumber)
import           Network.TLS (ClientParams)
import           Network.HTTP2.Client.Channels
import           Network.HTTP2.Client.Dispatch
import           Network.HTTP2.Client.FrameConnection
data IncomingFlowControl = IncomingFlowControl {
    _addCredit   :: WindowSize -> IO ()
  
  
  , _consumeCredit :: WindowSize -> IO Int
  
  , _updateWindow :: IO Bool
  
  
  
  
  }
data OutgoingFlowControl = OutgoingFlowControl {
    _receiveCredit  :: WindowSize -> IO ()
  
  , _withdrawCredit :: WindowSize -> IO WindowSize
  
  
  
  
  }
data StreamDefinition a = StreamDefinition {
    _initStream   :: IO StreamThread
  
  
  
  
  , _handleStream :: IncomingFlowControl -> OutgoingFlowControl -> IO a
  
  
  
  
  
  }
type StreamStarter a =
     (Http2Stream -> StreamDefinition a) -> IO (Either TooMuchConcurrency a)
newtype TooMuchConcurrency = TooMuchConcurrency { _getStreamRoomNeeded :: Int }
    deriving Show
data Http2Client = Http2Client {
    _ping             :: ByteString -> IO (IO (FrameHeader, FramePayload))
  
  
  
  
  
  , _settings         :: SettingsList -> IO (IO (FrameHeader, FramePayload))
  
  
  
  
  , _goaway           :: ErrorCodeId -> ByteString -> IO ()
  
  , _startStream      :: forall a. StreamStarter a
  
  , _incomingFlowControl :: IncomingFlowControl
  
  
  , _outgoingFlowControl :: OutgoingFlowControl
  
  
  , _payloadSplitter :: IO PayloadSplitter
  
  , _asyncs         :: !Http2ClientAsyncs
  
  , _close           :: IO ()
  
  
  }
data InitHttp2Client = InitHttp2Client {
    _initPing                :: ByteString -> IO (IO (FrameHeader, FramePayload))
  , _initSettings            :: SettingsList -> IO (IO (FrameHeader, FramePayload))
  , _initGoaway              :: ErrorCodeId -> ByteString -> IO ()
  , _initStartStream         :: forall a. StreamStarter a
  , _initIncomingFlowControl :: IncomingFlowControl
  , _initOutgoingFlowControl :: OutgoingFlowControl
  , _initPaylodSplitter      :: IO PayloadSplitter
  , _initClose               :: IO ()
  
  , _initStop                :: IO Bool
  
  }
data Http2ClientAsyncs = Http2ClientAsyncs {
    _waitSettingsAsync   :: Async (FrameHeader, FramePayload)
  
  , _incomingFramesAsync :: Async ()
  
  
  
  }
linkAsyncs :: Http2Client -> IO ()
linkAsyncs client =
    let Http2ClientAsyncs{..} = _asyncs client in do
            link _waitSettingsAsync
            link _incomingFramesAsync
_gtfo :: Http2Client -> ErrorCodeId -> ByteString -> IO ()
_gtfo = _goaway
data StreamThread = CST
data Http2Stream = Http2Stream {
    _headers      :: HPACK.HeaderList
                  -> (FrameFlags -> FrameFlags)
                  -> IO StreamThread
  
  
  
  , _prio         :: Priority -> IO ()
  
  , _rst          :: ErrorCodeId -> IO ()
  
  , _waitEvent    :: IO StreamEvent
  
  , _sendDataChunk     :: (FrameFlags -> FrameFlags) -> ByteString -> IO ()
  
  
  
  
  
  , _handlePushPromise :: StreamId -> HeaderList -> PushPromiseHandler -> IO ()
  }
trailers :: Http2Stream -> HPACK.HeaderList -> (FrameFlags -> FrameFlags) -> IO ()
trailers stream hdrs flagmod = void $ _headers stream hdrs flagmod
type PushPromiseHandler =
    StreamId -> Http2Stream -> HeaderList -> IncomingFlowControl -> OutgoingFlowControl -> IO ()
withHttp2Stream :: Http2Client -> StreamStarter a
withHttp2Stream = _startStream
type FlagSetter = FrameFlags -> FrameFlags
headers :: Http2Stream -> HeaderList -> FlagSetter -> IO StreamThread
headers = _headers
runHttp2Client
  :: Http2FrameConnection
  
  -> Int
  
  -> Int
  
  -> SettingsList
  
  -> GoAwayHandler
  
  -> FallBackFrameHandler
  
  
  -> (Http2Client -> IO a)
  
  -> IO a
runHttp2Client conn encoderBufSize decoderBufSize initSettings goAwayHandler fallbackHandler mainHandler = do
    (incomingLoop, initClient) <- initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler
    withAsync incomingLoop $ \aIncoming -> do
        settsIO <- _initSettings initClient initSettings
        withAsync settsIO $ \aSettings -> do
            let client = Http2Client {
              _settings            = _initSettings initClient
            , _ping                = _initPing initClient
            , _goaway              = _initGoaway initClient
            , _close               =
                _initStop initClient >> _initClose initClient
            , _startStream         = _initStartStream initClient
            , _incomingFlowControl = _initIncomingFlowControl initClient
            , _outgoingFlowControl = _initOutgoingFlowControl initClient
            , _payloadSplitter     = _initPaylodSplitter initClient
            , _asyncs              = Http2ClientAsyncs aSettings aIncoming
            }
            linkAsyncs client
            ret <- mainHandler client
            _close client
            return ret
newHttp2Client
  :: Http2FrameConnection
  
  -> Int
  
  -> Int
  
  -> SettingsList
  
  -> GoAwayHandler
  
  -> FallBackFrameHandler
  
  
  -> IO Http2Client
newHttp2Client conn encoderBufSize decoderBufSize initSettings goAwayHandler fallbackHandler = do
    (incomingLoop, initClient) <- initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler
    aIncoming <- async incomingLoop
    settsIO <- _initSettings initClient initSettings
    aSettings <- async settsIO
    return $ Http2Client {
        _settings            = _initSettings initClient
      , _ping                = _initPing initClient
      , _goaway              = _initGoaway initClient
      , _close               =
          _initStop initClient >> _initClose initClient
      , _startStream         = _initStartStream initClient
      , _incomingFlowControl = _initIncomingFlowControl initClient
      , _outgoingFlowControl = _initOutgoingFlowControl initClient
      , _payloadSplitter     = _initPaylodSplitter initClient
      , _asyncs              = Http2ClientAsyncs aSettings aIncoming
      }
initHttp2Client
  :: Http2FrameConnection
  -> Int
  -> Int
  -> GoAwayHandler
  -> FallBackFrameHandler
  -> IO (IO (), InitHttp2Client)
initHttp2Client conn encoderBufSize decoderBufSize goAwayHandler fallbackHandler = do
    let controlStream = makeFrameClientStream conn 0
    let ackPing = sendPingFrame controlStream HTTP2.setAck
    let ackSettings = sendSettingsFrame controlStream HTTP2.setAck []
    
    dispatch  <- newDispatchIO
    dispatchControl <- newDispatchControlIO encoderBufSize
                                            ackPing
                                            ackSettings
                                            goAwayHandler
                                            fallbackHandler
    _initIncomingFlowControl <- newIncomingFlowControl dispatchControl controlStream
    (_initOutgoingFlowControl,windowUpdatesChan) <- newOutgoingFlowControl dispatchControl 0
    dispatchHPACK <- newDispatchHPACKIO decoderBufSize
    (incomingLoop,endIncomingLoop) <- dispatchLoop conn dispatch dispatchControl windowUpdatesChan _initIncomingFlowControl dispatchHPACK
    
    conccurentStreams <- newIORef 0
    
    clientStreamIdMutex <- newMVar 0
    let withClientStreamId h = bracket (takeMVar clientStreamIdMutex)
            (putMVar clientStreamIdMutex . succ)
            (\k -> h (2 * k + 1)) 
    let _initStartStream getWork = do
            maxConcurrency <- fromMaybe 100 . maxConcurrentStreams . _serverSettings <$> readSettings dispatchControl
            roomNeeded <- atomicModifyIORef' conccurentStreams
                (\n -> if n < maxConcurrency then (n + 1, 0) else (n, 1 + n - maxConcurrency))
            if roomNeeded > 0
            then
                return $ Left $ TooMuchConcurrency roomNeeded
            else Right <$> do
                cont <- withClientStreamId $ \sid -> do
                    dispatchStream <- newDispatchStreamIO sid
                    initializeStream conn
                                     dispatch
                                     dispatchControl
                                     dispatchStream
                                     getWork
                                     Idle
                v <- cont
                atomicModifyIORef' conccurentStreams (\n -> (n - 1, ()))
                pure v
    let _initPing dat = do
            handler <- registerPingHandler dispatchControl dat
            sendPingFrame controlStream id dat
            return $ waitPingReply handler
    let _initSettings settslist = do
            handler <- registerSetSettingsHandler dispatchControl
            sendSettingsFrame controlStream id settslist
            return $ do
                ret <- waitSetSettingsReply handler
                modifySettings dispatchControl
                    (\(ConnectionSettings cli srv) ->
                        (ConnectionSettings (HTTP2.updateSettings cli settslist) srv, ()))
                return ret
    let _initGoaway err errStr = do
            sId <- readMaxReceivedStreamIdIO dispatch
            sendGTFOFrame controlStream sId err errStr
    let _initPaylodSplitter = settingsPayloadSplitter <$> readSettings dispatchControl
    let _initStop = endIncomingLoop
    let _initClose = closeConnection conn
    return (incomingLoop, InitHttp2Client{..})
initializeStream
  :: Http2FrameConnection
  -> Dispatch
  -> DispatchControl
  -> DispatchStream
  -> (Http2Stream -> StreamDefinition a)
  -> StreamFSMState
  -> IO (IO a)
initializeStream conn dispatch control stream getWork initialState = do
    let sid = _dispatchStreamId stream
    let frameStream = makeFrameClientStream conn sid
    let events        = _dispatchStreamReadEvents stream
    
    incomingStreamFlowControl <- newIncomingFlowControl control frameStream
    (outgoingStreamFlowControl, windowUpdatesChan) <- newOutgoingFlowControl control sid
    registerStream dispatch sid (StreamState windowUpdatesChan events initialState)
    
    let _headers headersList flags = do
            splitter <- settingsPayloadSplitter <$> readSettings control
            cst <- sendHeaders frameStream (_dispatchControlHpackEncoder control) headersList splitter flags
            when (testEndStream $ flags 0) $ do
                closeLocalStream dispatch sid
            return cst
    let _waitEvent    = readChan events
    let _sendDataChunk flags dat = do
            sendDataFrame frameStream flags dat
            when (testEndStream $ flags 0) $ do
                closeLocalStream dispatch sid
    let _rst = \err -> do
            sendResetFrame frameStream err
            closeReleaseStream dispatch sid
    let _prio           = sendPriorityFrame frameStream
    let _handlePushPromise ppSid ppHeaders ppHandler = do
            let mkStreamActions s = StreamDefinition (return CST) (ppHandler sid s ppHeaders)
            newStream <- newDispatchStreamIO ppSid
            ppCont <- initializeStream conn
                                       dispatch
                                       control
                                       newStream
                                       mkStreamActions
                                       ReservedRemote
            ppCont
    let streamActions = getWork $ Http2Stream{..}
    
    _ <- _initStream streamActions
    
    return $ _handleStream streamActions incomingStreamFlowControl outgoingStreamFlowControl
dispatchLoop
  :: Http2FrameConnection
  -> Dispatch
  -> DispatchControl
  -> Chan (FrameHeader, FramePayload)
  -> IncomingFlowControl
  -> DispatchHPACK
  -> IO (IO (), IO Bool)
dispatchLoop conn d dc windowUpdatesChan inFlowControl dh = do
    let getNextFrame = next conn
    let go = delayException . forever $ do
            frame <- getNextFrame
            dispatchFramesStep frame d
            whenFrame (hasStreamId 0) frame $ \got ->
                dispatchControlFramesStep windowUpdatesChan got dc
            whenFrame (hasTypeId [FrameData]) frame $ \got ->
                creditDataFramesStep d inFlowControl got
            whenFrame (hasTypeId [FrameWindowUpdate]) frame $ \got -> do
                updateWindowsStep d got
            whenFrame (hasTypeId [FramePushPromise, FrameHeaders]) frame $ \got -> do
                let hpackLoop (FinishedWithHeaders curFh sId mkNewHdrs) = do
                        newHdrs <- mkNewHdrs
                        chan <- fmap _streamStateEvents <$> lookupStreamState d sId
                        let msg = StreamHeadersEvent curFh newHdrs
                        maybe (return ()) (flip writeChan msg) chan
                    hpackLoop (FinishedWithPushPromise curFh parentSid newSid mkNewHdrs) = do
                        newHdrs <- mkNewHdrs
                        chan <- fmap _streamStateEvents <$> lookupStreamState d parentSid
                        let msg = StreamPushPromiseEvent curFh newSid newHdrs
                        maybe (return ()) (flip writeChan msg) chan
                    hpackLoop (WaitContinuation act)        =
                        getNextFrame >>= act >>= hpackLoop
                    hpackLoop (FailedHeaders curFh sId err)        = do
                        chan <- fmap _streamStateEvents <$> lookupStreamState d sId
                        let msg = StreamErrorEvent curFh err
                        maybe (return ()) (flip writeChan msg) chan
                hpackLoop (dispatchHPACKFramesStep got dh)
            whenFrame (hasTypeId [FrameRSTStream]) frame $ \got -> do
                handleRSTStep d got
            finalizeFramesStep frame d
    end <- newEmptyMVar
    let run = void $ race go (takeMVar end)
    let stop = tryPutMVar end ()
    return (run, stop)
handleRSTStep
  :: Dispatch
  -> (FrameHeader, FramePayload)
  -> IO ()
handleRSTStep d (fh, payload) = do
    let sid = streamId fh
    case payload of
        (RSTStreamFrame err) -> do
            chan <- fmap _streamStateEvents <$> lookupStreamState d sid
            let msg = StreamErrorEvent fh (HTTP2.fromErrorCodeId err)
            maybe (return ()) (flip writeChan msg) chan
            closeReleaseStream d sid
        _ ->
            error $ "expecting RSTFrame but got " ++ show payload
dispatchFramesStep
  :: (FrameHeader, Either HTTP2Error FramePayload)
  -> Dispatch
  -> IO ()
dispatchFramesStep (fh,_) d = do
    let sid = streamId fh
    
    atomicModifyIORef' (_dispatchMaxStreamId d) (\n -> (max n sid, ()))
finalizeFramesStep
  :: (FrameHeader, Either HTTP2Error FramePayload)
  -> Dispatch
  -> IO ()
finalizeFramesStep (fh,_) d = do
    let sid = streamId fh
    
    when (testEndStream $ flags fh) $ do
        closeRemoteStream d sid
dispatchControlFramesStep
  :: Chan (FrameHeader, FramePayload)
  -> (FrameHeader, FramePayload)
  -> DispatchControl
  -> IO ()
dispatchControlFramesStep windowUpdatesChan controlFrame@(fh, payload) control@(DispatchControl{..}) = do
    case payload of
        (SettingsFrame settsList)
            | not . testAck . flags $ fh -> do
                atomicModifyIORef' _dispatchControlConnectionSettings
                                   (\(ConnectionSettings cli srv) ->
                                      (ConnectionSettings cli (HTTP2.updateSettings srv settsList), ()))
                maybe (return ())
                      (_applySettings _dispatchControlHpackEncoder)
                      (lookup SettingsHeaderTableSize settsList)
                _dispatchControlAckSettings
            | otherwise                 -> do
                handler <- lookupAndReleaseSetSettingsHandler control
                maybe (return ()) (notifySetSettingsHandler controlFrame) handler
        (PingFrame pingMsg)
            | not . testAck . flags $ fh ->
                _dispatchControlAckPing pingMsg
            | otherwise                 -> do
                handler <- lookupAndReleasePingHandler control pingMsg
                maybe (return ()) (notifyPingHandler controlFrame) handler
        (WindowUpdateFrame _ )  ->
                writeChan windowUpdatesChan controlFrame
        (GoAwayFrame lastSid errCode reason)  ->
             _dispatchControlOnGoAway $ RemoteSentGoAwayFrame lastSid errCode reason
        _                   ->
             _dispatchControlOnFallback controlFrame
creditDataFramesStep
  :: Dispatch
  -> IncomingFlowControl
  -> (FrameHeader, FramePayload)
  -> IO ()
creditDataFramesStep d flowControl (fh,payload) = do
    
    
    _ <- _consumeCredit flowControl (HTTP2.payloadLength fh)
    _addCredit flowControl (HTTP2.payloadLength fh)
    
    let sid = streamId fh
    case payload of
        (DataFrame dat) -> do
            chan <- fmap _streamStateEvents <$> lookupStreamState d sid
            maybe (return ()) (flip writeChan $ StreamDataEvent fh dat) chan
        _ ->
            error $ "expecting DataFrame but got " ++ show payload
updateWindowsStep
  :: Dispatch
  -> (FrameHeader, FramePayload)
  -> IO ()
updateWindowsStep d got@(fh,_) = do
    let sid = HTTP2.streamId fh
    chan <- fmap _streamStateWindowUpdatesChan <$> lookupStreamState d sid
    maybe (return ()) (flip writeChan got) chan 
data HPACKLoopDecision =
    ForwardHeader !StreamId
  | OpenPushPromise !StreamId !StreamId
data HPACKStepResult =
    WaitContinuation !((FrameHeader, Either HTTP2Error FramePayload) -> IO HPACKStepResult)
  | FailedHeaders !FrameHeader !StreamId ErrorCode
  | FinishedWithHeaders !FrameHeader !StreamId (IO HeaderList)
  | FinishedWithPushPromise !FrameHeader !StreamId !StreamId (IO HeaderList)
dispatchHPACKFramesStep
  :: (FrameHeader, FramePayload)
  -> DispatchHPACK
  -> HPACKStepResult
dispatchHPACKFramesStep (fh,fp) (DispatchHPACK{..}) =
    let (decision, pattern) = case fp of
            PushPromiseFrame ppSid hbf -> do
                (OpenPushPromise sid ppSid, Right hbf)
            HeadersFrame _ hbf       -> 
                (ForwardHeader sid, Right hbf)
            RSTStreamFrame err       ->
                (ForwardHeader sid, Left err)
            _                        ->
                error "wrong TypeId"
    in go fh decision pattern
  where
    sid :: StreamId
    sid = HTTP2.streamId fh
    go :: FrameHeader -> HPACKLoopDecision -> Either ErrorCodeId ByteString -> HPACKStepResult
    go curFh decision (Right buffer) =
        if not $ HTTP2.testEndHeader (HTTP2.flags curFh)
        then WaitContinuation $ \frame -> do
            let interrupted fh2 fp2 =
                    not $ hasTypeId [ FrameRSTStream , FrameContinuation ] fh2 fp2
            whenFrameElse interrupted frame (\_ ->
                error "invalid frame type while waiting for CONTINUATION")
                                            (\(lastFh, lastFp) ->
                case lastFp of
                    ContinuationFrame chbf ->
                        return $ go lastFh decision (Right (ByteString.append buffer chbf))
                    RSTStreamFrame err     ->
                        return $ go lastFh decision (Left err)
                    _                     ->
                        error "continued frame has invalid type")
        else case decision of
            ForwardHeader sId ->
                FinishedWithHeaders curFh sId (decodeHeader _dispatchHPACKDynamicTable buffer)
            OpenPushPromise parentSid newSid ->
                FinishedWithPushPromise curFh parentSid newSid (decodeHeader _dispatchHPACKDynamicTable buffer)
    go curFh _ (Left err) =
        FailedHeaders curFh sid (HTTP2.fromErrorCodeId err)
newIncomingFlowControl
  :: DispatchControl
  -> Http2FrameClientStream
  -> IO IncomingFlowControl
newIncomingFlowControl control stream = do
    let getBase = if _getStreamId stream == 0
                  then return HTTP2.defaultInitialWindowSize
                  else initialWindowSize . _clientSettings <$> readSettings control
    creditAdded <- newIORef 0
    creditConsumed <- newIORef 0
    let _addCredit n = atomicModifyIORef' creditAdded (\c -> (c + n, ()))
    let _consumeCredit n = do
            conso <- atomicModifyIORef' creditConsumed (\c -> (c + n, c + n))
            base <- getBase
            extra <- readIORef creditAdded
            return $ base + extra - conso
    let _updateWindow = do
            base <- initialWindowSize . _clientSettings <$> readSettings control
            added <- readIORef creditAdded
            consumed <- readIORef creditConsumed
            let transferred = min added (HTTP2.maxWindowSize - base + consumed)
            let shouldUpdate = transferred > 0
            _addCredit (negate transferred)
            _ <- _consumeCredit (negate transferred)
            when shouldUpdate (sendWindowUpdateFrame stream transferred)
            return shouldUpdate
    return $ IncomingFlowControl _addCredit _consumeCredit _updateWindow
newOutgoingFlowControl ::
     DispatchControl
  -> StreamId
  -> IO (OutgoingFlowControl, Chan (FrameHeader, FramePayload))
newOutgoingFlowControl control sid = do
    credit <- newIORef 0
    frames <- newChan
    let getBase = if sid == 0
                  then return HTTP2.defaultInitialWindowSize
                  else initialWindowSize . _serverSettings <$> readSettings control
    let receive n = atomicModifyIORef' credit (\c -> (c + n, ()))
    let withdraw 0 = return 0
        withdraw n = do
            base <- getBase
            got <- atomicModifyIORef' credit (\c ->
                    if base + c >= n
                    then (c - n, n)
                    else (0 - base, base + c))
            if got > 0
            then return got
            else do
                amount <- race (waitSettingsChange base) (waitSomeCredit frames)
                receive (either (const 0) id amount)
                withdraw n
    return $ (OutgoingFlowControl receive withdraw, frames)
  where
    
    
    
    
    
    
    
    waitSettingsChange prev = do
            new <- initialWindowSize . _serverSettings <$> readSettings control
            if new == prev then threadDelay 1000000 >> waitSettingsChange prev else return ()
    waitSomeCredit frames = do
        got <- readChan frames
        case got of
            (_, WindowUpdateFrame amt) ->
                return amt
            _                         ->
                error "got forwarded an unknown frame"
sendHeaders
  :: Http2FrameClientStream
  -> HpackEncoderContext
  -> HeaderList
  -> PayloadSplitter
  -> (FrameFlags -> FrameFlags)
  -> IO StreamThread
sendHeaders s enc hdrs blockSplitter flagmod = do
    _sendFrames s mkFrames
    return CST
  where
    mkFrames = do
        headerBlockFragments <- blockSplitter <$> _encodeHeaders enc hdrs
        let framers           = (HeadersFrame Nothing) : repeat ContinuationFrame
        let frames            = zipWith ($) framers headerBlockFragments
        let modifiersReversed = (HTTP2.setEndHeader . flagmod) : repeat id
        let arrangedFrames    = reverse $ zip modifiersReversed (reverse frames)
        return arrangedFrames
type PayloadSplitter = ByteString -> [ByteString]
settingsPayloadSplitter :: ConnectionSettings -> PayloadSplitter
settingsPayloadSplitter (ConnectionSettings _ srv) =
    fixedSizeChunks (maxFrameSize srv)
fixedSizeChunks :: Int -> ByteString -> [ByteString]
fixedSizeChunks 0   _    = error "cannot chunk by zero-length blocks"
fixedSizeChunks _   ""   = []
fixedSizeChunks len bstr =
      let
        (chunk, rest) = ByteString.splitAt len bstr
      in
        chunk : fixedSizeChunks len rest
sendData :: Http2Client -> Http2Stream -> FlagSetter -> ByteString -> IO ()
sendData conn stream flagmod dat = do
    splitter <- _payloadSplitter conn
    let chunks = splitter dat
    let pairs  = reverse $ zip (flagmod : repeat id) (reverse chunks)
    when (null chunks) $ _sendDataChunk stream flagmod ""
    forM_ pairs $ \(flags, chunk) -> _sendDataChunk stream flags chunk
sendDataFrame
  :: Http2FrameClientStream
  -> (FrameFlags -> FrameFlags) -> ByteString -> IO ()
sendDataFrame s flagmod dat = do
    sendOne s flagmod (DataFrame dat)
sendResetFrame :: Http2FrameClientStream -> ErrorCodeId -> IO ()
sendResetFrame s err = do
    sendOne s id (RSTStreamFrame err)
sendGTFOFrame
  :: Http2FrameClientStream
     -> StreamId -> ErrorCodeId -> ByteString -> IO ()
sendGTFOFrame s lastStreamId err errStr = do
    sendOne s id (GoAwayFrame lastStreamId err errStr)
rfcError :: String -> a
rfcError msg = error (msg ++ "draft-ietf-httpbis-http2-17")
sendPingFrame
  :: Http2FrameClientStream
  -> (FrameFlags -> FrameFlags)
  -> ByteString
  -> IO ()
sendPingFrame s flags dat
  | _getStreamId s /= 0        =
        rfcError "PING frames are not associated with any individual stream."
  | ByteString.length dat /= 8 =
        rfcError "PING frames MUST contain 8 octets"
  | otherwise                  = sendOne s flags (PingFrame dat)
sendWindowUpdateFrame
  :: Http2FrameClientStream -> WindowSize -> IO ()
sendWindowUpdateFrame s amount = do
    let payload = WindowUpdateFrame amount
    sendOne s id payload
    return ()
sendSettingsFrame
  :: Http2FrameClientStream
     -> (FrameFlags -> FrameFlags) -> SettingsList -> IO ()
sendSettingsFrame s flags setts
  | _getStreamId s /= 0        =
        rfcError "The stream identifier for a SETTINGS frame MUST be zero (0x0)."
  | otherwise                  = do
    let payload = SettingsFrame setts
    sendOne s flags payload
    return ()
sendPriorityFrame :: Http2FrameClientStream -> Priority -> IO ()
sendPriorityFrame s p = do
    let payload = PriorityFrame p
    sendOne s id payload
    return ()
delayException :: IO a -> IO a
delayException act = act `catch` slowdown
  where
    slowdown :: SomeException -> IO a
    slowdown e = threadDelay 50000 >> throwIO e