second-transfer-0.6.0.0: tests/tests-hs-src/SecondTransfer/Test/DecoySession.hs
{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}
module SecondTransfer.Test.DecoySession where
import Control.Concurrent
import Control.Concurrent.MVar
import Control.Concurrent.Chan
import Control.Exception
import Control.Lens (view, (^.))
import qualified Control.Lens as L
import qualified Network.HTTP2 as NH2
import qualified Network.HPACK as HP
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
import SecondTransfer.Http2
import SecondTransfer.Types
import SecondTransfer.MainLoop.Internal
data DecoySession = DecoySession {
_inputDataChannel :: Chan B.ByteString
,_outputDataChannel :: Chan LB.ByteString
,_sessionThread :: ThreadId
,_remainingOutputBit:: MVar B.ByteString
,_nh2Settings :: NH2.Settings
,_sessionThrowed :: MVar Bool
,_waiting :: MVar ThreadId
,_encodeHeadersHere :: MVar HP.DynamicTable
,_decodeHeadersHere :: MVar HP.DynamicTable
}
L.makeLenses ''DecoySession
-- Supposed to be an HTTP/2 attendant
createDecoySession :: Attendant -> IO DecoySession
createDecoySession attendant = do
input_data_channel <- newChan
output_data_channel <- newChan
let
push_action :: PushAction
push_action = writeChan output_data_channel
pull_action :: PullAction
pull_action = readChan input_data_channel
close_action :: CloseAction
best_effort_pull_action _ = error "NotImplemented"
close_action = return ()
attendant_callbacks = AttendantCallbacks {
_pushAction_AtC = push_action,
_pullAction_AtC = pull_action,
_closeAction_AtC = close_action,
_bestEffortPullAction_AtC = best_effort_pull_action
}
dtable_for_encoding <- HP.newDynamicTableForEncoding 4096
dtable_for_encoding_mvar <- newMVar dtable_for_encoding
dtable_for_decoding <- HP.newDynamicTableForDecoding 4096
dtable_for_decoding_mvar <- newMVar dtable_for_decoding
waiting_mvar <- newEmptyMVar
thread_id <- forkIO $ catch
(do
attendant push_action pull_action close_action
)
((\ e -> do
putStrLn $ "Exception: " ++ (show e)
maybe_waiting <- tryTakeMVar waiting_mvar
case maybe_waiting of
Just thread_id -> throwTo thread_id e
Nothing -> return ()
):: SomeException -> IO ())
remaining_output_bit <- newMVar ""
let session = DecoySession {
_inputDataChannel = input_data_channel,
_outputDataChannel = output_data_channel,
_sessionThread = thread_id,
_remainingOutputBit= remaining_output_bit,
_nh2Settings = NH2.defaultSettings,
_waiting = waiting_mvar,
_encodeHeadersHere = dtable_for_encoding_mvar,
_decodeHeadersHere = dtable_for_decoding_mvar
}
return session
-- Tell when we are done with a decision
sessionIsUndone :: DecoySession -> IO Bool
sessionIsUndone session = error "NotImplemented"
-- Send a frame to a session
sendFrameToSession :: DecoySession -> OutputFrame -> IO ()
sendFrameToSession session (encode_info, frame_payload) = do
let
bs_list = NH2.encodeFrameChunks encode_info frame_payload
input_data_channel = session ^. inputDataChannel
writeChan2Chan input_data_channel bs_list
-- Read a frame from a session... if possible. It will block
-- until the frame comes out, but should fail if there is
-- an exception and/or the session is closed.
recvFrameFromSession :: DecoySession -> IO (Maybe NH2.Frame)
recvFrameFromSession decoy_session = do
let
output_data_channel = decoy_session ^. outputDataChannel
remaining_output_bit_mvar = decoy_session ^. remainingOutputBit
pull_action = fmap LB.toStrict $ readChan output_data_channel
settings = decoy_session ^. nh2Settings
waiting_for_read = decoy_session ^. waiting
my_thread_id <- myThreadId
putMVar waiting_for_read my_thread_id
remaining_output_bit <- takeMVar remaining_output_bit_mvar
(packet, rest) <- readNextChunkAndContinue http2FrameLength remaining_output_bit pull_action
putMVar remaining_output_bit_mvar rest
takeMVar waiting_for_read
let
error_or_frame = NH2.decodeFrame settings packet
case error_or_frame of
Left _ -> return Nothing
Right frame -> return $ Just frame
-- Encode headers to send to the session
-- TODO: There is an important bug here... we are using the default encoding
-- strategy everywhere
encodeHeadersForSession :: DecoySession -> Headers -> IO B.ByteString
encodeHeadersForSession decoy_session headers =
do
let
mv = decoy_session ^. encodeHeadersHere
dtable <- takeMVar mv
(dtable', bs ) <- HP.encodeHeader HP.defaultEncodeStrategy dtable headers
putMVar mv dtable'
return bs
-- Decode headers to receive them from the session
decodeHeadersForSession :: DecoySession -> B.ByteString -> IO Headers
decodeHeadersForSession decoy_session bs =
do
let
mv = decoy_session ^. decodeHeadersHere
dtable <- takeMVar mv
(dtable', headers) <- HP.decodeHeader dtable bs
putMVar mv dtable'
return headers
-- Send raw data to a session
sendRawDataToSession :: DecoySession -> B.ByteString -> IO ()
sendRawDataToSession decoy_session data_to_send = do
let
input_data_channel = decoy_session ^. inputDataChannel
waiting_for_write = decoy_session ^. waiting
thread_id <- myThreadId
putMVar waiting_for_write thread_id
writeChan input_data_channel data_to_send
takeMVar waiting_for_write
return ()
-- Send a headers frame to the session that ends the stream...
performRequestSimple :: DecoySession -> Int -> Headers -> IO ()
performRequestSimple decoy_session stream_id headers = do
headers_data <- encodeHeadersForSession decoy_session headers
let
frame = NH2.HeadersFrame
Nothing -- No priority
headers_data
frame_data_ei = NH2.EncodeInfo {
NH2.encodeFlags = NH2.setEndStream . NH2.setEndHeader $ NH2.defaultFlags,
NH2.encodeStreamId = stream_id,
NH2.encodePadding = Nothing
}
sendFrameToSession decoy_session (frame_data_ei,frame)
-- Read raw data from a session. Normally blocks until data be available.
-- It returns Nothing when the session is to be considered finished.
recvRawDataFromSession :: DecoySession -> IO (Maybe B.ByteString)
recvRawDataFromSession decoy_session = error "NotImplemented"