second-transfer 0.6.0.0 → 0.6.1.0
raw patch · 13 files changed
+251/−91 lines, 13 files
Files
- cbits/tlsinc.c +57/−18
- changelog.md +10/−0
- hs-src/SecondTransfer.hs +11/−6
- hs-src/SecondTransfer/Http1/Parse.hs +0/−2
- hs-src/SecondTransfer/Http1/Session.hs +77/−14
- hs-src/SecondTransfer/Http2/Framer.hs +2/−2
- hs-src/SecondTransfer/Http2/Session.hs +8/−6
- hs-src/SecondTransfer/MainLoop/CoherentWorker.hs +31/−15
- hs-src/SecondTransfer/MainLoop/OpenSSL_TLS.hs +25/−21
- hs-src/SecondTransfer/MainLoop/Protocol.hs +11/−0
- hs-src/SecondTransfer/MainLoop/PushPullType.hs +14/−5
- hs-src/SecondTransfer/Types.hs +2/−0
- second-transfer.cabal +3/−2
cbits/tlsinc.c view
@@ -63,7 +63,10 @@ void dispose_wired_session(wired_session_t* ws); static int thread_setup(void); +// Preprocessor flag: when 1, we will trace ALPN info+#define TRACE_ALPN_NEGOTIATION 0 + //////////////////////////////////////////////////////////////////////// @@ -319,24 +322,32 @@ connection_t* conn = (connection_t*) arg; static char output[64]; - char* incursor = (char*) in; - while (incursor < (char*)in + inlen )- {- // Got a protocol.... can I satisfy it?- char sublen = *incursor;- //printf("offered prot %.*s \n", sublen, incursor+1);+#if TRACE_ALPN_NEGOTIATION+ printf("protocol check starts, offered %d bytes for protocols ^^^^^^^^^^^^^ \n", inlen);+ int first_run = 1;+#endif+ char* stored_cursor = conn->protocol_list; - char* stored_cursor = conn->protocol_list;+ while( stored_cursor < conn->protocol_list + conn->protocol_list_length)+ {+ char* incursor = (char*) in; int sto_protocol = 0;+ char sublen2 = *stored_cursor; - while( stored_cursor < conn->protocol_list + conn->protocol_list_length)+ while (incursor < (char*)in + inlen ) {- char sublen2 = *stored_cursor;+ // Got a protocol.... can I satisfy it?+ char sublen = *incursor; +#if TRACE_ALPN_NEGOTIATION+ if (first_run)+ printf("offered prot %.*s \n", sublen, incursor+1);+#endif+ if (sublen != sublen2) {-+ // No match, just continue } else { int cmpresult = strncmp( incursor + 1, stored_cursor + 1, sublen); if (cmpresult == 0)@@ -344,19 +355,29 @@ // They are equal, choose this one... strncpy( output, stored_cursor+1, sublen); *outlen = sublen;- *out = output;--+ *out = (unsigned char*)output;+#if TRACE_ALPN_NEGOTIATION+ printf("protocol check ends by match vvvvvvvvvvv \n");+#endif return SSL_TLSEXT_ERR_OK; } }- sto_protocol += 1;- stored_cursor += (1+sublen2);++ incursor += (1+sublen); } - incursor += (1+sublen);+#if TRACE_ALPN_NEGOTIATION+ first_run = 0;+#endif++ sto_protocol += 1;+ stored_cursor += (1+sublen2);+ } +#if TRACE_ALPN_NEGOTIATION+ printf("protocol check ends without match vvvvvvvvvvv \n");+#endif // I think this is what should be returned return -1; }@@ -487,7 +508,10 @@ // Give the impression that we are using SNI SSL_CTX_set_tlsext_servername_callback(c->sslContext, ssl_servername_cb); + // Disable the session cache+ SSL_CTX_set_session_cache_mode(c->sslContext, SSL_SESS_CACHE_OFF); + // The only cipher supported by HTTP/2 ... sort of. result = SSL_CTX_set_cipher_list(c->sslContext, "ECDHE-RSA-AES128-GCM-SHA256" // -- ibidem@@ -811,16 +835,31 @@ { if (ws == 0) return ;+ if ( ws-> sslHandle ) {- SSL_shutdown( ws->sslHandle );- ws -> sslHandle = 0;+ int rr = SSL_shutdown( ws->sslHandle );+ // printf("rr=%d\n", rr);+ int err = SSL_get_error(ws->sslHandle, rr);+ if ( err < 0)+ {+ if ( err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE )+ {+ printf("error when closing socket / wait-for-data\n");+ } else if (err != 0 ){+ printf("cause openssl error code %d \n",err);+ }+ } }++ SSL_free(ws->sslHandle);+ ws -> sslHandle = 0; if (ws->socket) { close(ws->socket); ws -> socket = 0; }+ free(ws); }
changelog.md view
@@ -1,9 +1,19 @@+- 0.6.1.0 :+ * Improved documentation.+ * Improved ALPN negotiation handling.+ * Messages with a body are now handled by the HTTP/1.1 speaking part of this+ library.+ - 0.6.0.0 : * Changed the interface to provide more information to and from workers * Implemented HTTP/2 push * Upgraded dependencies to 1.0. for http2 package * Extended channel interface in such a way that latency is reduced, as a result, the test suite is completely broken. Please wait for 0.6.0.1 release.++- 0.5.5.1 :+ * Restricted HTTP2 package version.+ * Fixes #2 - 0.5.5.0 : * Made HeaderEditor a Monoid instance
hs-src/SecondTransfer.hs view
@@ -6,6 +6,9 @@ Stability : experimental Portability : POSIX +SecondTransfer is a HTTP/1.1 and HTTP/2 server session library, with an emphasis towards+experimentation (so far).+ This library implements enough of the HTTP/2 to build compliant HTTP/2 servers. It also implements enough of HTTP/1.1 so you can actually use it to build polyglot web-servers.@@ -15,9 +18,6 @@ This library just takes care of making sense of sent and received frames. -You can find more detailed information about this library at the page-<https://www.httptwo.com/second-transfer/>.- The library * Is concurrent, meaning that you can use amazing Haskell lightweight threads to@@ -63,9 +63,11 @@ -- if you wish. -- If you do multiple yields, no data will be left buffered between them, -- so that you can for example implement a chat client in a single HTTP/2 stream.- -- Pity browsers hardly support that.+ -- Not that browsers support that. yield "Hello world!"- -- No footers+ -- The HTTP/2 protocol supports sending headers *after* stream data. So, you usually+ -- return an empty list to signal that you don't want any of those headers. + -- In these docs, the post headers are often called "footers". return [] @@ -107,7 +109,10 @@ `AwareWorker` is the type of the basic callback function that you need to implement, but most times you can do with a simplified version called `CoherentWorker`. The function-`coherentToAwareWorker` does the conversion.+`coherentToAwareWorker` does the conversion. The difference between the two callbacks is +the level of information that you manage. With AwareWorker, you get a record on the request+with all sort of details, things like the session id and the + The callback is used to handle all requests to the server on a given negotiated ALPN protocol. If you need routing functionality (and you most certainly will need it), you need to build that functionality inside the callback.
hs-src/SecondTransfer/Http1/Parse.hs view
@@ -48,8 +48,6 @@ import SecondTransfer.MainLoop.CoherentWorker (Headers) import SecondTransfer.Utils (subByteString) -import Prelude- data IncrementalHttp1Parser = IncrementalHttp1Parser { _fullText :: Bu.Builder
hs-src/SecondTransfer/Http1/Session.hs view
@@ -8,6 +8,7 @@ import Control.Lens import Control.Exception (catch) import Control.Concurrent (forkIO)+import Control.Monad.IO.Class (liftIO) import qualified Data.ByteString as B -- import qualified Data.ByteString.Lazy as LB@@ -15,10 +16,12 @@ -- import qualified Data.ByteString.Builder as Bu import Data.Conduit import Data.Conduit.List (consume)+import Data.IORef -- import Data.Monoid (mconcat, mappend) import SecondTransfer.MainLoop.CoherentWorker import SecondTransfer.MainLoop.PushPullType+import SecondTransfer.MainLoop.Protocol import SecondTransfer.Sessions.Internal (SessionsContext, acquireNewSessionTag, sessionsConfig) -- Logging utilities@@ -41,7 +44,7 @@ = do new_session_tag <- acquireNewSessionTag sessions_context- infoM "Session.Session_HTTP11" $ "Starting new session with tag: " ++(show new_session_tag)+ -- infoM "Session.Session_HTTP11" $ "Starting new session with tag: " ++(show new_session_tag) forkIO $ go new_session_tag (Just "") 1 return () where@@ -52,13 +55,15 @@ go :: Int -> Maybe B.ByteString -> Int -> IO () go session_tag (Just leftovers) reuse_no = do- infoM "Session.Session_HTTP11" $ "(Re)Using session with tag: " ++ (show session_tag)+ -- infoM "Session.Session_HTTP11" $ "(Re)Using session with tag: " ++ (show session_tag) maybe_leftovers <- add_data newIncrementalHttp1Parser leftovers session_tag reuse_no go session_tag maybe_leftovers (reuse_no + 1) go _ Nothing _ = return () + -- This function will invoke itself as long as data is coming for the currently-being-parsed+ -- request/response. add_data :: IncrementalHttp1Parser -> B.ByteString -> Int -> Int -> IO (Maybe B.ByteString) add_data parser bytes session_tag reuse_no = do let@@ -78,7 +83,7 @@ ( (\ _e -> do -- This is a pretty harmless condition that happens -- often when the remote peer closes the connection- debugM "Session.HTTP1" "Could not receive data"+ -- debugM "Session.HTTP1" "Could not receive data" close_action return Nothing ) :: IOProblem -> IO (Maybe B.ByteString) )@@ -98,9 +103,11 @@ _headers_RQ = modified_headers, _inputData_RQ = Nothing, _perception_RQ = Perception {- _startedTime_Pr = started_time,- _streamId_Pr = reuse_no,- _sessionId_Pr = session_tag+ _startedTime_Pr = started_time,+ _streamId_Pr = reuse_no,+ _sessionId_Pr = session_tag,+ _protocol_Pr = Http11_HPV,+ _anouncedProtocols_Pr = Nothing } } let@@ -117,18 +124,74 @@ return $ Just leftovers ) ((\ _e -> do- debugM "Session.HTTP1" "Session abandoned"+ -- debugM "Session.HTTP1" "Session abandoned" close_action return Nothing ) :: IOProblem -> IO (Maybe B.ByteString) ) - HeadersAndBody_H1PC _headers _stopcondition _recv_leftovers -> do- -- print "HeadersAndBody_H1PC"- -- Let's see if I can go through the basic movements first, then through- -- more complicated things.- -- TODO: Implement posts and other requests with bodies....- close_action- error "NotImplemented requests with bodies"+ HeadersAndBody_H1PC headers stopcondition recv_leftovers -> do+ let+ modified_headers = addExtraHeaders sessions_context headers+ started_time <- getTime Monotonic+ set_leftovers <- newIORef ""++ principal_stream <- coherent_worker Request {+ _headers_RQ = modified_headers,+ _inputData_RQ = Just $ counting_read recv_leftovers stopcondition set_leftovers,+ _perception_RQ = Perception {+ _startedTime_Pr = started_time,+ _streamId_Pr = reuse_no,+ _sessionId_Pr = session_tag,+ _protocol_Pr = Http11_HPV,+ _anouncedProtocols_Pr = Nothing+ }+ }+ let+ data_and_conclusion = principal_stream ^. dataAndConclusion_PS+ response_headers = principal_stream ^. headers_PS+ (_, fragments) <- runConduit $ fuseBoth data_and_conclusion consume+ channel_leftovers <- readIORef set_leftovers+ let+ response_text =+ serializeHTTPResponse response_headers fragments++ catch+ (do+ push_action response_text+ return $ Just channel_leftovers+ )+ ((\ _e -> do+ -- debugM "Session.HTTP1" "Session abandoned"+ close_action+ return Nothing+ ) :: IOProblem -> IO (Maybe B.ByteString) )++ counting_read :: B.ByteString -> BodyStopCondition -> IORef B.ByteString -> Source IO B.ByteString+ counting_read leftovers un@(UseBodyLength_BSC n) set_leftovers = do+ -- Can I continue?+ if n == 0 then+ do+ liftIO $ writeIORef set_leftovers leftovers+ return ()+ else+ do+ let+ lngh_leftovers = B.length leftovers+ if lngh_leftovers > 0 then+ if lngh_leftovers <= n then+ do+ yield leftovers+ counting_read "" (UseBodyLength_BSC (n - lngh_leftovers )) set_leftovers+ else+ do+ let+ (pass, new_leftovers) = B.splitAt n leftovers+ yield pass+ counting_read new_leftovers (UseBodyLength_BSC 0) set_leftovers+ else+ do+ more_text <- liftIO $ best_effort_pull_action True+ counting_read more_text un set_leftovers addExtraHeaders :: SessionsContext -> Headers -> Headers
hs-src/SecondTransfer/Http2/Framer.hs view
@@ -135,7 +135,7 @@ , _closeAction :: CloseAction -- Global id of the session, used for e.g. error reporting.- , _sessionIdAtFramer :: Int+ , _sessionIdAtFramer :: !Int -- Sessions context, used for thing like e.g. error reporting , _sessionsContext :: SessionsContext@@ -166,7 +166,7 @@ new_session_id <- modifyMVarMasked session_id_mvar $- \ session_id -> return (session_id+1, session_id)+ \ session_id -> return $ session_id `seq` (session_id + 1, session_id) (session_input, session_output) <- http2Session aware_worker
hs-src/SecondTransfer/Http2/Session.hs view
@@ -66,6 +66,7 @@ -- Imports from other parts of the program import SecondTransfer.MainLoop.CoherentWorker import SecondTransfer.MainLoop.Tokens+import SecondTransfer.MainLoop.Protocol import SecondTransfer.Sessions.Config import SecondTransfer.Sessions.Internal (sessionExceptionHandler, SessionsContext,@@ -356,8 +357,6 @@ --- more robust. sessionInputThread :: ReaderT SessionData IO () sessionInputThread = do- INSTRUMENTATION( debugM "HTTP2.Session" "Entering sessionInputThread" )- -- 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...@@ -518,7 +517,9 @@ perception = Perception { _startedTime_Pr = headers_arrived_time, _streamId_Pr = stream_id,- _sessionId_Pr = current_session_id+ _sessionId_Pr = current_session_id,+ _protocol_Pr = Http2_HPV,+ _anouncedProtocols_Pr = Nothing } request = Request { _headers_RQ = header_list_after,@@ -571,9 +572,10 @@ maybe_thread_id <- H.lookup stream2workerthread stream_id case maybe_thread_id of Nothing ->- -- This is actually more like an internal error, when this- -- happens, cancel the session- error "InterruptingUnexistentStream"+ -- This is can actually happen in some implementations: we are asked to+ -- cancel an stream we know nothing about. Log which stream it is+ logit $ "InterruptingUnexistentStream " `mappend` (pack . show $ stream_id)+ -- and don't get too crazy about it. Just thread_id -> do -- INSTRUMENTATION( infoM "HTTP2.Session" $ "Stream successfully interrupted" )
hs-src/SecondTransfer/MainLoop/CoherentWorker.hs view
@@ -27,6 +27,7 @@ , CoherentWorker , InputDataStream , TupledPrincipalStream+ , TupledRequest , FragmentDeliveryCallback , headers_RQ@@ -42,6 +43,8 @@ , startedTime_Pr , streamId_Pr , sessionId_Pr+ , anouncedProtocols_Pr+ , protocol_Pr , fragmentDeliveryCallback_Ef , priorityEffect_Ef @@ -54,12 +57,14 @@ import Control.Lens-import qualified Data.ByteString as B+import qualified Data.ByteString as B import Data.Conduit-import Data.Foldable (find)-import System.Clock (TimeSpec)+import Data.Foldable (find)+import System.Clock (TimeSpec) +import SecondTransfer.MainLoop.Protocol (HttpProtocolVersion) + -- | The name part of a header type HeaderName = B.ByteString @@ -84,15 +89,19 @@ -- | Data related to the request data Perception = Perception {- -- Monotonic time close to when the request was first seen in- -- the processing pipeline.- _startedTime_Pr :: TimeSpec,- -- The HTTP/2 stream id. Or the serial number of the request in an- -- HTTP/1.1 session.- _streamId_Pr :: Int,- -- You know better than to use this for normal web request- -- processing. But otherwise a number uniquely identifying the session. - _sessionId_Pr :: Int+ -- | The HTTP/2 stream id. Or the serial number of the request in an+ -- HTTP/1.1 session.+ _streamId_Pr :: Int,+ -- | A number uniquely identifying the session. This number is unique and+ -- the same for each TPC connection that a client opens using a given protocol.+ _sessionId_Pr :: Int,+ -- | Monotonic time close to when the request was first seen in+ -- the processing pipeline.+ _startedTime_Pr :: TimeSpec,+ -- | Which protocol is serving the request+ _protocol_Pr :: HttpProtocolVersion,+ -- | For new connections, probably a list of anounced protocols+ _anouncedProtocols_Pr :: Maybe [B.ByteString] } makeLenses ''Perception@@ -197,15 +206,22 @@ -- cancels the stream type AwareWorker = Request -> IO PrincipalStream --- | A CoherentWorker is a less fuzzy worker, but less aware.-type CoherentWorker = (Headers, Maybe InputDataStream) -> IO (Headers, PushedStreams, DataAndConclusion)+-- | A CoherentWorker is a simplified callback that you can implement to handle requests.+-- Then you can convert it to an AwareWorker with `tupledPrincipalStreamToPrincipalStream`.+type CoherentWorker = TupledRequest -> IO TupledPrincipalStream --- | Not exactly equivalent of the prinicipal stream+-- | A tuple representing the data alone that you usually need to give as a response, that+-- is, the headers in the response (including the HTTP/2 :status), any pushed streams,+-- a stream with the response data and the footers. type TupledPrincipalStream = (Headers, PushedStreams, DataAndConclusion) +-- | A tuple representing the data alone usually needed to create a response. That is,+-- the headers (including HTTP/2 :path, :authority, etc) and maybe an input data stream+-- for requests that include it, that is, POST and PUT. type TupledRequest = (Headers, Maybe InputDataStream) +-- | Convert between the two types of callback. tupledPrincipalStreamToPrincipalStream :: TupledPrincipalStream -> PrincipalStream tupledPrincipalStreamToPrincipalStream (headers, pushed_streams, data_and_conclusion) = PrincipalStream {
hs-src/SecondTransfer/MainLoop/OpenSSL_TLS.hs view
@@ -303,15 +303,13 @@ -- provideActions :: Wired_Ptr -> IO (LB.ByteString -> IO (), Int -> IO B.ByteString, IO ()) provideActions :: Wired_Ptr -> IO AttendantCallbacks provideActions wired_ptr = do- already_closed_mvar <- newMVar False+ can_write_mvar <- newMVar True+ can_read_mvar <- newMVar True let pushAction :: LB.ByteString -> IO ()- pushAction datum = do- already_closed <- readMVar already_closed_mvar- if already_closed+ pushAction datum = withMVar can_write_mvar $ \ can_write ->+ if can_write then- throwIO $ TLSLayerGenericProblem "Tried to send data on closed handle"- else BU.unsafeUseAsCStringLen (LB.toStrict datum) $ \ (pchar, len) -> do result <- sendData wired_ptr pchar (fromIntegral len) case result of@@ -319,14 +317,13 @@ return () | r == badHappened -> throwIO $ TLSLayerGenericProblem "Could not send data"+ else+ throwIO $ TLSLayerGenericProblem "CouldNotWriteData--SocketAlreadyClosed" pullAction :: Int -> IO B.ByteString- pullAction bytes_to_get = do- already_closed <- readMVar already_closed_mvar- if already_closed+ pullAction bytes_to_get = withMVar can_read_mvar $ \ can_read ->+ if can_read then- throwIO $ TLSLayerGenericProblem "Tried to receive on closed handle"- else allocaBytes bytes_to_get $ \ pcharbuffer -> alloca $ \ data_recvd_ptr -> do result <- recvData wired_ptr pcharbuffer (fromIntegral bytes_to_get) data_recvd_ptr@@ -336,14 +333,13 @@ throwIO $ TLSLayerGenericProblem "Could not receive data" B.packCStringLen (pcharbuffer, fromIntegral recvd_bytes)+ else+ throwIO $ TLSLayerGenericProblem "CouldNotReadData--SocketAlreadyClosed" bestEffortPullAction :: Bool -> IO B.ByteString- bestEffortPullAction can_wait = do- already_closed <- readMVar already_closed_mvar- if already_closed+ bestEffortPullAction can_wait = withMVar can_read_mvar $ \ can_read ->+ if can_read then- throwIO $ TLSLayerGenericProblem "Tried to receive on closed handle"- else allocaBytes useBufferSize $ \ pcharbuffer -> alloca $ \ data_recvd_ptr -> do result <- recvDataBestEffort@@ -358,14 +354,22 @@ throwIO $ TLSLayerGenericProblem "Could not receive data" B.packCStringLen (pcharbuffer, fromIntegral recvd_bytes)+ else+ throwIO $ TLSLayerGenericProblem "CouldNotReadData--SocketAlreadyClosed" + closeAction :: IO () -- Ensure that the socket and the struct are only closed once- closeAction = do- b <- readMVar already_closed_mvar- unless b $ do- modifyMVar_ already_closed_mvar (\ _ -> return True)- disposeWiredSession wired_ptr+ closeAction =+ modifyMVar_ can_write_mvar $ \ can_write ->+ modifyMVar can_read_mvar $ \ can_read ->+ if can_write && can_read+ then do+ disposeWiredSession wired_ptr+ return (False,False)+ else+ return (False,False)+ return AttendantCallbacks { _pushAction_AtC = pushAction, _pullAction_AtC = pullAction,
+ hs-src/SecondTransfer/MainLoop/Protocol.hs view
@@ -0,0 +1,11 @@+module SecondTransfer.MainLoop.Protocol (+ HttpProtocolVersion(..)+ ) where+++-- | The protocol version used. Here we distinguish only between+-- HTTP/1.1 and HTTP/2+data HttpProtocolVersion =+ Http11_HPV+ |Http2_HPV+
hs-src/SecondTransfer/MainLoop/PushPullType.hs view
@@ -47,20 +47,29 @@ -- is severed suddenly. type CloseAction = IO () -+-- | A set of functions describing how to do I/O in a session.+-- As usual, we provide lenses accessors. data AttendantCallbacks = AttendantCallbacks {+ -- | put some data in the channel _pushAction_AtC :: PushAction,+ -- | get exactly this much data from the channel. This function can+ -- be used by HTTP/2 since lengths are pretty well built inside the+ -- protocoll itself. _pullAction_AtC :: PullAction,+ -- | pull data from the channel, as much as the TCP stack wants to provide.+ -- we have no option but use this one when talking HTTP/1.1, where the best+ -- way to know the length is to scan until a Content-Length is found. _bestEffortPullAction_AtC :: BestEffortPullAction,+ -- | this is called when we wish to close the channel. _closeAction_AtC :: CloseAction } makeLenses ''AttendantCallbacks --- | A function which takes three arguments: the first one says--- how to send data (on a socket or similar transport), and the second one how--- to receive data on the transport. The third argument encapsulates--- the sequence of steps needed for a clean shutdown.+-- | This is an intermediate type. It represents what you obtain+-- by combining something that speaks the protocol and an AwareWorker.+-- In turn, you need to feed a bundle of callbacks implementing I/O+-- to finally start a server. -- -- You can implement one of these to let somebody else supply the -- push, pull and close callbacks. For example, 'tlsServeWithALPN' will
hs-src/SecondTransfer/Types.hs view
@@ -1,7 +1,9 @@ module SecondTransfer.Types( module SecondTransfer.MainLoop.PushPullType ,module SecondTransfer.MainLoop.CoherentWorker+ ,module SecondTransfer.MainLoop.Protocol ) where import SecondTransfer.MainLoop.PushPullType import SecondTransfer.MainLoop.CoherentWorker+import SecondTransfer.MainLoop.Protocol
second-transfer.cabal view
@@ -7,7 +7,7 @@ -- PVP summary: +-+------- breaking API changes -- | | +----- non-breaking API additions -- | | | +--- code changes with no API change-version : 0.6.0.0+version : 0.6.1.0 synopsis : Second Transfer HTTP/2 web server @@ -53,7 +53,7 @@ source-repository this type: git location: git@github.com:alcidesv/second-transfer.git- tag: 0.6.0.0+ tag: 0.6.1.0 library @@ -75,6 +75,7 @@ other-modules: SecondTransfer.MainLoop.CoherentWorker , SecondTransfer.MainLoop.PushPullType+ , SecondTransfer.MainLoop.Protocol , SecondTransfer.MainLoop.Tokens , SecondTransfer.MainLoop.Framer , SecondTransfer.MainLoop.Logging