packages feed

quic 0.1.7 → 0.1.8

raw patch · 17 files changed

+211/−70 lines, 17 files

Files

ChangeLog.md view
@@ -1,3 +1,8 @@+## 0.1.8++- Announcing MaxStreams properly.+- Terminating a connection if the peer violates flow controls.+ ## 0.1.7  - Using System.Timeout.timeout.
Network/QUIC/Connection/State.hs view
@@ -19,6 +19,7 @@   , addRxMaxData   , getRxMaxData   , getRxDataWindow+  , checkRxMaxData   , addTxBytes   , getTxBytes   , addRxBytes@@ -128,6 +129,18 @@  getRxDataWindow :: Connection -> IO Int getRxDataWindow Connection{..} = flowWindow <$> readIORef flowRx++----------------------------------------------------------------++checkRxMaxData :: Connection -> Int -> IO Bool+checkRxMaxData Connection{..} len = do+    received <- readIORef flowBytesRx+    maxData <- flowMaxData <$> readIORef flowRx+    if received + len < maxData then do+        modifyIORef' flowBytesRx (+ len)+        return True+      else+        return False  ---------------------------------------------------------------- 
Network/QUIC/Connection/Stream.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE BinaryLiterals #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} @@ -5,16 +6,20 @@     getMyStreamId   , waitMyNewStreamId   , waitMyNewUniStreamId-  , setMyMaxStreams-  , setMyUniMaxStreams-  , getPeerMaxStreams-  , setPeerMaxStreams+  , setTxMaxStreams+  , setTxUniMaxStreams+  , checkRxMaxStreams+  , updatePeerStreamId+  , checkStreamIdRoom   ) where  import UnliftIO.STM +import Network.QUIC.Connection.Misc import Network.QUIC.Connection.Types+import Network.QUIC.Connector import Network.QUIC.Imports+import Network.QUIC.Parameters import Network.QUIC.Types  getMyStreamId :: Connection -> IO Int@@ -31,27 +36,70 @@ get :: TVar Concurrency -> IO Int get tvar = atomically $ do     conc@Concurrency{..} <- readTVar tvar-    checkSTM (currentStream < maxStreams * 4 + streamType)+    let streamType = currentStream .&. 0b11+        StreamIdBase base = maxStreams+    checkSTM (currentStream < base * 4 + streamType)     let currentStream' = currentStream + 4     writeTVar tvar conc { currentStream = currentStream' }     return currentStream -setMyMaxStreams :: Connection -> Int -> IO ()-setMyMaxStreams Connection{..} = set myStreamId+-- From "Peer", but set it to "My".+-- So, using "Tx".+setTxMaxStreams :: Connection -> Int -> IO ()+setTxMaxStreams Connection{..} = set myStreamId -setMyUniMaxStreams :: Connection -> Int -> IO ()-setMyUniMaxStreams Connection{..} = set myUniStreamId+setTxUniMaxStreams :: Connection -> Int -> IO ()+setTxUniMaxStreams Connection{..} = set myUniStreamId  set :: TVar Concurrency -> Int -> IO ()-set tvar mx = atomically $ modifyTVar tvar $ \c -> c { maxStreams = mx }+set tvar mx = atomically $ modifyTVar tvar $ \c -> c { maxStreams = StreamIdBase mx } -setPeerMaxStreams :: Connection -> Int -> IO ()-setPeerMaxStreams Connection{..} n =-    atomicModifyIORef'' peerStreamId $ \c -> c { maxStreams = n }+updatePeerStreamId :: Connection -> StreamId -> IO ()+updatePeerStreamId conn sid = do+    when ((isClient conn && isServerInitiatedBidirectional sid)+       || (isServer conn && isClientInitiatedBidirectional sid)) $ do+        atomicModifyIORef'' (peerStreamId conn) check+    when ((isClient conn && isServerInitiatedUnidirectional sid)+       || (isServer conn && isClientInitiatedUnidirectional sid)) $ do+        atomicModifyIORef'' (peerUniStreamId conn) check+  where+    check conc@Concurrency{..}+      | currentStream < sid = conc { currentStream = sid }+      | otherwise           = conc -getPeerMaxStreams :: Connection -> IO Int-getPeerMaxStreams Connection{..} = atomicModifyIORef' peerStreamId inc+checkRxMaxStreams :: Connection -> StreamId -> IO Bool+checkRxMaxStreams conn@Connection{..} sid = do+    Concurrency{..} <- if isClient conn then readForClient else readForServer+    let StreamIdBase base = maxStreams+        ok = sid < base * 4 + streamType+    return ok   where-    inc c = (c { maxStreams = next}, next)-      where-        next = maxStreams c + 1+    streamType = sid .&. 0b11+    readForClient = case streamType of+      0 -> readTVarIO myStreamId+      1 -> readIORef  peerStreamId+      2 -> readTVarIO myUniStreamId+      3 -> readIORef  peerUniStreamId+      _ -> error "never reach"+    readForServer = case streamType of+      0 -> readIORef  peerStreamId+      1 -> readTVarIO myStreamId+      2 -> readIORef  peerUniStreamId+      3 -> readTVarIO myUniStreamId+      _ -> error "never reach"++checkStreamIdRoom :: Connection -> Direction -> IO (Maybe Int)+checkStreamIdRoom conn dir = do+    let ref | dir == Bidirectional = peerStreamId conn+            | otherwise            = peerUniStreamId conn+    atomicModifyIORef' ref check+  where+    check conc@Concurrency{..} =+        let StreamIdBase base = maxStreams+            initialStreams = initialMaxStreamsBidi $ getMyParameters conn+            cbase = currentStream !>>. 2+        in if (base - cbase < (initialStreams !>>. 3)) then+               let base' = base + initialStreams+               in (conc { maxStreams = StreamIdBase base' }, Just base')+           else+               (conc, Nothing)
Network/QUIC/Connection/StreamTable.hs view
@@ -32,12 +32,16 @@ addStream :: Connection -> StreamId -> IO Stream addStream conn@Connection{..} sid = do     strm <- newStream conn sid-    peerParams <- getPeerParameters conn-    let txMaxStreamData | isClient conn = clientInitial sid peerParams-                        | otherwise     = serverInitial sid peerParams-    setTxMaxStreamData strm txMaxStreamData-    let rxMaxStreamData = initialRxMaxStreamData conn sid-    setRxMaxStreamData strm rxMaxStreamData+    if isClient conn then do+         let clientParams = getMyParameters conn+         setRxMaxStreamData strm $ clientInitial sid clientParams+         serverParams <- getPeerParameters conn+         setTxMaxStreamData strm $ serverInitial sid serverParams+      else do+         let serverParams = getMyParameters conn+         setRxMaxStreamData strm $ serverInitial sid serverParams+         clientParams <- getPeerParameters conn+         setTxMaxStreamData strm $ clientInitial sid clientParams     atomicModifyIORef'' streamTable $ insertStream sid strm     return strm @@ -54,15 +58,16 @@  clientInitial :: StreamId -> Parameters -> Int clientInitial sid params-  | isClientInitiatedBidirectional  sid = initialMaxStreamDataBidiRemote params-  | isServerInitiatedBidirectional  sid = initialMaxStreamDataBidiLocal  params-  -- intentionally not using isClientInitiatedUnidirectional+  | isClientInitiatedBidirectional  sid = initialMaxStreamDataBidiLocal  params+  | isServerInitiatedBidirectional  sid = initialMaxStreamDataBidiRemote params+  -- intentionally not using isServerInitiatedUnidirectional   | otherwise                           = initialMaxStreamDataUni        params  serverInitial :: StreamId -> Parameters -> Int serverInitial sid params-  | isServerInitiatedBidirectional  sid = initialMaxStreamDataBidiRemote params-  | isClientInitiatedBidirectional  sid = initialMaxStreamDataBidiLocal  params+  | isServerInitiatedBidirectional  sid = initialMaxStreamDataBidiLocal  params+  | isClientInitiatedBidirectional  sid = initialMaxStreamDataBidiRemote params+  -- intentionally not using isClientInitiatedUnidirectional   | otherwise                           = initialMaxStreamDataUni        params  ----------------------------------------------------------------
Network/QUIC/Connection/Types.hs view
@@ -151,17 +151,19 @@  ---------------------------------------------------------------- +newtype StreamIdBase = StreamIdBase { fromStreamIdBase :: Int }+    deriving (Eq, Show)+ data Concurrency = Concurrency {-    currentStream :: Int-  , streamType    :: Int-  , maxStreams    :: Int+    currentStream :: StreamId+  , maxStreams    :: StreamIdBase   }  newConcurrency :: Role -> Direction -> Int -> Concurrency-newConcurrency rl dir n = Concurrency typ typ n+newConcurrency rl dir n = Concurrency ini $ StreamIdBase n  where    bidi = dir == Bidirectional-   typ | rl == Client = if bidi then 0 else 2+   ini | rl == Client = if bidi then 0 else 2        | otherwise    = if bidi then 1 else 3  ----------------------------------------------------------------@@ -206,15 +208,17 @@   -- State   , peerPacketNumber  :: IORef PacketNumber      -- for RTT1   , streamTable       :: IORef StreamTable-  , myStreamId        :: TVar Concurrency-  , myUniStreamId     :: TVar Concurrency-  , peerStreamId      :: IORef Concurrency+  , myStreamId        :: TVar Concurrency  -- C:0 S:1+  , myUniStreamId     :: TVar Concurrency  -- C:2 S:3+  , peerStreamId      :: IORef Concurrency -- C:1 S:0+  , peerUniStreamId   :: IORef Concurrency -- C:3 S:2   , flowTx            :: TVar Flow   , flowRx            :: IORef Flow+  , flowBytesRx       :: IORef Int   , migrationState    :: TVar MigrationState   , minIdleTimeout    :: IORef Microseconds-  , bytesTx           :: TVar Int-  , bytesRx           :: TVar Int+  , bytesTx           :: TVar Int -- TVar for anti amplification+  , bytesRx           :: TVar Int -- TVar for anti amplification   , addressValidated  :: TVar Bool   -- TLS   , pendingQ          :: Array   EncryptionLevel (TVar [ReceivedPacket])@@ -302,8 +306,10 @@         <*> newTVarIO (newConcurrency rl Bidirectional  0)         <*> newTVarIO (newConcurrency rl Unidirectional 0)         <*> newIORef  peerConcurrency+        <*> newIORef  peerUniConcurrency         <*> newTVarIO defaultFlow         <*> newIORef defaultFlow { flowMaxData = initialMaxData myparams }+        <*> newIORef 0         <*> newTVarIO NonMigration         <*> newIORef (milliToMicro $ maxIdleTimeout myparams)         <*> newTVarIO 0@@ -336,6 +342,7 @@     peer | isclient  = Server          | otherwise = Client     peerConcurrency = newConcurrency peer Bidirectional (initialMaxStreamsBidi myparams)+    peerUniConcurrency = newConcurrency peer Unidirectional (initialMaxStreamsUni myparams)  defaultTrafficSecrets :: (ClientTrafficSecret a, ServerTrafficSecret a) defaultTrafficSecrets = (ClientTrafficSecret "", ServerTrafficSecret "")
Network/QUIC/Crypto/Nite.hs view
@@ -22,6 +22,7 @@ import qualified Data.ByteString.Internal as BS import Foreign.ForeignPtr (withForeignPtr, newForeignPtr_) import Foreign.Marshal.Alloc (mallocBytes)+import Foreign.Marshal.Utils (copyBytes) import Foreign.Ptr (Ptr, plusPtr, nullPtr) import Foreign.Storable (peek, poke) import Network.TLS hiding (Version)@@ -118,7 +119,7 @@         let src0 = p0 `plusPtr` off0         let src1 = p1 `plusPtr` off1         let diff = len0 - len1-        BS.memcpy dst src0 diff+        copyBytes dst src0 diff         loop (dst `plusPtr` diff) (src0 `plusPtr` diff) src1 len1   where     loop :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> Int -> IO ()
Network/QUIC/Handshake.hs view
@@ -265,8 +265,8 @@         setTxMaxData conn $ initialMaxData params         setMinIdleTimeout conn $ milliToMicro $ maxIdleTimeout params         setMaxAckDaley (connLDCC conn) $ milliToMicro $ maxAckDelay params-        setMyMaxStreams conn $ initialMaxStreamsBidi params-        setMyUniMaxStreams conn $ initialMaxStreamsUni params+        setTxMaxStreams conn $ initialMaxStreamsBidi params+        setTxUniMaxStreams conn $ initialMaxStreamsUni params      serverVersionNegotiation Nothing = return ()     serverVersionNegotiation (Just peerVerInfo) = do
Network/QUIC/IO.hs view
@@ -16,12 +16,14 @@ -- | Creating a bidirectional stream. stream :: Connection -> IO Stream stream conn = do+    -- FLOW CONTROL: MAX_STREAMS: send: respecting peer's limit     sid <- waitMyNewStreamId conn     addStream conn sid  -- | Creating a unidirectional stream. unidirectionalStream :: Connection -> IO Stream unidirectionalStream conn = do+    -- FLOW CONTROL: MAX_STREAMS: send: respecting peer's limit     sid <- waitMyNewUniStreamId conn     addStream conn sid @@ -60,6 +62,8 @@     conn = streamConnection s     flowControl dats len wait = do         -- 1-RTT+        -- FLOW CONTROL: MAX_STREAM_DATA: send: respecting peer's limit+        -- FLOW CONTROL: MAX_DATA: send: respecting peer's limit         eblocked <- checkBlocked s len wait         case eblocked of           Right n@@ -145,8 +149,21 @@     delStream conn s     when ((isClient conn && isServerInitiatedBidirectional sid)        || (isServer conn && isClientInitiatedBidirectional sid)) $ do-        n <- getPeerMaxStreams conn-        putOutput conn $ OutControl RTT1Level [MaxStreams Unidirectional n] $ return ()+        -- FLOW CONTROL: MAX_STREAMS: recv: announcing my limit properly+        checkMaxStreams conn Bidirectional+    when ((isClient conn && isServerInitiatedUnidirectional sid)+       || (isServer conn && isClientInitiatedUnidirectional sid)) $ do+        -- FLOW CONTROL: MAX_STREAMS: recv: announcing my limit properly+        checkMaxStreams conn Unidirectional+  where+    checkMaxStreams conn dir = do+        mx <- checkStreamIdRoom conn dir+        case mx of+          Nothing -> return ()+          Just nms -> do+            sendFrames conn RTT1Level [MaxStreams dir nms]+            fire conn (Microseconds 50000) $+                sendFrames conn RTT1Level [MaxStreams dir nms]  -- | Accepting a stream initiated by the peer. acceptStream :: Connection -> IO Stream@@ -166,20 +183,20 @@     window <- getRxStreamWindow s     let sid = streamId s         initialWindow = initialRxMaxStreamData conn sid+    -- FLOW CONTROL: MAX_STREAM_DATA: recv: announcing my limit properly     when (window <= (initialWindow !>>. 1)) $ do         newMax <- addRxMaxStreamData s initialWindow         sendFrames conn RTT1Level [MaxStreamData sid newMax]-        fire conn (Microseconds 50000) $ do-            newMax' <- getRxMaxStreamData s-            sendFrames conn RTT1Level [MaxStreamData sid newMax']+        fire conn (Microseconds 50000) $+            sendFrames conn RTT1Level [MaxStreamData sid newMax]+    -- FLOW CONTROL: MAX_DATA: recv: announcing my limit properly     cwindow <- getRxDataWindow conn     let cinitialWindow = initialMaxData $ getMyParameters conn     when (cwindow <= (cinitialWindow !>>. 1)) $ do         newMax <- addRxMaxData conn cinitialWindow         sendFrames conn RTT1Level [MaxData newMax]-        fire conn (Microseconds 50000) $ do-            newMax' <- getRxMaxData conn-            sendFrames conn RTT1Level [MaxData newMax']+        fire conn (Microseconds 50000) $+            sendFrames conn RTT1Level [MaxData newMax]     return bs  -- | Closing a stream with an error code.
Network/QUIC/Imports.hs view
@@ -33,7 +33,7 @@ import Data.Array.IO import Data.Bits import Data.ByteString.Builder (Builder)-import Data.ByteString.Internal (ByteString(..), memcpy)+import Data.ByteString.Internal (ByteString(..)) import Data.ByteString.Short.Internal (ShortByteString(..)) import Data.Foldable import Data.IORef@@ -43,6 +43,7 @@ import Data.Ord import Data.Word import Foreign.ForeignPtr+import Foreign.Marshal.Utils (copyBytes) import Foreign.Ptr import Network.ByteOrder import Network.QUIC.Utils@@ -68,5 +69,5 @@ copyBS :: Buffer -> ByteString -> IO Int copyBS dst (PS fptr off len) = withForeignPtr fptr $ \src0 -> do     let src = src0 `plusPtr` off-    memcpy dst src len+    copyBytes dst src len     return len
Network/QUIC/Receiver.hs view
@@ -248,6 +248,10 @@         closeConnection ProtocolViolation "NEW_TOKEN for server or in 1-RTT"     when (isClient conn) $ setNewToken conn token processFrame conn RTT0Level (StreamF sid off (dat:_) fin) = do+    when (off == 0) $ updatePeerStreamId conn sid+    -- FLOW CONTROL: MAX_STREAMS: recv: rejecting if over my limit+    ok <- checkRxMaxStreams conn sid+    unless ok $ closeConnection StreamLimitError "stream id is too large"     when (isSendOnly conn sid) $         closeConnection StreamStateError "send-only stream"     mstrm <- findStream conn sid@@ -255,14 +259,28 @@     strm <- maybe (createStream conn sid) return mstrm     let len = BS.length dat         rx = RxStreamData dat off len fin-    ok <- putRxStreamData strm rx-    unless ok $ closeConnection FlowControlError "Flow control error in 0-RTT"+    fc <- putRxStreamData strm rx+    case fc of+      -- FLOW CONTROL: MAX_STREAM_DATA: recv: rejecting if over my limit+      OverLimit -> closeConnection FlowControlError "Flow control error for stream in 0-RTT"+      Duplicated -> return ()+      Reassembled -> do+          ok' <- checkRxMaxData conn len+          -- FLOW CONTROL: MAX_DATA: send: respecting peer's limit+          unless ok' $ closeConnection FlowControlError "Flow control error for connection in 0-RTT" processFrame conn RTT1Level (StreamF sid _ [""] False) = do+    -- FLOW CONTROL: MAX_STREAMS: recv: rejecting if over my limit+    ok <- checkRxMaxStreams conn sid+    unless ok $ closeConnection StreamLimitError "stream id is too large"     when (isSendOnly conn sid) $         closeConnection StreamStateError "send-only stream"     mstrm <- findStream conn sid     guardStream conn sid mstrm processFrame conn RTT1Level (StreamF sid off (dat:_) fin) = do+    when (off == 0) $ updatePeerStreamId conn sid+    -- FLOW CONTROL: MAX_STREAMS: recv: rejecting if over my limit+    ok <- checkRxMaxStreams conn sid+    unless ok $ closeConnection StreamLimitError "stream id is too large"     when (isSendOnly conn sid) $         closeConnection StreamStateError "send-only stream"     mstrm <- findStream conn sid@@ -270,8 +288,15 @@     strm <- maybe (createStream conn sid) return mstrm     let len = BS.length dat         rx = RxStreamData dat off len fin-    ok <- putRxStreamData strm rx-    unless ok $ closeConnection FlowControlError "Flow control error in 1-RTT"+    fc <- putRxStreamData strm rx+    case fc of+      -- FLOW CONTROL: MAX_STREAM_DATA: recv: rejecting if over my limit+      OverLimit -> closeConnection FlowControlError "Flow control error for stream in 1-RTT"+      Duplicated -> return ()+      Reassembled -> do+          ok' <- checkRxMaxData conn len+          -- FLOW CONTROL: MAX_DATA: send: respecting peer's limit+          unless ok' $ closeConnection FlowControlError "Flow control error for connection in 1-RTT" processFrame conn lvl (MaxData n) = do     when (lvl == InitialLevel || lvl == HandshakeLevel) $         closeConnection ProtocolViolation "MAX_DATA in Initial or Handshake"@@ -293,9 +318,9 @@     when (n > 2^(60 :: Int)) $         closeConnection FrameEncodingError "Too large MAX_STREAMS"     if dir == Bidirectional then-        setMyMaxStreams conn n+        setTxMaxStreams conn n       else-        setMyUniMaxStreams conn n+        setTxUniMaxStreams conn n processFrame _conn _lvl DataBlocked{} = return () processFrame _conn _lvl (StreamDataBlocked _sid _) = return () processFrame _conn lvl (StreamsBlocked _dir n) = do
Network/QUIC/Stream.hs view
@@ -33,6 +33,7 @@   -- * Reass   , takeRecvStreamQwithSize   , putRxStreamData+  , FlowCntl(..)   , tryReassemble   -- * Table   , StreamTable
Network/QUIC/Stream/Reass.hs view
@@ -4,6 +4,7 @@ module Network.QUIC.Stream.Reass (     takeRecvStreamQwithSize   , putRxStreamData+  , FlowCntl(..)   , tryReassemble   ) where @@ -87,14 +88,16 @@ ---------------------------------------------------------------- ---------------------------------------------------------------- -putRxStreamData :: Stream -> RxStreamData -> IO Bool-putRxStreamData s rx@(RxStreamData dat off _ _) = do+data FlowCntl = OverLimit | Duplicated | Reassembled++putRxStreamData :: Stream -> RxStreamData -> IO FlowCntl+putRxStreamData s rx@(RxStreamData _ off len _) = do     lim <- getRxMaxStreamData s-    if BS.length dat + off > lim then-        return False+    if len + off > lim then+        return OverLimit       else do-        _ <- tryReassemble s rx put putFin-        return True+        dup <- tryReassemble s rx put putFin+        return $ if dup then Duplicated else Reassembled   where     put "" = return ()     put d  = putRecvStreamQ s d
Network/QUIC/Types/Frame.hs view
@@ -69,6 +69,12 @@ isServerInitiated :: StreamId -> Bool isServerInitiated sid = (0b1 .&. sid) == 1 +isBidirectional :: StreamId -> Bool+isBidirectional sid = (0b10 .&. sid) == 0++isUnidirectional :: StreamId -> Bool+isUnidirectional sid = (0b10 .&. sid) == 2+ type Delay = Milliseconds  type Fin = Bool
quic.cabal view
@@ -1,6 +1,6 @@ cabal-version:      >=1.10 name:               quic-version:            0.1.7+version:            0.1.8 license:            BSD3 license-file:       LICENSE maintainer:         kazu@iij.ad.jp
test/FrameSpec.hs view
@@ -1,9 +1,8 @@ module FrameSpec where -import Control.Monad-import Data.ByteString.Internal (memset) import Data.Word import Foreign.Marshal.Alloc+import Foreign.Marshal.Utils (fillBytes) import Foreign.Ptr import Foreign.Storable import Test.Hspec@@ -19,7 +18,7 @@             E.bracket (mallocBytes siz) free $ \beg -> do                 let (+.) = plusPtr                     end  = beg +. siz-                void $ memset beg 0 $ fromIntegral siz+                fillBytes beg 0 $ fromIntegral siz                 countZero beg end `shouldReturn` siz                 countZero (beg +. 1) end `shouldReturn` (siz - 1)                 countZero (beg +. 2) end `shouldReturn` (siz - 2)
test/TransportError.hs view
@@ -43,6 +43,9 @@         it "MUST send FLOW_CONTROL_ERROR if a STREAM frame with a large offset is received [Transport 4.1]" $ \_ -> do             let cc = addHook cc0 $ setOnPlainCreated largeOffset             runCnoOp cc ms `shouldThrow` transportErrorsIn [FlowControlError]+        it "MUST send STREAM_LIMIT_ERROR if a stream ID exceeding the limit" $ \_ -> do+            let cc = addHook cc0 $ setOnPlainCreated largeStreamId+            runCnoOp cc ms `shouldThrow` transportErrorsIn [StreamLimitError]         it "MUST send TRANSPORT_PARAMETER_ERROR if initial_source_connection_id is missing [Transport 7.3]" $ \_ -> do             let cc = addHook cc0 $ setOnTransportParametersCreated dropInitialSourceConnectionId             runCnoOp cc ms `shouldThrow` transportErrorsIn [TransportParameterError]@@ -228,6 +231,13 @@   | otherwise        = plain   where     fake = StreamF 0 100000000 ["GET /\r\n"] True++largeStreamId :: EncryptionLevel -> Plain -> Plain+largeStreamId lvl plain+  | lvl == RTT1Level = plain { plainFrames = fake : plainFrames plain }+  | otherwise        = plain+  where+    fake = StreamF 1000000000 0 ["GET /\r\n"] True  unknownFrame :: EncryptionLevel -> Plain -> Plain unknownFrame lvl plain
util/ClientX.hs view
@@ -34,7 +34,7 @@     loop n = do         auxDebug "GET"         get-        threadDelay 1000000+        threadDelay 100000         loop (n - 1)     get = do         s <- stream conn@@ -63,7 +63,7 @@     loop n hdrblk = do         auxDebug "GET"         get hdrblk-        threadDelay 1000000+        threadDelay 100000         loop (n - 1) hdrblk     get hdrblk = do         s <- stream conn