packages feed

second-transfer-0.10.0.1: hs-src/SecondTransfer/Http2/Session.hs

-- Session: links frames to streams, and helps in ordering the header frames
-- so that they don't get mixed with header frames from other streams when
-- resources are being served concurrently.
{-# LANGUAGE FlexibleContexts, Rank2Types, TemplateHaskell, OverloadedStrings, BangPatterns #-}
{-# OPTIONS_HADDOCK hide #-}
module SecondTransfer.Http2.Session(
    http2ServerSession
    ,http2ClientSession
    ,getFrameFromSession
    ,sendFirstFrameToSession
    ,sendMiddleFrameToSession
    ,sendCommandToSession
    ,makeClientState
    ,pendingRequests_ClS

    ,CoherentSession
    ,SessionInput(..)
    ,SessionInputCommand(..)
    ,SessionOutput
    ,SessionCoordinates(..)
    ,SessionComponent(..)
    ,SessionsCallbacks(..)
    ,SessionsConfig(..)
    ,ErrorCallback
    ,ClientState(..)
    ,SessionRole(..)

    -- Internal stuff
    ,InputFrame
    ,nextPushStream  -- Exporting just to hide the warning
    ) where


-- System grade utilities imports
import           Control.Concurrent                     (ThreadId)
import           Control.Concurrent.Chan
import qualified Control.Concurrent.BoundedChan         as BC
import           Control.Exception                      (throwTo)
import qualified Control.Exception                      as E
import           Control.Monad                          (
                                                         forever,
                                                         unless,
                                                         when,
--                                                         mapM_,
                                                         forM,
                                                         forM_)
import           Control.Monad.Morph                    (hoist, lift)
import           Control.Monad.IO.Class                 (liftIO, MonadIO)
import           Control.DeepSeq                        (
                                                         --($!!),
                                                        deepseq )
import           Control.Monad.Trans.Reader
--import           Control.Monad.Trans.Class              (lift)
import qualified Control.Monad.Catch                    as CMC

import           Control.Concurrent.MVar
import qualified Data.ByteString                        as B
--import           Data.ByteString.Char8                  (pack,unpack)
import qualified Data.ByteString.Builder                as Bu
import qualified Data.ByteString.Lazy                   as Bl
import           Data.Conduit
--import qualified Data.Conduit                           as Cnd
import qualified Data.HashTable.IO                      as H
import qualified Data.IntSet                            as NS
import           Data.Maybe                             (isJust)
import qualified Data.IORef                             as DIO
import           Data.Typeable

import           Control.Lens

-- No framing layer here... let's use Kazu's Yamamoto library
import qualified Network.HPACK                          as HP
import qualified Network.HTTP2                          as NH2

import qualified Control.Monad.Trans.Resource           as ReT
import           System.Clock                            ( getTime
                                                         , Clock(..)
                                                         , TimeSpec
                                                         )


-- Imports from other parts of the program
import           SecondTransfer.MainLoop.CoherentWorker
import           SecondTransfer.MainLoop.Tokens
import           SecondTransfer.MainLoop.Protocol
import           SecondTransfer.MainLoop.ClientPetitioner

import           SecondTransfer.Sessions.Config
import           SecondTransfer.IOCallbacks.Types       (ConnectionData, addr_CnD)
import           SecondTransfer.Sessions.Internal       (--sessionExceptionHandler,
                                                         SessionsContext,
                                                         sessionsConfig)
import           SecondTransfer.Utils                   (unfoldChannelAndSource)
import           SecondTransfer.Exception
import qualified SecondTransfer.Utils.HTTPHeaders       as He
import qualified SecondTransfer.Http2.TransferTypes     as TT
#ifdef SECONDTRANSFER_MONITORING
import           SecondTransfer.MainLoop.Logging        (logit)

#endif

-- import           Debug.Trace                            (traceStack)


type InputFrame  = NH2.Frame


--useChunkLength :: Int
-- useChunkLength = 2048


-- What to do regarding headers
data HeaderOutputMessage =
    -- Send the headers of the principal stream
    NormalResponse_HM (GlobalStreamId,  Headers, Effect, MVar TT.OutputDataFeed)
    -- Send a push-promise
    |PushPromise_HM   (GlobalStreamId, GlobalStreamId, Headers, Effect)
    -- Send a reset stream notification for the stream given below
    -- --|ResetStream_HM   (GlobalStreamId, Effect)
    -- Send a GoAway, where last-stream is the stream given below.
    |GoAway_HM (GlobalStreamId, Effect)


-- Settings imposed by the peer
data SessionSettings = SessionSettings {
    _pushEnabled_SeS :: DIO.IORef Bool
  , _frameSize_SeS   :: DIO.IORef Int
    }

makeLenses ''SessionSettings



-- Whatever a worker thread is going to need comes here....
-- this is to make refactoring easier, but not strictly needed.
data WorkerThreadEnvironment = WorkerThreadEnvironment {
    -- What's the header stream id?
    _streamId_WTE                 :: GlobalStreamId

    -- For high priority headers information
    , _headersOutput_WTE          :: Chan HeaderOutputMessage

    -- And regular contents can come this way and thus be properly mixed
    -- with everything else.... for now...
    ,_streamBytesSink_WTE         :: MVar TT.OutputDataFeed

    ,_streamsCancelled_WTE        :: MVar NS.IntSet

    ,_sessionSettings_WTE         :: SessionSettings

    ,_nextPushStream_WTE          :: MVar Int

    ,_resetStreamButton_WTE       :: IO ()

    ,_childResetStreamButton_WTE  :: GlobalStreamId -> IO ()
    }

makeLenses ''WorkerThreadEnvironment


-- An HTTP/2 session. Basically a couple of channels ...
type Session = (SessionInput, SessionOutput)


-- From outside, one can only write to this one ... the newtype is to enforce
--    this.
newtype SessionInput = SessionInput ( Chan SessionInputCommand )
sendMiddleFrameToSession :: SessionInput  -> InputFrame -> IO ()
sendMiddleFrameToSession (SessionInput chan) frame = writeChan chan $ MiddleFrame_SIC frame

sendFirstFrameToSession :: SessionInput -> InputFrame -> IO ()
sendFirstFrameToSession (SessionInput chan) frame = writeChan chan $ FirstFrame_SIC frame

sendCommandToSession :: SessionInput  -> SessionInputCommand -> IO ()
sendCommandToSession (SessionInput chan) command = writeChan chan command

newtype SessionOutputChannelAbstraction = SOCA (BC.BoundedChan TT.SessionOutputPacket)

sendOutputToFramer :: SessionOutputChannelAbstraction -> TT.SessionOutputPacket -> IO ()
sendOutputToFramer (SOCA chan) p = {-# SCC serial #-}  p `seq` BC.writeChan chan p

newSessionOutput :: IO SessionOutputChannelAbstraction
newSessionOutput =
  do
    -- It could be length 1, but 8 will give it a bit more of space...
    chan <- BC.newBoundedChan 8
    return . SOCA $ chan

-- From outside, one can only read from this one
type SessionOutput = SessionOutputChannelAbstraction
getFrameFromSession :: SessionOutput -> IO TT.SessionOutputPacket
getFrameFromSession (SOCA chan) = BC.readChan chan


type HashTable k v = H.CuckooHashTable k v


type Stream2HeaderBlockFragment = HashTable GlobalStreamId Bu.Builder


type WorkerMonad = ReaderT WorkerThreadEnvironment IO


-- Have to figure out which are these...but I would expect to have things
-- like unexpected aborts here in this type.
data SessionInputCommand =
    FirstFrame_SIC InputFrame               -- This frame is special
    |MiddleFrame_SIC InputFrame             -- Ordinary frame
    |InternalAbort_SIC                      -- Internal abort from the session itself
    |InternalAbortStream_SIC GlobalStreamId  -- Internal abort, but only for a frame
    |CancelSession_SIC                      -- Cancel request from the framer
  deriving Show



-- The role of a session is either server or client. There are small
-- differences between both.
data SessionRole  =
    Client_SR
   |Server_SR
  deriving (Eq,Show)

-- Here is how we make a session
type SessionMaker = SessionsContext -> IO Session


-- Here is how we make a session wrapping a CoherentWorker
type CoherentSession = AwareWorker -> SessionMaker

data PostInputMechanism = PostInputMechanism (MVar (Maybe B.ByteString), InputDataStream)

------------- Regarding client state
type Message = (Headers,InputDataStream)

type RequestResult = Either ConnectionCloseReason Message

data ClientState = ClientState {
    -- Holds a queue. The client here puts the message (the request) and an VAR
    -- where it will receive the response.
    _pendingRequests_ClS       :: MVar (Message, MVar RequestResult)

    -- This is the id of the next available stream
    ,_nextStream_ClS           :: MVar Int

    -- Says if the client has been closed
    ,_clientIsClosed_ClS      :: MVar Bool

    -- A dictionary from stream id to the client which is waiting for the
    -- message
    ,_response2Waiter_ClS      ::HashTable GlobalStreamId (MVar RequestResult)
    }

makeLenses ''ClientState


makeClientState :: IO ClientState
makeClientState = do
    request_chan <- newEmptyMVar
    next_stream_mvar <- newMVar 3
    client_is_closed_mvar <- newMVar False
    new_h <- H.new

    return ClientState {
        _pendingRequests_ClS = request_chan
        ,_nextStream_ClS = next_stream_mvar
        ,_response2Waiter_ClS = new_h
        ,_clientIsClosed_ClS = client_is_closed_mvar
        }


handleRequest' :: ClientState -> Headers -> InputDataStream -> IO (Headers,InputDataStream)
handleRequest' client_state headers input_data = runReaderT (handleRequest headers input_data) client_state

type ClientMonad = ReaderT ClientState IO

handleRequest :: Headers -> InputDataStream -> ClientMonad Message
handleRequest headers input_data = do
    pending_requests <- view pendingRequests_ClS
    response_mvar <- liftIO $ newEmptyMVar

    liftIO $ E.catch
       (do
           {-# SCC cause1 #-} putMVar pending_requests ((headers,input_data),response_mvar)
           either_reason_or_message <- {-# SCC cause2 #-} liftIO $ takeMVar response_mvar
           case either_reason_or_message of
               Left break_reason -> E.throw $ ClientSessionAbortedException break_reason
               Right message -> return message
       )
       ( ( \ _ -> E.throw $ ClientSessionAbortedException SessionAlreadyClosed_CCR ):: E.BlockedIndefinitelyOnMVar -> IO Message )


instance ClientPetitioner ClientState where

    request = handleRequest'

-------------- end of Regarding client state

-- SessionData is the actual state of the session, including the channels to the framer
-- outside.
--
-- NH2.Frame != Frame
data SessionData = SessionData {
    -- ATTENTION: Ignore the warning coming from here for now
    _sessionsContext             :: SessionsContext

    ,_sessionInput               :: Chan SessionInputCommand

    -- We need to lock this channel occassionally so that we can order multiple
    -- header frames properly....that's the reason for the outer MVar
    --
    --  TODO: This outer MVar is no longer needed, since multi-headers and
    --  and such are handled specially now . REMOVE!
    ,_sessionOutput              :: MVar SessionOutputChannelAbstraction

    -- Use to encode
    ,_toEncodeHeaders            :: MVar HP.DynamicTable

    -- And used to decode
    ,_toDecodeHeaders            :: MVar HP.DynamicTable

    -- While I'm receiving headers, anything which
    -- is not a header should end in the connection being
    -- closed
    ,_receivingHeaders           :: MVar (Maybe Int)

    -- _lastGoodStream is used both to report the last good stream in the
    -- GoAwayFrame and to keep track of streams oppened by the client. In
    -- other words, it contains the stream_id of the last valid client
    -- stream and is updated as soon as the first frame of that stream is
    -- received.
    ,_lastGoodStream             :: MVar GlobalStreamId

    -- Used for decoding the headers... actually, this dictionary should
    -- even contain just one entry... BIG TODO!!!
    ,_stream2HeaderBlockFragment :: Stream2HeaderBlockFragment

    -- Used for worker threads... this is actually a pre-filled template
    -- I make copies of it in different contexts, and as needed.
    ,_forWorkerThread            :: WorkerThreadEnvironment

    -- When acting as a server, this is the handler for processing requests
    ,_awareWorker                :: AwareWorker

    -- When acting as a client, this is where new requests are taken
    -- from
    ,_simpleClient               :: ClientState

    -- Some streams may be cancelled
    ,_streamsCancelled           :: MVar NS.IntSet

    -- Data input mechanism corresponding to some threads
    ,_stream2PostInputMechanism  :: HashTable Int PostInputMechanism

    -- Worker thread register. This is a dictionary from stream id to
    -- the ThreadId of the thread with the worker thread. I use this to
    -- raise asynchronous exceptions in the worker thread if the stream
    -- is cancelled by the client. This way we get early finalization.
    -- Notice that ThreadIds here retain the thread's stack in place.
    ,_stream2WorkerThread        :: HashTable Int ThreadId

    -- Use to retrieve/set the session id
    ,_sessionIdAtSession         :: ! Int

    -- And used to keep peer session settings
    ,_sessionSettings            :: SessionSettings

    -- What is the next stream available for push?
    ,_nextPushStream             :: MVar Int

    -- What role does this session has?
    ,_sessionRole                :: SessionRole

    -- When did we start this session?
    ,_startTime                  :: TimeSpec

    -- The address of the peer.
    ,_peerAddress                :: Maybe HashableSockAddr

    -- Used to decide what to do when some exceptions bubble
    ,_sessionIsEnding            :: DIO.IORef Bool
    }


makeLenses ''SessionData


instance ActivityMeteredSession SessionData where
    sessionLastActivity s = return $ s ^. startTime

instance CleanlyPrunableSession SessionData where
    cleanlyCloseSession s = runReaderT (quietlyCloseConnection NH2.NoError) s


http2ServerSession :: ConnectionData -> AwareWorker -> Int -> SessionsContext -> IO Session
http2ServerSession conn_data a i sctx = http2Session (Just conn_data) Server_SR a (error "NotAClient") i sctx


http2ClientSession :: ClientState -> Int -> SessionsContext -> IO Session
http2ClientSession client_state session_id sctx =
    http2Session Nothing Client_SR (error "NotAServer") client_state session_id sctx


--                                v- {headers table size comes here!!}
http2Session :: Maybe ConnectionData -> SessionRole -> AwareWorker -> ClientState  -> Int -> SessionsContext -> IO Session
http2Session maybe_connection_data session_role aware_worker client_state session_id sessions_context =   do
    session_input             <- newChan
    session_output            <- newSessionOutput
    session_output_mvar       <- newMVar session_output


    -- For incremental construction of headers...
    stream_request_headers    <- H.new :: IO Stream2HeaderBlockFragment

    -- Warning: we should find a way of coping with different table sizes.
    decode_headers_table      <- HP.newDynamicTableForDecoding 4096
    decode_headers_table_mvar <- newMVar decode_headers_table

    encode_headers_table      <- HP.newDynamicTableForEncoding 4096
    encode_headers_table_mvar <- newMVar encode_headers_table

    -- These ones need independent threads taking care of sending stuff
    -- their way...
    headers_output            <- newChan :: IO (Chan HeaderOutputMessage)

    stream2postinputmechanism <- H.new
    stream2workerthread       <- H.new
    last_good_stream_mvar     <- newMVar (-1)

    receiving_headers         <- newMVar Nothing
    frame_size_ioref          <- DIO.newIORef (sessions_context ^.  sessionsConfig . dataFrameSize)
    push_enabled_ioref        <- DIO.newIORef (sessions_context ^.  sessionsConfig . pushEnabled)
    let
        session_settings = SessionSettings {
            _pushEnabled_SeS = push_enabled_ioref
          , _frameSize_SeS   = frame_size_ioref
            }
    next_push_stream          <- newMVar (sessions_context ^. sessionsConfig . firstPushStream )

    start_time                <- getTime Monotonic
    session_is_ending_ioref   <- DIO.newIORef False

    -- What about stream cancellation?
    cancelled_streams_mvar    <- newMVar $ NS.empty :: IO (MVar NS.IntSet)

    let
        for_worker_thread = WorkerThreadEnvironment {
             _streamId_WTE = error "NotInitialized"
          ,  _headersOutput_WTE = headers_output
          ,  _streamsCancelled_WTE = cancelled_streams_mvar
          ,  _sessionSettings_WTE = session_settings
          ,  _nextPushStream_WTE = next_push_stream
          ,  _resetStreamButton_WTE = error "Not initialized"
          ,  _childResetStreamButton_WTE = error "Not initialized"
          ,  _streamBytesSink_WTE = error "Not initialized"
        }

        maybe_hashable_addr = case maybe_connection_data of
            Just connection_info ->  connection_info ^. addr_CnD
            Nothing -> Nothing

    let session_data  = SessionData {
        _sessionsContext             = sessions_context
        ,_sessionInput               = session_input
        ,_sessionOutput              = session_output_mvar
        ,_toDecodeHeaders            = decode_headers_table_mvar
        ,_toEncodeHeaders            = encode_headers_table_mvar
        ,_stream2HeaderBlockFragment = stream_request_headers
        ,_forWorkerThread            = for_worker_thread
        ,_awareWorker                = aware_worker
        ,_simpleClient               = client_state
        ,_streamsCancelled           = cancelled_streams_mvar
        ,_stream2PostInputMechanism  = stream2postinputmechanism
        ,_stream2WorkerThread        = stream2workerthread
        ,_sessionIdAtSession         = session_id
        ,_receivingHeaders           = receiving_headers
        ,_sessionSettings            = session_settings
        ,_lastGoodStream             = last_good_stream_mvar
        ,_nextPushStream             = next_push_stream
        ,_sessionRole                = session_role
        ,_startTime                  = start_time
        ,_peerAddress                = maybe_hashable_addr
        ,_sessionIsEnding            = session_is_ending_ioref
        }

    let

        new_session :: HashableSockAddr -> SessionGenericHandle -> forall a . a -> IO ()
        new_session a b c = case maybe_callback of
            Just (NewSessionCallback callback) -> callback a b c
            Nothing -> return ()
          where
            maybe_callback =
                (sessions_context ^. (sessionsConfig . sessionsCallbacks . newSessionCallback_SC) )

        io_exc_handler :: SessionComponent -> E.BlockedIndefinitelyOnMVar -> IO ()
        io_exc_handler _component _e = do
           case session_role of
               Server_SR -> do
                   -- sessionExceptionHandler component session_id sessions_context e
                   -- Just ignore this kind of exceptions here, very explicitly, as they should naturally
                   -- happen when the framer is closed
                   return ()

               Client_SR -> do
                   clientSideTerminate client_state IOChannelClosed_CCR


        exc_handler :: SessionComponent -> HTTP2SessionException -> IO ()
        exc_handler _component e = do
            case session_role of
                Server_SR ->
                    -- May be leaking space here, disabling for now since we are not using it.
                    -- sessionExceptionHandler component session_id sessions_context e
                    return ()

                Client_SR -> do
                    let
                        maybe_client_session_aborted :: Maybe ClientSessionAbortedException
                        maybe_client_session_aborted | HTTP2SessionException ee <- e  = cast ee

                        maybe_protocol_error :: Maybe HTTP2ProtocolException
                        maybe_protocol_error | HTTP2SessionException ee <- e = cast ee

                    case maybe_client_session_aborted of
                        Just (ClientSessionAbortedException reason) -> do
                            clientSideTerminate client_state reason

                        Nothing ->
                            if isJust maybe_protocol_error
                                then
                                    clientSideTerminate client_state ProtocolError_CCR
                                else do
                                    E.throw e

        exc_guard :: SessionComponent -> IO () -> IO ()
        exc_guard component action =
                E.catch
                    (E.catch
                         action
                         (\e -> do
                             -- INSTRUMENTATION( errorM "HTTP2.Session" "Exception processed" )
                             exc_handler component e
                         )
                    )
                    (io_exc_handler component)

    -- Create an input thread that decodes frames...
    _ <- forkIOExc "s2f1" $ exc_guard SessionInputThread_HTTP2SessionComponent
           $ runReaderT sessionInputThread session_data

    -- Create a thread that captures headers and sends them down the tube
    _ <- forkIOExc "s2f2" $ exc_guard SessionHeadersOutputThread_HTTP2SessionComponent
           $ runReaderT (headersOutputThread headers_output session_output_mvar) session_data

    -- New session! TODO: Have to fix this manager code maybe?
    case maybe_hashable_addr of
          Just hashable_addr ->
              new_session
                 hashable_addr
                 (Whole_SGH session_data)
                 session_data

          Nothing ->
              putStrLn "Warning, created session without registering it"


    -- If I'm a client, I also need a thread to poll for requests
    when (session_role == Client_SR) $ do
         _ <- forkIOExc "s2f4" $ exc_guard SessionClientPollThread_HTTP2SessionComponent
                $ sessionPollThread session_data headers_output
         return ()


    -- The two previous threads fill the session_output argument below (they write to it)
    -- the session machinery in the other end is in charge of sending that data through the
    -- socket.

    return ( (SessionInput session_input),
             session_output )

-- | Takes frames from the Framer and starts streams mini-workers to make sense
-- of them.
--
-- TODO: Really limit the number of streams that a single client is allowed to have active.
--
-- TODO: Some ill clients can break this thread with exceptions. Make these paths a bit
--- more robust.
sessionInputThread :: ReaderT SessionData IO ()
sessionInputThread  = do
    -- This is an introductory and declarative block... all of this is tail-executed
    -- every time that  a packet needs to be processed. It may be a good idea to abstract
    -- these values in a closure...
    session_input             <- view sessionInput

    -- decode_headers_table_mvar <- view toDecodeHeaders
    -- stream_request_headers    <- view stream2HeaderBlockFragment
    cancelled_streams_mvar    <- view streamsCancelled
    -- coherent_worker           <- view awareWorker

    -- for_worker_thread_uns     <- view forWorkerThread
    stream2workerthread       <- view stream2WorkerThread
    -- receiving_headers_mvar    <- view receivingHeaders
    -- last_good_stream_mvar     <- view lastGoodStream
    -- current_session_id        <- view sessionIdAtSession

    input                     <- {-# SCC session_input #-} liftIO $ readChan session_input
    session_role              <- view sessionRole

    case input of

        FirstFrame_SIC
            (NH2.Frame
                (NH2.FrameHeader _ 0 null_stream_id )
                (NH2.SettingsFrame settings_list)
            ) | 0 == null_stream_id  -> do
            -- Good, handle
            handleSettingsFrame settings_list
            continue

        FirstFrame_SIC (NH2.Frame
            (NH2.FrameHeader _ 1 null_stream_id )  (NH2.SettingsFrame _ ) ) |  0 == null_stream_id  -> do
            -- This is a SETTINGS ACK frame, which is okej to have,
            -- do nothing here
            continue

        FirstFrame_SIC _ -> do
            -- Bad, incorrect id or god knows only what ....
            -- liftIO $ putStrLn "cc1"
            closeConnectionBecauseIsInvalid NH2.ProtocolError
            return ()

        CancelSession_SIC -> do
            -- Good place to tear down worker threads... Let the rest of the finalization
            -- to the framer.
            --
            -- This message is normally got from the Framer
            --
            cancellAllStreams

            -- We do not continue here, but instead let it finish
            return ()

        InternalAbort_SIC -> do
            -- Message triggered because the worker failed to behave.
            -- When this is sent, the connection is closed
            closeConnectionBecauseIsInvalid NH2.InternalError
            return ()

        InternalAbortStream_SIC stream_id -> do
            -- Message triggered because the worker failed to behave, but
            -- here we believe that it is not necessary to tear down the
            -- entire session and therefore it's enough with a stream
            -- reset
            -- liftIO $ putStrLn "cc4"
            sendOutPriorityTrain
                (NH2.EncodeInfo
                    NH2.defaultFlags
                    stream_id
                    Nothing
                )
                (NH2.RSTStreamFrame NH2.InternalError
                )
            continue

        -- The block below will process both HEADERS and CONTINUATION frames.
        -- TODO: As it stands now, the server will happily start a new stream with
        -- a CONTINUATION frame instead of a HEADERS frame. That's against the
        -- protocol.
        MiddleFrame_SIC frame | Just (_stream_id, _bytes) <- isAboutHeaders frame ->
            case session_role of
                Server_SR -> do
                    -- Just append the frames to streamRequestHeaders
                    serverProcessIncomingHeaders frame
                    continue

                Client_SR -> do
                    clientProcessIncomingHeaders frame
                    continue

        -- Peer can order a stream aborted, meaning that we shall not send any more data
        -- on it.
        MiddleFrame_SIC frame@(NH2.Frame _ (NH2.RSTStreamFrame _error_code_id)) -> do
            let stream_id = streamIdFromFrame frame
            is_iddle <- streamIsIdle stream_id
            if ( stream_id == 0 || (is_iddle && odd stream_id ) )
              then do
                closeConnectionBecauseIsInvalid NH2.ProtocolError
                return ()
              else do
                liftIO $ do
                    -- putStrLn $ "StreamReset " ++ show (_error_code_id)
                    cancelled_streams <- takeMVar cancelled_streams_mvar
                    putMVar cancelled_streams_mvar $ NS.insert  stream_id cancelled_streams
                closePostDataSource stream_id
                liftIO $ do
                    maybe_thread_id <- H.lookup stream2workerthread stream_id
                    case maybe_thread_id  of
                        Nothing ->
                            -- This is can actually happen in some implementations: we are asked to
                            -- cancel an stream we know nothing about.
                            return ()

                        Just thread_id -> do
                            -- INSTRUMENTATION( infoM "HTTP2.Session" $ "Stream successfully interrupted" )
                            throwTo thread_id StreamCancelledException
                            H.delete stream2workerthread stream_id

                continue

        MiddleFrame_SIC _frame@(NH2.Frame frame_header (NH2.WindowUpdateFrame _credit) ) -> do
             -- The Framer is the one using this information, here I just merely inspect the length and destroy
             -- the session if that length is not good
             let frame_length = frameLength frame_header
             if frame_length /= 4
               then do
                 closeConnectionBecauseIsInvalid NH2.FrameSizeError
                 return ()
               else
                 continue

        MiddleFrame_SIC frame@(NH2.Frame (NH2.FrameHeader _ _ nh2_stream_id) (NH2.DataFrame somebytes)) -> unlessReceivingHeaders $ do
            -- So I got data to process
            -- TODO: Handle end of stream
            let stream_id = nh2_stream_id

            -- The call below will block if there is not space in the mvar which is sending data to the
            was_ok <- streamWorkerSendData stream_id somebytes

            if was_ok
              then do
                -- After that data has been received and forwarded downstream, we can issue a windows update
                --
                --
                -- TODO: Consider that the best place to output these frames can be somewhere else...
                --
                -- TODO: Use a special, with-quota queue here to do flow control. Don't send meaningless
                --       WindowUpdateFrame's
                sendOutPriorityTrainMany [
                      (
                        (NH2.EncodeInfo
                            NH2.defaultFlags
                            nh2_stream_id
                            Nothing
                        ),
                        (NH2.WindowUpdateFrame
                            (fromIntegral (B.length somebytes))
                        )
                      ),

                      (
                        (NH2.EncodeInfo
                            NH2.defaultFlags
                            0
                            Nothing
                        ),
                        (NH2.WindowUpdateFrame
                            (fromIntegral (B.length somebytes))
                        )
                      )
                    ]

                if frameEndsStream frame
                  then do
                    -- Good place to close the source ...
                    closePostDataSource stream_id
                  else
                    return ()

                continue
              else do
                -- For some reason there is no PostInput processing mechanism, therefore,
                -- we were not expecting data at this point
                closeConnectionBecauseIsInvalid NH2.ProtocolError
                return ()

        MiddleFrame_SIC (NH2.Frame frame_header (NH2.PingFrame _)) | not (isStreamZero frame_header)  || frameLength frame_header /= 8  -> do
            closeConnectionBecauseIsInvalid NH2.ProtocolError
            return ()

        MiddleFrame_SIC (NH2.Frame (NH2.FrameHeader _ flags _) (NH2.PingFrame _)) | NH2.testAck flags-> do
            -- Deal with pings: this is an Ack, so do nothing
            -- In the future we may have some metric information here....
            continue

        MiddleFrame_SIC (NH2.Frame (NH2.FrameHeader _ _ _) (NH2.PingFrame somebytes))  -> do
            -- Deal with pings: NOT an Ack, so answer
            -- INSTRUMENTATION( debugM "HTTP2.Session" "Ping processed" )
            sendOutPriorityTrain
                (NH2.EncodeInfo
                    (NH2.setAck NH2.defaultFlags)
                    0
                    Nothing
                )
                (NH2.PingFrame somebytes)

            continue

        MiddleFrame_SIC (NH2.Frame frame_header (NH2.SettingsFrame settings_list))
          | frameLength frame_header `mod` 6 /= 0 -> do
            liftIO . putStrLn $ "FrameSizeError"
            closeConnectionBecauseIsInvalid NH2.FrameSizeError
            return ()

          | isSettingsAck frame_header && isStreamZero frame_header && isLengthZero frame_header  -> do
            -- Frame was received by the peer, do nothing here...
            continue

          | not (isSettingsAck frame_header) && isStreamZero frame_header -> do
            handleSettingsFrame settings_list
            continue

          | otherwise  -> do
            -- Frame was received by the peer, do nothing here...
            liftIO . putStrLn $ "SettingsHasWrongSize"
            closeConnectionBecauseIsInvalid NH2.ProtocolError
            return ()

        MiddleFrame_SIC (NH2.Frame _ (NH2.GoAwayFrame _ _err _ ))
            | Server_SR <- session_role -> do
                -- I was sent a go away, so go-away...
                --liftIO . putStrLn $ "Received GoAway frame"
                quietlyCloseConnection NH2.NoError
                return ()

            | Client_SR <- session_role -> do
                -- I was sent a go away, so go-away, but use the kind of exception
                -- that will unwind the stack gracefully
                _ <- closeConnectionForClient NH2.NoError
                return ()

        MiddleFrame_SIC (NH2.Frame  (NH2.FrameHeader _ _ nh2_stream_id) (NH2.PriorityFrame NH2.Priority {NH2.exclusive=_e, NH2.streamDependency=dep_id, NH2.weight=_w}  ) )
            | nh2_stream_id == dep_id -> do
                closeConnectionBecauseIsInvalid NH2.ProtocolError
                return ()

            | otherwise ->
                continue

        MiddleFrame_SIC _somethingelse ->  unlessReceivingHeaders $ do
            -- An undhandled case here....
            liftIO $ putStrLn $ "Unhandled " ++ show _somethingelse
            continue

  where
    continue = sessionInputThread

    -- TODO: Do use the settings!!!
    handleSettingsFrame :: NH2.SettingsList -> ReaderT SessionData IO ()
    handleSettingsFrame _settings_list = do
        session_settings <- view sessionSettings
        sessions_config <- view $ sessionsContext . sessionsConfig
        let
            enable_push = lookup NH2.SettingsEnablePush _settings_list
            max_frame_size = lookup NH2.SettingsMaxFrameSize _settings_list

            -- Handled by the framer, but errors should be reported here.
            max_flow_control_size = lookup NH2.SettingsInitialWindowSize _settings_list

        ok <- case enable_push of
            Just 1      -> do
                liftIO $ DIO.writeIORef (session_settings ^. pushEnabled_SeS) True
                return True
            Just 0      -> do
                liftIO $ DIO.writeIORef (session_settings ^. pushEnabled_SeS) False
                return True
            Just _      -> do
                closeConnectionBecauseIsInvalid NH2.ProtocolError
                return True

            Nothing     ->  do
                return True

        ok2 <- if ok
                 then
                   case max_frame_size of
                       -- The spec says clearly what's the minimum size that can come here
                       Just n | n < 16384 || n > 16777215
                                   -> do
                                      -- liftIO $ putStrLn "Wild max frame size"
                                      closeConnectionBecauseIsInvalid NH2.FrameSizeError
                                      return False

                              | otherwise
                                   ->
                                      if n > (sessions_config ^. dataFrameSize)
                                         -- Ignore if it is bigger than the size configured in this context
                                         then do
                                            return True
                                         else do
                                             liftIO $  DIO.writeIORef (session_settings ^. frameSize_SeS) n
                                             return True
                       Nothing     -> return True
                 else
                   return False

        ok3 <- if ok2
                 then
                   case max_flow_control_size of
                       Just n
                         | n > 2147483647 || n < 0
                             -> do
                                    closeConnectionBecauseIsInvalid NH2.FlowControlError
                                    return False
                         | otherwise
                            -> return True
                       Nothing -> return True
                 else
                   return False


        if ok3
          then
            sendOutPriorityTrain
                (NH2.EncodeInfo
                    (NH2.setAck NH2.defaultFlags)
                    0
                    Nothing
                )
                (NH2.SettingsFrame [])
          else
            return ()



streamIsIdle :: GlobalStreamId -> ReaderT SessionData IO Bool
streamIsIdle stream_id =
  do
    last_good_stream_mvar <- view lastGoodStream
    last_good_stream <- liftIO . readMVar $ last_good_stream_mvar
    return ( stream_id > last_good_stream )


serverProcessIncomingHeaders :: NH2.Frame ->  ReaderT SessionData IO ()
serverProcessIncomingHeaders frame | Just (!stream_id, bytes) <- isAboutHeaders frame = do

    -- Just append the frames to streamRequestHeaders
    opens_stream              <- appendHeaderFragmentBlock stream_id bytes
    receiving_headers_mvar    <- view receivingHeaders
    last_good_stream_mvar     <- view lastGoodStream
    for_worker_thread_uns     <- view forWorkerThread
    decode_headers_table_mvar <- view toDecodeHeaders
    stream_request_headers    <- view stream2HeaderBlockFragment
    coherent_worker           <- view awareWorker
    current_session_id        <- view sessionIdAtSession
    session_input             <- view sessionInput
    stream2workerthread       <- view stream2WorkerThread
    maybe_hashable_addr       <- view peerAddress

    if opens_stream
      then {-# SCC gpAb #-} do
        --maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar
        all_ok <- liftIO . modifyMVar receiving_headers_mvar $ \  maybe_rcv_headers_of ->
            case maybe_rcv_headers_of of
                Just _ ->
                    -- Bad peer, it is already sending headers
                    -- and trying to open another one
                    return (maybe_rcv_headers_of, False)
                Nothing -> do
                    return (Just stream_id, True)
        if all_ok
          then do
              -- And go to check if the stream id is valid
              ok2 <- liftIO . modifyMVar last_good_stream_mvar $  \ last_good_stream ->
                  if (odd stream_id ) && (stream_id > last_good_stream)
                    then
                        -- We are golden, set the new good stream
                        return (stream_id, True)
                    else
                        -- The new oppened stream has a new id
                        return (stream_id, False)
              unless ok2 $
                  closeConnectionBecauseIsInvalid NH2.ProtocolError
          else do
            closeConnectionBecauseIsInvalid NH2.ProtocolError
      else {-# SCC gpcb #-} do
        maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar
        case maybe_rcv_headers_of of
            Just a_stream_id
              | a_stream_id == stream_id -> do
                  -- Nothing to complain about
                  liftIO $ putMVar receiving_headers_mvar maybe_rcv_headers_of
              | otherwise ->
                  error "IncorrectStreamId1"

            Nothing -> error "InternalError, this should be set"

    if frameEndsHeaders frame then
      do
        -- Ok, let it be known that we are not receiving more headers
        liftIO $ modifyMVar_
            receiving_headers_mvar
            (\ _ -> return Nothing )
        -- Lets get a time
        headers_arrived_time      <- liftIO $ getTime Monotonic

        -- This is where the bytes of the stream will end up .
        stream_bytes <- liftIO newEmptyMVar

        let
            reset_button  = writeChan session_input (InternalAbortStream_SIC stream_id)
            child_reset_button = \stream_id' -> writeChan session_input  (InternalAbortStream_SIC stream_id')
            -- Prepare the environment for the new working thread
            for_worker_thread     =
                (set streamId_WTE stream_id)
                .
                (set streamBytesSink_WTE stream_bytes)
                .
                (set resetStreamButton_WTE reset_button)
                .
                (set childResetStreamButton_WTE child_reset_button)
                $
                for_worker_thread_uns

        -- Let's decode the headers
        headers_bytes             <- getHeaderBytes stream_id
        dyn_table                 <- liftIO $ takeMVar decode_headers_table_mvar
        maybe_table <- liftIO $
            E.catch
                (do
                   r  <- HP.decodeHeader dyn_table headers_bytes
                   return . Right $ r )
                ((const $ return $ Left () ):: HP.DecodeError -> IO (Either () (HP.DynamicTable, HP.HeaderList)))

        case maybe_table of
            Left _ -> do
                liftIO $ putStrLn "InvalidHeadersReceived"
                closeConnectionBecauseIsInvalid NH2.ProtocolError

            Right (new_table, header_list ) -> do

                -- /DEBUG
                -- Good moment to remove the headers from the table.... we don't want a space
                -- leak here
                liftIO $ do
                    H.delete stream_request_headers stream_id
                    putMVar decode_headers_table_mvar new_table

                -- TODO: Validate headers, abort session if the headers are invalid.
                -- Otherwise other invariants will break!!
                -- THIS IS PROBABLY THE BEST PLACE FOR DOING IT.
                let
                    headers_editor = He.fromList header_list

                maybe_good_headers_editor <- validateIncomingHeadersServer headers_editor

                good_headers <- case maybe_good_headers_editor of
                    Just yes_they_are_good -> return yes_they_are_good
                    Nothing -> {-# SCC ccB3 #-} do
                        closeConnectionBecauseIsInvalid NH2.ProtocolError
                        return . error $ "NotUsedHeaderRepr"

                -- Add any extra headers, on demand
                --headers_extra_good      <- addExtraHeaders good_headers
                let
                    headers_extra_good = good_headers
                    header_list_after = He.toList headers_extra_good
                -- liftIO $ putStrLn $ "header list after " ++ (show header_list_after)

                -- If the headers end the request....
                post_data_source <- if not (frameEndsStream frame)
                  then do
                    mechanism <- createMechanismForStream stream_id
                    let source = postDataSourceFromMechanism mechanism
                    return $ Just source
                  else do
                    return Nothing

                let
                  perception = Perception {
                      _startedTime_Pr = headers_arrived_time,
                      _streamId_Pr = stream_id,
                      _sessionId_Pr = current_session_id,
                      _protocol_Pr = Http2_HPV,
                      _anouncedProtocols_Pr = Nothing,
                      _peerAddress_Pr = maybe_hashable_addr
                      }
                  request' = Request {
                      _headers_RQ = header_list_after,
                      _inputData_RQ = post_data_source,
                      _perception_RQ = perception
                      }

                -- TODO: Handle the cases where a request tries to send data
                -- even if the method doesn't allow for data.

                -- I'm clear to start the worker, in its own thread
                --
                -- NOTE: Some late internal errors from the worker thread are
                --       handled here by closing the session.
                --
                -- TODO: Log exceptions handled here.

                session_is_ending_ioref <- view sessionIsEnding
                liftIO $ do
                    -- The mvar below: avoid starting until the entry has
                    -- been properly inserted in the table...
                    ready <- newMVar ()
                    let
                        general_exc_handler :: E.SomeException -> IO ()
                        general_exc_handler e = do
                            -- Actions to take when the thread breaks....
                            -- We cancel the entire session because there is a more specific
                            -- handler that doesn't somewhere below. If the exception bubles here,
                            -- it is because the situation is out of control. We may as well
                            -- exit the server, but I'm not being so extreme now.
                            H.delete stream2workerthread stream_id
                            session_is_ending <- DIO.readIORef session_is_ending_ioref
                            unless session_is_ending $ do
                                putStrLn $ "ERROR: Aborting session after non-handled exception bubbled up " ++ E.displayException e
                                writeChan session_input InternalAbort_SIC
                        io_closed_handle :: E.BlockedIndefinitelyOnMVar -> IO ()
                        io_closed_handle _e = return ()
                    thread_id <- forkIOExc "s2f7" . E.handle general_exc_handler . E.handle io_closed_handle $
                        ({-# SCC growP1 #-} do
                            putMVar ready ()
                            runReaderT
                                (workerThread
                                       request'
                                       coherent_worker)
                                for_worker_thread
                            H.delete stream2workerthread stream_id
                        )
                    H.insert stream2workerthread stream_id thread_id
                    takeMVar ready



                return ()
    else
        -- Frame doesn't end the headers... it was added before... so
        -- probably do nothing
        return ()

serverProcessIncomingHeaders _ = error "serverProcessIncomingHeadersNotDefined"

clientProcessIncomingHeaders :: NH2.Frame ->  ReaderT SessionData IO ()
clientProcessIncomingHeaders frame | Just (stream_id, bytes) <- isAboutHeaders frame = do

    opens_stream              <- appendHeaderFragmentBlock stream_id bytes
    receiving_headers_mvar    <- view receivingHeaders
    last_good_stream_mvar     <- view lastGoodStream
    decode_headers_table_mvar <- view toDecodeHeaders
    stream_request_headers    <- view stream2HeaderBlockFragment
    response2waiter           <- view (simpleClient . response2Waiter_ClS)

    if opens_stream
      then do
        maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar
        case maybe_rcv_headers_of of
          Just _ -> do
            -- Bad peer, it is already sending headers
            -- and trying to open another one
            closeConnectionBecauseIsInvalid NH2.ProtocolError
            -- An exception will be thrown above, so to not complicate
            -- control flow here too much.
          Nothing -> do
            -- Signal that we are receiving headers now, for this stream
            liftIO $ putMVar receiving_headers_mvar (Just stream_id)
            -- And go to check if the stream id is valid
            --last_good_stream <- liftIO $ takeMVar last_good_stream_mvar
            stream_initiated_by_client <- streamInitiatedByClient stream_id
            if (odd stream_id ) && stream_initiated_by_client
              then do
                -- We are golden, set the new good stream
                liftIO $ putMVar last_good_stream_mvar (stream_id)
              else do
                -- We are not golden
                -- TODO: Control for pushed streams here.
                closeConnectionBecauseIsInvalid NH2.ProtocolError
      else do
        maybe_rcv_headers_of <- liftIO $ takeMVar receiving_headers_mvar
        case maybe_rcv_headers_of of
            Just a_stream_id
              | a_stream_id == stream_id -> do
                   -- Nothing to complain about
                   liftIO $ putMVar receiving_headers_mvar maybe_rcv_headers_of
              | otherwise -> error "StreamIdMismatch3"

            Nothing -> error "InternalError, this should be set"

    if frameEndsHeaders frame then
      do
        -- Ok, let it be known that we are not receiving more headers
        liftIO $ modifyMVar_
            receiving_headers_mvar
            (\ _ -> return Nothing )
        -- Lets get a time
        -- headers_arrived_time      <- liftIO $ getTime Monotonic
        -- Let's decode the headers
        headers_bytes             <- getHeaderBytes stream_id
        dyn_table                 <- liftIO $ takeMVar decode_headers_table_mvar
        (new_table, header_list ) <- liftIO $ HP.decodeHeader dyn_table headers_bytes

        -- Good moment to remove the headers from the table.... we don't want a space
        -- leak here
        liftIO $ do
            H.delete stream_request_headers stream_id
            putMVar decode_headers_table_mvar new_table

        -- TODO: Validate headers, abort session if the headers are invalid.
        -- Otherwise other invariants will break!!
        -- THIS IS PROBABLY THE BEST PLACE FOR DOING IT.
        let
            headers_editor = He.fromList header_list

        maybe_good_headers_editor <- validateIncomingHeadersClient headers_editor

        good_headers <- case maybe_good_headers_editor of
            Just yes_they_are_good -> return yes_they_are_good
            -- Function below throws an exception and therefore closes everything
            -- TODO: Can we device smoother ways of terminating the session?
            Nothing -> do
                closeConnectionBecauseIsInvalid NH2.ProtocolError
                return . error $ "NotUsedHeaderRepr2"

        -- If the headers end the request....
        post_data_source <- if not (frameEndsStream frame)
          then do
            mechanism <-  createMechanismForStream stream_id
            return $ postDataSourceFromMechanism mechanism
          else
            return $ return ()

        (Just response_mvar) <- liftIO $ H.lookup response2waiter stream_id
        liftIO $ putMVar response_mvar $ Right (He.toList good_headers, post_data_source)

        return ()
    else
        -- Frame doesn't end the headers... it was added before... so
        -- probably do nothing
        return ()

clientProcessIncomingHeaders _ = error "Not defined"

streamInitiatedByClient :: GlobalStreamId -> ReaderT SessionData IO Bool
streamInitiatedByClient stream_id = do
    response2waiter <- view (simpleClient . response2Waiter_ClS)
    response_handle_maybe <- liftIO $ H.lookup response2waiter stream_id
    return $ isJust response_handle_maybe


sendOutPriorityTrain :: NH2.EncodeInfo -> NH2.FramePayload  -> ReaderT SessionData IO ()
sendOutPriorityTrain encode_info payload = do
    session_output_mvar <- view sessionOutput

    liftIO $ withMVar session_output_mvar $ \ session_output ->
      sendOutputToFramer session_output  $ TT.PriorityTrain_StFB [(
          encode_info,
          payload,
          -- Not sending effects in this frame, since it is not related...
          error "sendOutFrameNotFor")]


sendOutPriorityTrainMany :: [(NH2.EncodeInfo , NH2.FramePayload)]  -> ReaderT SessionData IO ()
sendOutPriorityTrainMany many = do
    session_output_mvar <- view sessionOutput

    liftIO $ withMVar session_output_mvar $ \ session_output ->
        sendOutputToFramer session_output $
            TT.PriorityTrain_StFB $
            map
                (\(encode_info, payload) -> (encode_info, payload, error "no-effect"))
                many


-- TODO: Close connection on unexepcted pseudo-headers
validateIncomingHeadersServer :: He.HeaderEditor -> ReaderT SessionData IO (Maybe He.HeaderEditor)
validateIncomingHeadersServer headers_editor = do
    -- Check that the headers block comes with all mandatory headers.
    -- Right now I'm not checking that they come in the mandatory order though...
    --
    -- Notice that this function will transform a "host" header to an ":authority"
    -- one.
    let
        h1 = He.replaceHostByAuthority headers_editor
        -- Check that headers are lowercase
        headers_are_lowercase = He.headersAreLowercaseAtHeaderEditor headers_editor
        -- Check that we have mandatory headers
        maybe_authority = h1 ^. (He.headerLens ":authority")
        maybe_method    = h1 ^. (He.headerLens ":method")
        maybe_scheme    = h1 ^. (He.headerLens ":scheme")
        maybe_path      = h1 ^. (He.headerLens ":path")

    if
        (isJust maybe_authority) &&
        (isJust maybe_method) &&
        (isJust maybe_scheme) &&
        (isJust maybe_path ) &&
        headers_are_lowercase
        then
            return (Just h1)
        else
            return Nothing

validateIncomingHeadersClient :: He.HeaderEditor -> ReaderT SessionData IO (Maybe He.HeaderEditor)
validateIncomingHeadersClient headers_editor = do
    -- Check that the headers block comes with all mandatory headers.
    -- Right now I'm not checking that they come in the mandatory order though...
    --
    -- Notice that this function will transform a "host" header to an ":authority"
    -- one.
    let
        h1 = He.replaceHostByAuthority headers_editor
        -- Check that headers are lowercase
        headers_are_lowercase = He.headersAreLowercaseAtHeaderEditor headers_editor
        -- Check that we have mandatory headers
        maybe_status = h1 ^. (He.headerLens ":status")

    if (isJust maybe_status) && headers_are_lowercase
        then
            return (Just h1)
        else
            return Nothing


-- Sends a GO_AWAY frame and raises an exception, effectively terminating the input
-- thread of the session.
closeConnectionBecauseIsInvalid :: NH2.ErrorCodeId -> ReaderT SessionData IO ()
closeConnectionBecauseIsInvalid error_code = do
    last_good_stream_mvar <- view lastGoodStream
    last_good_stream <- liftIO $ takeMVar last_good_stream_mvar

    requestTermination last_good_stream error_code
    cancellAllStreams


-- Sends a GO_AWAY frame and closes everything, without being too drastic
quietlyCloseConnection :: NH2.ErrorCodeId -> ReaderT SessionData IO ()
quietlyCloseConnection error_code = do
    -- liftIO $ putStrLn "quietlyCloseConnection"
    last_good_stream_mvar <- view lastGoodStream
    last_good_stream <- liftIO $ takeMVar last_good_stream_mvar

    cancellAllStreams
    requestTermination last_good_stream error_code


-- Sends a GO_AWAY frame and raises an exception, effectively terminating the input
-- thread of the session. This one for the client is different because it throws
-- an exception of type ClientSessionAbortedException
closeConnectionForClient :: NH2.ErrorCodeId -> ReaderT SessionData IO a
closeConnectionForClient error_code = do
    let
        use_reason = case error_code of
            NH2.NoError -> NormalTermination_CCR
            _           -> ProtocolError_CCR

    last_good_stream_mvar <- view lastGoodStream
    last_good_stream <- liftIO $ takeMVar last_good_stream_mvar

    client_is_closed_mvar <- view (simpleClient . clientIsClosed_ClS )
    requestTermination last_good_stream error_code
    cancellAllStreams
    liftIO $ do

        -- Let's also mark the session as closed from the client side, so that
        -- any further requests end with the correct exception
        modifyMVar_ client_is_closed_mvar (\ _ -> return True)

        -- And unwind the input thread in the session, so that the
        -- exception handler runs....
        E.throw $ ClientSessionAbortedException use_reason


cancellAllStreams :: ReaderT SessionData IO ()
cancellAllStreams =
  do
    session_is_ending_ioref <- view sessionIsEnding
    stream2workerthread <- view stream2WorkerThread

    liftIO $ do
        DIO.writeIORef session_is_ending_ioref True
        -- Close all active threads for this session
        H.mapM_
            ( \(_stream_id, thread_id) ->
                    throwTo thread_id StreamCancelledException
            )
            stream2workerthread


requestTermination :: GlobalStreamId -> NH2.ErrorCodeId -> ReaderT SessionData IO ()
requestTermination stream_id error_code =
  do
    session_is_ending_ioref <- view sessionIsEnding
    liftIO $ DIO.writeIORef session_is_ending_ioref True
    session_output_mvar <- view sessionOutput
    let
        message = TT.Command_StFB (TT.SpecificTerminate_SOC stream_id error_code)
    liftIO . withMVar session_output_mvar $ \ session_output -> do
        sendOutputToFramer session_output message


frameEndsStream :: InputFrame -> Bool
frameEndsStream (NH2.Frame (NH2.FrameHeader _ flags _) _)  = NH2.testEndStream flags


-- Executes its argument, unless receiving
-- headers, in which case the connection is closed.
unlessReceivingHeaders :: ReaderT SessionData IO a -> ReaderT SessionData IO a
unlessReceivingHeaders comp = do
    receiving_headers_mvar    <- view receivingHeaders
    -- First check if we are receiving headers
    maybe_recv_headers <- liftIO $ readMVar receiving_headers_mvar
    if isJust maybe_recv_headers
      then do
        -- So, this frame is highly illegal
        closeConnectionBecauseIsInvalid NH2.ProtocolError
        return . error $ "NotUsedRepr3"
      else
        comp


createMechanismForStream :: GlobalStreamId -> ReaderT SessionData IO PostInputMechanism
createMechanismForStream stream_id = do
    (chan, source) <- liftIO $ unfoldChannelAndSource
    stream2postinputmechanism <- view stream2PostInputMechanism
    let pim = PostInputMechanism (chan, hoist lift  source)
    liftIO $ H.insert stream2postinputmechanism stream_id pim
    return pim


-- TODO: Can be optimized by factoring out the mechanism lookup
-- TODO IMPORTANT: This is a good place to drop the postinputmechanism
-- for a stream, so that unprocessed data can be garbage-collected.
closePostDataSource :: GlobalStreamId -> ReaderT SessionData IO ()
closePostDataSource stream_id = do
    stream2postinputmechanism <- view stream2PostInputMechanism

    pim_maybe <- liftIO $ H.lookup stream2postinputmechanism stream_id

    case pim_maybe of

        Just (PostInputMechanism (chan, _))  ->
            liftIO $ do
                putMVar chan Nothing
                -- Not sure if this will work
                H.delete  stream2postinputmechanism stream_id

        Nothing ->
            -- Assume this is ok
            return ()


streamWorkerSendData :: Int -> B.ByteString -> ReaderT SessionData IO Bool
streamWorkerSendData stream_id bytes = do
    s2pim <- view stream2PostInputMechanism
    pim_maybe <- liftIO $ H.lookup s2pim stream_id

    case pim_maybe of

        Just pim  -> do
            sendBytesToPim pim bytes
            return True

        Nothing ->
            -- There is no input mechanism
            return False


sendBytesToPim :: PostInputMechanism -> B.ByteString -> ReaderT SessionData IO ()
sendBytesToPim (PostInputMechanism (chan, _)) bytes =
    liftIO $ putMVar chan (Just bytes)


postDataSourceFromMechanism :: PostInputMechanism -> InputDataStream
postDataSourceFromMechanism (PostInputMechanism (_, source)) = source


isSettingsAck :: NH2.FrameHeader -> Bool
isSettingsAck (NH2.FrameHeader _ flags _) =
    NH2.testAck flags


isLengthZero :: NH2.FrameHeader -> Bool
isLengthZero (NH2.FrameHeader l _ _ ) = l == 0

frameLength :: NH2.FrameHeader -> Int
frameLength (NH2.FrameHeader l _ _ ) = l

isStreamZero :: NH2.FrameHeader -> Bool
isStreamZero (NH2.FrameHeader _  _ s) = s == 0


isStreamCancelled :: GlobalStreamId  -> WorkerMonad Bool
isStreamCancelled stream_id = do
    cancelled_streams_mvar <- view streamsCancelled_WTE
    cancelled_streams <- liftIO $ readMVar cancelled_streams_mvar
    return $ NS.member stream_id cancelled_streams


sendPrimitive500Error :: IO TupledPrincipalStream
sendPrimitive500Error =
  return (
        [
            (":status", "500")
        ],
        [],
        do
            yield "Internal server error\n"
            -- No footers
            return []
    )


-- | Invokes the Coherent worker, and interacts with the rest of
--   the session and the Framer so that data is sent and received by
--   the peer following the protocol.
workerThread :: Request -> AwareWorker -> WorkerMonad ()
workerThread req aware_worker =
    ignoreCancels $  do
        headers_output <- view headersOutput_WTE
        stream_id      <- view streamId_WTE
        --session_settings <- view sessionSettings_WTE
        --next_push_stream_mvar <-  view nextPushStream_WTE

        -- If the request get rejected right away, we can just send
        -- a 500 error in this very stream, without making any fuss.
        -- (headers, _, data_and_conclussion)
        --
        -- TODO: Can we add debug information in a header here?
        principal_stream <-
            liftIO $ {-# SCC wTer1  #-} E.catch
                (
                    aware_worker  req
                )
                (
                    const $ tupledPrincipalStreamToPrincipalStream <$> sendPrimitive500Error
                    :: HTTP500PrecursorException -> IO PrincipalStream
                )

        -- Pieces of the header
        let
           effects              = principal_stream ^. effect_PS
           interrupt_maybe      = effects          ^. interrupt_Ef


        -- Exceptions can bubble in the points below. If so, send proper resets...
        case interrupt_maybe of

            Nothing -> do
                {-# SCC nHS #-} normallyHandleStream principal_stream

            Just (InterruptConnectionAfter_IEf) -> do
                normallyHandleStream principal_stream
                liftIO . writeChan headers_output $ GoAway_HM (stream_id, effects)

            Just (InterruptConnectionNow_IEf) -> do
                -- Not one hundred-percent sure of this being correct, but we don't want
                -- to acknowledge reception of this stream then
                let use_stream_id = stream_id - 1
                liftIO . writeChan headers_output $ GoAway_HM (use_stream_id, effects)
  where
    ignoreCancels = CMC.handle ((\_ -> return () ):: StreamCancelledException -> WorkerMonad ())


normallyHandleStream :: PrincipalStream -> WorkerMonad ()
normallyHandleStream principal_stream = do
    headers_output <- view headersOutput_WTE
    stream_id      <- view streamId_WTE
    session_settings <- view sessionSettings_WTE
    next_push_stream_mvar <-  view nextPushStream_WTE
    reset_button <- view resetStreamButton_WTE

    -- Pieces of the header
    let
       headers              = He.removeConnectionHeaders $ principal_stream ^. headers_PS
       data_and_conclusion  = principal_stream ^. dataAndConclusion_PS
       effects              = principal_stream ^. effect_PS
       pushed_streams       = principal_stream ^. pushedStreams_PS

       can_push_ioref       = session_settings ^. pushEnabled_SeS

    can_push <- liftIO . DIO.readIORef $ can_push_ioref
      -- This gets executed in normal conditions, when no interruption is required.

    -- There are several possible moments where the PUSH_PROMISEs can be sent,
    -- but a default safe one is before sending the response HEADERS, so that
    -- LINK headers in the response come after any potential promises.
    data_promises <- if can_push then do
        forM pushed_streams $ \ pushed_stream_comp -> do
            -- Initialize pushed stream
            pushed_stream <- liftIO pushed_stream_comp
            -- Send the request headers properly wrapped in a "Push Promise", do
            -- it before sending the actual response headers of this stream
            let
                request_headers            = pushed_stream ^. requestHeaders_Psh
                -- We do not expect connection headers in pushed streams, since they
                -- are HTTP/2-specific.
                response_headers           = pushed_stream ^. responseHeaders_Psh
                pushed_data_and_conclusion = pushed_stream ^. dataAndConclusion_Psh

            child_stream_id <- liftIO $ modifyMVar next_push_stream_mvar
                                     $ (\ x -> return (x+2,x) )

            liftIO . writeChan headers_output . PushPromise_HM $
                (stream_id, child_stream_id, request_headers, effects)
            return (child_stream_id, response_headers, pushed_data_and_conclusion, effects)
      else
        return []

    -- Now I send the headers, if that's possible at all
    data_output <- view streamBytesSink_WTE
    headers `seq` ( liftIO $ writeChan headers_output $ NormalResponse_HM (stream_id, headers, effects, data_output) )

    -- At this moment I should ask if the stream hasn't been cancelled by the browser before
    -- commiting to the work of sending addtitional data... this is important for pushed
    -- streams
    is_stream_cancelled <- isStreamCancelled stream_id
    unless  is_stream_cancelled $ do
        -- I have a beautiful source that I can de-construct...
        -- TODO: Optionally pulling data out from a Conduit ....
        -- liftIO ( data_and_conclussion $$ (_sendDataOfStream stream_id) )
        --
        -- This threadlet should block here waiting for the headers to finish going
        -- NOTE: Exceptions generated here inheriting from HTTP500PrecursorException
        -- are let to bubble and managed in this thread fork point...
        _ <- liftIO . ReT.runResourceT $ do
            resource_key <- ReT.register reset_button
            _ <- runConduit $
               (data_and_conclusion)
               `fuseBothMaybe`
               (sendDataOfStream stream_id data_output)
            ReT.unprotect resource_key

        -- BIG TODO: Send the footers ... likely stream conclusion semantics
        -- will need to be changed.

        -- AFTER sending the data of the main stream, start sending the data of the
        -- pushed streams...

        -- Now, time to fork threads for the pusher streams
        -- we send those pushers even if the main stream is cancelled...
        forM_ data_promises
            $ \ (child_stream_id, response_headers, pushed_data_and_conclusion, _effects) -> do
                  environment <- ask
                  let
                      action = pusherThread
                                    child_stream_id
                                    response_headers
                                    pushed_data_and_conclusion
                                    effects
                  -- And let the action run in its own thread
                  liftIO . forkIOExc "s2f8" $ runReaderT action environment

        return ()



-- Takes care of pushed data, which  is sent through pipes to
-- the output thread here in this session.
pusherThread :: GlobalStreamId -> Headers -> DataAndConclusion -> Effect  -> WorkerMonad ()
pusherThread child_stream_id response_headers pushed_data_and_conclusion effects =
  do
    headers_output <- view headersOutput_WTE
    -- session_settings <- view sessionSettings_WTE
    pushed_reset_button <- view childResetStreamButton_WTE

    -- TODO: Handle exceptions here: what happens if the coherent worker
    --       throws an exception signaling that the request is ill-formed
    --       and should be dropped? That could happen in a couple of occassions,
    --       but really most cases should be handled here in this file...
    -- (headers, _, data_and_conclussion)
    pusher_data_output <- liftIO $ newEmptyMVar

    -- Now I send the headers, if that's possible at all. These are classes as "Normal response"
    liftIO . writeChan headers_output
        $ NormalResponse_HM (child_stream_id,  response_headers, effects, pusher_data_output)

    -- At this moment I should ask if the stream hasn't been cancelled by the browser before
    -- commiting to the work of sending addtitional data... this is important for pushed
    -- streams
    is_stream_cancelled <- isStreamCancelled child_stream_id
    unless  is_stream_cancelled $ do
        _ <- liftIO . ReT.runResourceT $ do
            k <- ReT.register (pushed_reset_button child_stream_id)
            _ <- runConduit $
                 pushed_data_and_conclusion
                `fuseBothMaybe`
                sendDataOfStream child_stream_id pusher_data_output
            ReT.unprotect k

        return ()


--                                                       v-- comp. monad.
sendDataOfStream :: MonadIO m => GlobalStreamId -> MVar TT.OutputDataFeed  -> Sink B.ByteString m ()
sendDataOfStream _stream_id data_output  =
  do
    consumer
  where
    consumer  = do
        maybe_bytes <- await
        -- use_size_ioref  <- view (sessionSettings_WTE . frameSize_SeS)
        -- use_size <- liftIO $ DIO.readIORef use_size_ioref
        case maybe_bytes of

            Nothing -> do
                -- This is how we finish sending data
                liftIO $ putMVar data_output  ""

            Just bytes
                | lng <- B.length bytes, lng > 0   -> do

                    liftIO $ do
                        putMVar data_output bytes
                    consumer

                | otherwise -> do
                    -- Finish sending data, finish in general
                    liftIO $ putMVar data_output ""


-- Returns if the frame is the first in the stream
appendHeaderFragmentBlock :: GlobalStreamId -> B.ByteString -> ReaderT SessionData IO Bool
appendHeaderFragmentBlock global_stream_id bytes = do
    ht <- view stream2HeaderBlockFragment
    maybe_old_block <- liftIO $ H.lookup ht global_stream_id
    (new_block, new_stream) <- case maybe_old_block of

        Nothing -> do
            -- TODO: Make the commented message below more informative
            return $ (Bu.byteString bytes, True)

        Just something ->
            return $ (something `mappend` (Bu.byteString bytes), False)

    liftIO $ H.insert ht global_stream_id new_block
    return new_stream


getHeaderBytes :: GlobalStreamId -> ReaderT SessionData IO B.ByteString
getHeaderBytes global_stream_id = do
    ht <- view stream2HeaderBlockFragment
    Just bytes <- liftIO $ H.lookup ht global_stream_id
    return $ Bl.toStrict $ Bu.toLazyByteString bytes


isAboutHeaders :: InputFrame -> Maybe (GlobalStreamId, B.ByteString)
isAboutHeaders (NH2.Frame (NH2.FrameHeader _ _ stream_id) ( NH2.HeadersFrame _ block_fragment   ) )
    = Just (stream_id, block_fragment)
isAboutHeaders (NH2.Frame (NH2.FrameHeader _ _ stream_id) ( NH2.ContinuationFrame block_fragment) )
    = Just (stream_id, block_fragment)
isAboutHeaders _
    = Nothing


frameEndsHeaders  :: InputFrame -> Bool
frameEndsHeaders (NH2.Frame (NH2.FrameHeader _ flags _) _) = NH2.testEndHeader flags


streamIdFromFrame :: InputFrame -> GlobalStreamId
streamIdFromFrame (NH2.Frame (NH2.FrameHeader _ _ stream_id) _) = stream_id


-- TODO: Have different size for the headers..... just now going with a default size of 16 k...
-- TODO: Find a way to kill this thread....
headersOutputThread :: Chan HeaderOutputMessage  --  (GlobalStreamId, MVar HeadersSent, Headers, Effect)
                       -> MVar SessionOutputChannelAbstraction
                       -> ReaderT SessionData IO ()
headersOutputThread input_chan session_output_mvar = forever $ do

    frame_size_ioref <- view $ sessionSettings . frameSize_SeS
    use_chunk_length <- liftIO $ DIO.readIORef frame_size_ioref

    header_output_request <- {-# SCC input_chan  #-} liftIO $ readChan input_chan
    {-# SCC case_ #-} case header_output_request of
        NormalResponse_HM (stream_id, headers, effect, data_output)  -> do

            -- First encode the headers using the table
            encode_dyn_table_mvar <- view toEncodeHeaders

            encode_dyn_table <- liftIO $ takeMVar encode_dyn_table_mvar
            (new_dyn_table, data_to_send ) <- liftIO $ HP.encodeHeader HP.defaultEncodeStrategy encode_dyn_table headers
            liftIO $ putMVar encode_dyn_table_mvar new_dyn_table

            -- Now split the bytestring in chunks of the needed size....
            -- Note that the only way we can
            let
                bs_chunks = bytestringChunk use_chunk_length  data_to_send

            -- And send the chunks through while locking the output place....
            liftIO $ bs_chunks `deepseq` withMVar session_output_mvar $ \ session_output -> do
                    let
                        header_frames = headerFrames stream_id bs_chunks effect
                    sendOutputToFramer session_output
                        $ TT.HeadersTrain_StFB (stream_id, header_frames, effect, data_output)

        GoAway_HM (stream_id, _effect) -> do
           -- This is in charge of sending an interrupt message to the framer
           requestTermination stream_id NH2.NoError

        PushPromise_HM (parent_stream_id, child_stream_id, promise_headers, effect) -> do

            encode_dyn_table_mvar <- view toEncodeHeaders
            encode_dyn_table <- liftIO $ takeMVar encode_dyn_table_mvar

            (new_dyn_table, data_to_send ) <- liftIO $ HP.encodeHeader HP.defaultEncodeStrategy encode_dyn_table promise_headers
            liftIO $ putMVar encode_dyn_table_mvar new_dyn_table

            -- Now split the bytestring in chunks of the needed size....
            bs_chunks <- return $! bytestringChunk use_chunk_length data_to_send

            -- And send the chunks through while locking the output place....
            liftIO $ withMVar session_output_mvar $ \ session_output ->  do
                    let
                        pp_frames = pushPromiseFrames parent_stream_id child_stream_id bs_chunks effect
                    sendOutputToFramer session_output $ TT.PriorityTrain_StFB pp_frames

  where

    chunksToSequence ::
          ( NH2.FrameFlags -> B.ByteString -> TT.OutputFrame)
       -> ( NH2.FrameFlags -> B.ByteString -> TT.OutputFrame)
       -> [B.ByteString]
       -> Bool
       -> [TT.OutputFrame]
    chunksToSequence
        transform_first
        _transform_middle
        (last_chunk:[])
        True    -- If it is first, and last
        =
        [transform_first (NH2.setEndHeader NH2.defaultFlags) last_chunk]
    chunksToSequence
        _transform_first
        transform_middle
        (last_chunk:[])
        False    -- It is not first, but last
        =
        [transform_middle (NH2.setEndHeader NH2.defaultFlags) last_chunk]
    chunksToSequence
        transform_first
        transform_middle
        (chunk:rest)
        True    -- If it is first, but not last
        =
        (transform_first NH2.defaultFlags chunk):(chunksToSequence transform_first transform_middle rest False)
    chunksToSequence
        transform_first
        transform_middle
        (chunk:rest)
        False    -- It is not first, and not last
        =
        (transform_middle NH2.defaultFlags chunk):(chunksToSequence transform_first transform_middle rest False)
    chunksToSequence _ _ [] _ = error "ChunkingEmptySetOfHeaders!!"

    headerFrames :: GlobalStreamId -> [B.ByteString] -> Effect -> [TT.OutputFrame]
    headerFrames stream_id chunks effect =
        chunksToSequence
            ( \ flags chunk -> (
                  NH2.EncodeInfo {
                      NH2.encodeFlags    = flags
                     ,NH2.encodeStreamId = stream_id
                     ,NH2.encodePadding  = Nothing },
                  NH2.HeadersFrame Nothing chunk,
                  effect
                  )
            )
            ( \ flags chunk -> (
                  NH2.EncodeInfo {
                      NH2.encodeFlags    = flags
                     ,NH2.encodeStreamId = stream_id
                     ,NH2.encodePadding  = Nothing },
                  NH2.ContinuationFrame chunk,
                  effect
                  )
            )
            chunks
            True

    pushPromiseFrames  :: GlobalStreamId -> GlobalStreamId -> [B.ByteString] -> Effect -> [TT.OutputFrame]
    pushPromiseFrames parent_stream_id child_stream_id chunks effect =
        chunksToSequence
            ( \ flags chunk -> (
                  NH2.EncodeInfo {
                      NH2.encodeFlags    = flags
                     ,NH2.encodeStreamId = parent_stream_id
                     ,NH2.encodePadding  = Nothing },
                  NH2.PushPromiseFrame child_stream_id chunk,
                  effect
                  )
            )
            ( \ flags chunk -> (
                  NH2.EncodeInfo {
                      NH2.encodeFlags    = flags
                     ,NH2.encodeStreamId = parent_stream_id
                     ,NH2.encodePadding  = Nothing },
                  NH2.ContinuationFrame chunk,
                  effect
                  )
            )
            chunks
            True


bytestringChunk :: Int -> B.ByteString -> [B.ByteString]
bytestringChunk len s | (B.length s) < len = [ s ]
bytestringChunk len s = h:(bytestringChunk len xs)
  where
    (h, xs) = B.splitAt len s


-- This function works for HTTP/2 client sessions only...
sessionPollThread :: SessionData -> Chan HeaderOutputMessage -> IO ()
sessionPollThread  session_data headers_output = do
    let
        pending_requests_mvar  = session_data ^. (simpleClient . pendingRequests_ClS)
        client_next_stream_mvar  = session_data ^. (simpleClient . nextStream_ClS)
        response2waiter       = session_data ^. (simpleClient . response2Waiter_ClS)
        for_worker_thread  = session_data ^. forWorkerThread
        session_input  = session_data ^. sessionInput

    ((headers, input_data_stream), response_mvar) <- takeMVar pending_requests_mvar

    new_stream_id <- modifyMVar client_next_stream_mvar (\ n -> return (n + 1, n) )
    let
        worker_environment =
            set
               streamId_WTE
               new_stream_id
               for_worker_thread
        effects = defaultEffects

    -- Store the response place
    new_stream_id `seq` H.insert response2waiter new_stream_id response_mvar


    -- NON-tested code
    output_mvar <- newEmptyMVar

    -- Start by sending the headers of the request
    liftIO $ writeChan headers_output $ NormalResponse_HM (new_stream_id, headers, effects, output_mvar)

    _ <- liftIO . forkIOExc "s2f6" $ do
        either_e0 <- E.try $ runReaderT
            (clientWorkerThread new_stream_id output_mvar input_data_stream )
            worker_environment
        case either_e0 :: Either HTTP500PrecursorException () of
            Left _ -> writeChan session_input $ InternalAbortStream_SIC new_stream_id
            Right _ -> return ()

     -- E.catch
     --        (runReaderT
     --            (clientWorkerThread new_stream_id effects headers_sent input_data_stream )
     --            worker_environment
     --        )
     --        (
     --            (   \ _ ->  do
     --                -- Actions to take when the thread breaks....
     --                writeChan session_input InternalAbort_SIC
     --            )
     --            :: HTTP500PrecursorException -> IO ()
     --        )

    sessionPollThread session_data headers_output


clientWorkerThread :: GlobalStreamId  -> MVar TT.OutputDataFeed -> InputDataStream -> WorkerMonad ()
clientWorkerThread stream_id  output_mvar input_data_stream = do
    -- And now work on sending the data, if any...
    _ <- liftIO . ReT.runResourceT . runConduit $
        input_data_stream
        `fuseBothMaybe`
        sendDataOfStream stream_id output_mvar
    return ()


clientSideTerminate  :: ClientState -> ConnectionCloseReason  -> IO ()
clientSideTerminate client_state reason = do
    let
        terminateOnQueue :: ConnectionCloseReason -> MVar (Message, MVar RequestResult) -> IO ()
        terminateOnQueue reason' q = do
            w <- tryTakeMVar q
            case w of
                Just (_, m) -> do
                    _ <- tryPutMVar m (Left reason')
                    return ()

                Nothing ->
                    return ()


        terminate :: ConnectionCloseReason -> IO ()
        terminate reason' = do
            terminateOnQueue reason' (client_state ^. pendingRequests_ClS)

            -- Kill any waiters in the map
            H.mapM_
                (\(_k,v) -> tryPutMVar v (Left reason))
                (client_state ^. response2Waiter_ClS)

        client_is_closed_mvar = client_state ^. clientIsClosed_ClS

    -- Let's also mark the session as closed from the client side, so that
    -- any further requests end with the correct exception
    modifyMVar_ client_is_closed_mvar $ \ client_is_closed -> do
            unless client_is_closed $
                terminate reason
            return True