packages feed

h3spec 0.1.14 → 0.1.15

raw patch · 5 files changed

+292/−102 lines, 5 filesdep −networkdep ~http3dep ~quicdep ~tls

Dependencies removed: network

Dependency ranges changed: http3, quic, tls

Files

ChangeLog.md view
@@ -1,5 +1,22 @@ # Revision history for h3spec +## 0.1.15++These changes were made on main without PRs.++* Using `http3` v0.1.10 and `quic` v0.3.20.+* 9 new test cases, 86 in all. HTTP/3 and QPACK: H3_MESSAGE_ERROR for a+  duplicated pseudo-header; H3_FRAME_UNEXPECTED for PUSH_PROMISE;+  H3_ID_ERROR for a CANCEL_PUSH push ID not allowed, a MAX_PUSH_ID going+  down and a GOAWAY going up; H3_FRAME_ERROR for octets after MAX_PUSH_ID's+  push ID; a frame of unknown type ignored however long;+  QPACK_DECOMPRESSION_FAILED for a field line cut short and for a badly+  padded Huffman string.+* Test names and RFC section references corrected.+* `-m` works together with `-s`; an invalid `--timeout` is refused.+* HTTP/3 cases are pending on an HQ connection, and the 0-RTT case is+  pending against a server that does not accept 0-RTT.+ ## 0.1.14  * Using `http3` v0.1.7 and `quic` v0.3.13.
HTTP3Error.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeApplications #-}  module HTTP3Error (     h3ErrorSpec,@@ -29,27 +30,16 @@  type Millisecond = Int +-- | Making a request, and then waiting for the connection error a test+-- expects, for as long as the timeout allows.+--+-- It used to wait a tenth of a second after the response.  A server+-- answering the request before it got round to the control stream, as one+-- under load could, then had the connection closed under it, and the test+-- failed for want of an error it was about to send. runC     :: ClientConfig -> H3.ClientConfig -> H3.Config -> Millisecond -> IO (Maybe ())-runC qcc cconf conf ms = timeout us $ run qcc $ \conn -> do-    info <- getConnectionInfo conn-    case alpn info of-        Just proto | "hq" `BS.isPrefixOf` proto -> do-            waitEstablished conn-            putStrLn $-                "Warning: "-                    ++ C8.unpack proto-                    ++ " is negotiated. Skipping this test. Use \"h3spec -s HTTP/3\" next time."-            E.throwIO $ ApplicationProtocolErrorIsReceived H3InternalError ""-        _ -> H3.run conn cconf conf client-  where-    us = ms * 1000-    client :: H3.Client ()-    client sendRequest _aux = do-        let req = H3.requestNoBody methodGet "/" []-        ret <- sendRequest req $ \_rsp -> return ()-        threadDelay 100000-        return ret+runC = runCReq $ H3.requestNoBody methodGet "/" []  -- | Like 'runC', but sending a request of our own choosing. runCReq@@ -59,20 +49,62 @@     -> H3.Config     -> Millisecond     -> IO (Maybe ())-runCReq req qcc cconf conf ms = timeout us $ run qcc $ \conn -> do+runCReq = runCWith True++-- | Like 'runCReq', for a test that expects no error: done a tenth of a+-- second after the response.+runCReqOk+    :: H3.Request+    -> ClientConfig+    -> H3.ClientConfig+    -> H3.Config+    -> Millisecond+    -> IO (Maybe ())+runCReqOk = runCWith False++-- | With 'True', a test that expects a connection error: a reset of the+-- request does not end it, since a server closing the connection may reset+-- its streams first.+runCWith+    :: Bool+    -> H3.Request+    -> ClientConfig+    -> H3.ClientConfig+    -> H3.Config+    -> Millisecond+    -> IO (Maybe ())+runCWith expectError req qcc cconf conf ms = timeout us $ run qcc $ \conn -> do     info <- getConnectionInfo conn     case alpn info of         Just proto | "hq" `BS.isPrefixOf` proto -> do             waitEstablished conn-            E.throwIO $ ApplicationProtocolErrorIsReceived H3InternalError ""+            E.throwIO $ HQNegotiated proto         _ -> H3.run conn cconf conf client   where     us = ms * 1000-    client sendRequest _aux = do-        ret <- sendRequest req $ \_rsp -> return ()-        threadDelay 100000-        return ret+    client :: H3.Client ()+    client sendRequest _aux+        | expectError = do+            _ <- E.try @StreamResetIsReceived $ sendRequest req $ \_rsp -> return ()+            threadDelay us+        | otherwise = do+            ret <- sendRequest req $ \_rsp -> return ()+            threadDelay 100000+            return ret +-- | Thrown when hq is negotiated instead of h3, so that the test is+-- reported as pending rather than passing on a generic error code.+newtype HQNegotiated = HQNegotiated BS.ByteString deriving (Show)++instance E.Exception HQNegotiated++-- | 'it' for the tests here, leaving a test pending against an hq server.+itH3 :: HasCallStack => String -> (a -> Expectation) -> SpecWith a+itH3 name body = it name $ \x ->+    body x `E.catch` \(HQNegotiated proto) ->+        pendingWith $+            C8.unpack proto ++ " is negotiated. Use \"h3spec -s HTTP/3\" next time."+ -- | The error cases for an HTTP/3 server. -- -- These also make h3spec, which runs them against a server somewhere else.@@ -89,184 +121,234 @@ h3ErrorSpec qcc cconf ms served = do     conf0 <- runIO H3.allocSimpleConfig     describe "HTTP/3 servers" $ do-        it+        itH3             "MUST send H3_FRAME_UNEXPECTED if DATA is received before HEADERS [HTTP/3 4.1]"             $ \_ -> do                 let conf = addHook conf0 $ setOnHeadersFrameCreated requestIllegalData                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3FrameUnexpected]-        it "MUST send H3_MESSAGE_ERROR if a pseudo-header is duplicated [HTTP/3 4.1.1]" $ \_ -> do-            let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader3-                qcc' = addQUICHook qcc $ setOnResetStreamReceived $ \_strm aerr -> E.throwIO (ApplicationProtocolErrorIsReceived aerr "")-            runC qcc' cconf conf ms-                `shouldThrow` applicationProtocolErrorsIn [H3MessageError]-        it-            "MUST send H3_MESSAGE_ERROR if mandatory pseudo-header fields are absent [HTTP/3 4.1.3]"+        itH3+            "MUST send H3_MESSAGE_ERROR if a pseudo-header is duplicated [HTTP/3 4.3.1]"+            $ \_ -> do+                let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader3+                    qcc' = throwOnReset qcc+                runC qcc' cconf conf ms+                    `shouldThrow` applicationProtocolErrorsIn [H3MessageError]+        itH3+            "MUST send H3_MESSAGE_ERROR if mandatory pseudo-header fields are absent [HTTP/3 4.3.1]"             $ \x -> do                 let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader0-                    qcc' = addQUICHook qcc $ setOnResetStreamReceived $ \_strm aerr -> E.throwIO (ApplicationProtocolErrorIsReceived aerr "")+                    qcc' = throwOnReset qcc                 refusedNotServed (served x) $                     runC qcc' cconf conf ms                         `shouldThrow` applicationProtocolErrorsIn [H3MessageError]-        it-            "MUST send H3_MESSAGE_ERROR if prohibited pseudo-header fields are present[HTTP/3 4.1.3]"+        itH3+            "MUST send H3_MESSAGE_ERROR if prohibited pseudo-header fields are present [HTTP/3 4.3]"             $ \_ -> do                 let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader1-                    qcc' = addQUICHook qcc $ setOnResetStreamReceived $ \_strm aerr -> E.throwIO (ApplicationProtocolErrorIsReceived aerr "")+                    qcc' = throwOnReset qcc                 runC qcc' cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3MessageError]-        it-            "MUST send H3_MESSAGE_ERROR if pseudo-header fields exist after fields [HTTP/3 4.1.3]"+        itH3+            "MUST send H3_MESSAGE_ERROR if pseudo-header fields exist after fields [HTTP/3 4.3]"             $ \_ -> do                 let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader2-                    qcc' = addQUICHook qcc $ setOnResetStreamReceived $ \_strm aerr -> E.throwIO (ApplicationProtocolErrorIsReceived aerr "")+                    qcc' = throwOnReset qcc                 runC qcc' cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3MessageError]         forM_ connectionSpecific $ \field@(name, _) ->-            it+            itH3                 ( "MUST treat a request with "                     ++ C8.unpack (CI.original name)                     ++ " as malformed [HTTP/3 4.2]"                 )                 $ \x -> do                     let req = H3.requestNoBody methodGet "/" [field]-                        qcc' = addQUICHook qcc $ setOnResetStreamReceived $ \_strm aerr -> E.throwIO (ApplicationProtocolErrorIsReceived aerr "")+                        qcc' = throwOnReset qcc                     refusedNotServed (served x) $                         runCReq req qcc' cconf conf0 ms                             `shouldThrow` applicationProtocolErrorsIn [H3MessageError]-        it "MUST accept TE with trailers [HTTP/3 4.2]" $ \_ -> do+        itH3 "MUST accept TE with trailers [HTTP/3 4.2]" $ \_ -> do             let req = H3.requestNoBody methodGet "/" [("te", "trailers")]-            runCReq req qcc cconf conf0 ms `shouldReturn` Just ()-        it+            runCReqOk req qcc cconf conf0 ms `shouldReturn` Just ()+        itH3             "MUST send H3_MISSING_SETTINGS if the first control frame is not SETTINGS [HTTP/3 6.2.1]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlFrameCreated startWithNonSettings                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3MissingSettings]-        it+        itH3             "MUST send H3_FRAME_UNEXPECTED if a DATA frame is received on a control stream [HTTP/3 7.2.1]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlFrameCreated controlData                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3FrameUnexpected]-        it+        itH3             "MUST send H3_FRAME_UNEXPECTED if a HEADERS frame is received on a control stream [HTTP/3 7.2.2]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlFrameCreated controlHeaders                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3FrameUnexpected]-        it+        itH3             "MUST send H3_FRAME_UNEXPECTED if a second SETTINGS frame is received [HTTP/3 7.2.4]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlFrameCreated doubleSettings                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3FrameUnexpected]         {- this is MAY-                it "MUST send H3_SETTINGS_ERROR if duplicate setting identifiers exist [HTTP/3 7.2.4]" $ \_ -> do+                itH3 "MUST send H3_SETTINGS_ERROR if duplicate setting identifiers exist [HTTP/3 7.2.4]" $ \_ -> do                     let conf = addHook conf0 $ setOnControlFrameCreated illegalSettings0                     runC qcc cconf conf ms `shouldThrow` applicationProtocolErrorsIn [H3SettingsError]         -}-        it+        itH3             "MUST send H3_SETTINGS_ERROR if HTTP/2 settings are included [HTTP/3 7.2.4.1]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlFrameCreated illegalSettings1                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3SettingsError]-        it+        itH3             "MUST send H3_FRAME_ERROR if a SETTINGS frame stops mid-parameter [HTTP/3 7.1]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlFrameCreated truncatedSettings                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3FrameError]-        it+        itH3             "MUST NOT stop reading settings at a reserved identifier [HTTP/3 7.2.4.1]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlFrameCreated greaseThenHttp2Setting                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3SettingsError]-        it-            "treats a repeated setting identifier as an error whatever its value [HTTP/3 7.2.4.1]"+        itH3+            "treats a repeated setting identifier as an error whatever its value [HTTP/3 7.2.4]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlFrameCreated duplicateLargeSetting                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3SettingsError]-        it-            "MUST send H3_FRAME_UNEXPECTED if CANCEL_PUSH is received in a request stream [HTTP/3 7.2.5]"+        itH3+            "MUST send H3_FRAME_UNEXPECTED if CANCEL_PUSH is received in a request stream [HTTP/3 7.2.3]"             $ \_ -> do                 let conf = addHook conf0 $ setOnHeadersFrameCreated requestCancelPush                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3FrameUnexpected]-        it-            "MUST send QPACK_DECOMPRESSION_FAILED if an invalid static table index exits in a field line representation [QPACK 3.1]"+        itH3+            "MUST send H3_FRAME_UNEXPECTED if PUSH_PROMISE is received [HTTP/3 7.2.5]"             $ \_ -> do+                let conf = addHook conf0 $ setOnHeadersFrameCreated requestPushPromise+                runC qcc cconf conf ms+                    `shouldThrow` applicationProtocolErrorsIn [H3FrameUnexpected]+        itH3+            "MUST send H3_ID_ERROR if CANCEL_PUSH names a push ID not allowed [HTTP/3 7.2.3]"+            $ \_ -> do+                let conf = addHook conf0 $ setOnControlFrameCreated cancelPushNotAllowed+                runC qcc cconf conf ms+                    `shouldThrow` applicationProtocolErrorsIn [H3IdError]+        itH3+            "MUST send H3_ID_ERROR if MAX_PUSH_ID goes down [HTTP/3 7.2.7]"+            $ \_ -> do+                let conf = addHook conf0 $ setOnControlFrameCreated maxPushIdDown+                runC qcc cconf conf ms+                    `shouldThrow` applicationProtocolErrorsIn [H3IdError]+        itH3+            "MUST send H3_ID_ERROR if GOAWAY goes up [HTTP/3 5.2]"+            $ \_ -> do+                let conf = addHook conf0 $ setOnControlFrameCreated goawayUp+                runC qcc cconf conf ms+                    `shouldThrow` applicationProtocolErrorsIn [H3IdError]+        itH3+            "MUST send H3_FRAME_ERROR if MAX_PUSH_ID has octets after its push ID [HTTP/3 7.1]"+            $ \_ -> do+                let conf = addHook conf0 $ setOnControlFrameCreated maxPushIdTrailing+                runC qcc cconf conf ms+                    `shouldThrow` applicationProtocolErrorsIn [H3FrameError]+        itH3+            "MUST ignore a frame of unknown type, however long [HTTP/3 7.2.8]"+            $ \_ -> do+                let conf = addHook conf0 $ setOnControlFrameCreated longUnknownFrame+                runCReqOk (H3.requestNoBody methodGet "/" []) qcc cconf conf ms+                    `shouldReturn` Just ()+        itH3+            "MUST send QPACK_DECOMPRESSION_FAILED if an invalid static table index exists in a field line representation [QPACK 3.1]"+            $ \_ -> do                 let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader4                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [QpackDecompressionFailed]-        it-            "MUST send QPACK_DECOMPRESSION_FAILED if a field line references a dynamic table entry that is not there [QPACK 2.1.2]"+        itH3+            "MUST send QPACK_DECOMPRESSION_FAILED if a field line references a dynamic table entry that is not there [QPACK 2.2.3]"             $ \_ -> do                 let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader5                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [QpackDecompressionFailed]-        it-            "MUST NOT buffer a frame longer than SETTINGS_MAX_FIELD_SECTION_SIZE [HTTP/3 7.1]"+        itH3+            "MUST send QPACK_DECOMPRESSION_FAILED if a field line is cut short [QPACK 2.2.3]"             $ \_ -> do+                let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader6+                runC qcc cconf conf ms+                    `shouldThrow` applicationProtocolErrorsIn [QpackDecompressionFailed]+        itH3+            "MUST send QPACK_DECOMPRESSION_FAILED if a Huffman string is badly padded [QPACK 4.1.2]"+            $ \_ -> do+                let conf = addHook conf0 $ setOnHeadersFrameCreated illegalHeader7+                runC qcc cconf conf ms+                    `shouldThrow` applicationProtocolErrorsIn [QpackDecompressionFailed]+        itH3+            "MUST NOT buffer an over-long frame on a control stream [HTTP/3 7.1]"+            $ \_ -> do                 let conf = addHook conf0 $ setOnControlStreamCreated overLongFrame                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3ExcessiveLoad]-        it-            "MUST send QPACK_ENCODER_STREAM_ERROR if a new dynamic table capacity value exceeds the limit [QPACK 4.1.3]"+        itH3+            "MUST send QPACK_ENCODER_STREAM_ERROR if a new dynamic table capacity value exceeds the limit [QPACK 4.3.1]"             $ \_ -> do                 let conf = addHook conf0 $ setOnEncoderStreamCreated largeTableCapacity                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [QpackEncoderStreamError]-        it-            "MUST send H3_CLOSED_CRITICAL_STREAM if a control stream is closed [QPACK 4.2]"+        itH3+            "MUST send H3_CLOSED_CRITICAL_STREAM if a control stream is closed [HTTP/3 6.2.1]"             $ \_ -> do                 let conf = addHook conf0 $ setOnControlStreamCreated closeStream                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3ClosedCriticalStream]-        it+        itH3             "MUST send H3_STREAM_CREATION_ERROR if a second control stream is opened [HTTP/3 6.2.1]"             $ \_ -> do                 -- A stream type of 0x00, then an empty SETTINGS frame.                 opened <- newIORef False                 let conf = addHook conf0 $ setOnControlStreamCreated $ openAnother opened "\x00\x04\x00"                 expectIfOpened opened (runC qcc cconf conf ms) [H3StreamCreationError]-        it+        itH3             "MUST send H3_STREAM_CREATION_ERROR if a client opens a push stream [HTTP/3 6.2.2]"             $ \_ -> do                 -- A stream type of 0x01, then push ID 0.                 opened <- newIORef False                 let conf = addHook conf0 $ setOnControlStreamCreated $ openAnother opened "\x01\x00"                 expectIfOpened opened (runC qcc cconf conf ms) [H3StreamCreationError]-        it+        itH3             "MUST send H3_STREAM_CREATION_ERROR if a second encoder stream is opened [QPACK 4.2]"             $ \_ -> do                 opened <- newIORef False                 let conf = addHook conf0 $ setOnEncoderStreamCreated $ openAnother opened "\x02"                 expectIfOpened opened (runC qcc cconf conf ms) [H3StreamCreationError]-        it+        itH3             "MUST send H3_STREAM_CREATION_ERROR if a second decoder stream is opened [QPACK 4.2]"             $ \_ -> do                 opened <- newIORef False                 let conf = addHook conf0 $ setOnDecoderStreamCreated $ openAnother opened "\x03"                 expectIfOpened opened (runC qcc cconf conf ms) [H3StreamCreationError]-        it+        itH3             "MUST send H3_CLOSED_CRITICAL_STREAM if an encoder stream is closed [QPACK 4.2]"             $ \_ -> do                 let conf =                         noEncoderTable $ addHook conf0 $ setOnEncoderStreamCreated closeStream                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3ClosedCriticalStream]-        it+        itH3             "MUST send H3_CLOSED_CRITICAL_STREAM if a decoder stream is closed [QPACK 4.2]"             $ \_ -> do                 let conf =                         noDecoderTable $ addHook conf0 $ setOnDecoderStreamCreated closeStream                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3ClosedCriticalStream]-        it+        itH3             "MUST send H3_CLOSED_CRITICAL_STREAM if an encoder stream ends inside an instruction [QPACK 4.2]"             $ \_ -> do                 -- The first octet of a Set Dynamic Table Capacity that goes on.@@ -275,7 +357,7 @@                         closeStream strm                 runC qcc cconf conf ms                     `shouldThrow` applicationProtocolErrorsIn [H3ClosedCriticalStream]-        it+        itH3             "MUST send QPACK_DECODER_STREAM_ERROR if Insert Count Increment is 0 [QPACK 4.4.3]"             $ \_ -> do                 let conf = addHook conf0 $ setOnDecoderStreamCreated zeroInsertCountIncrement@@ -333,23 +415,23 @@ h3DrainSpec qcc cconf ms = do     conf0 <- runIO H3.allocSimpleConfig     describe "HTTP/3 servers reading a request's body" $ do-        it+        itH3             "MUST treat content that does not match content-length as malformed [HTTP/3 4.1.2]"             $ \_ -> do                 -- content-length says five octets and the request carries                 -- none.  /drain reads the body, which is where the count is                 -- checked; a body nobody reads is never counted.                 let req = H3.requestNoBody methodPost "/drain" [("content-length", "5")]-                    qcc' = addQUICHook qcc $ setOnResetStreamReceived $ \_strm aerr -> E.throwIO (ApplicationProtocolErrorIsReceived aerr "")+                    qcc' = throwOnReset qcc                 runCReq req qcc' cconf conf0 ms                     `shouldThrow` applicationProtocolErrorsIn [H3MessageError]-        it+        itH3             "MUST treat a request whose trailers carry connection as malformed [HTTP/3 4.2]"             $ \_ -> do                 -- /drain reads the body, and so the trailers after it.                 let req0 = H3.requestBuilder methodPost "/drain" [] "hello"                     req = H3.setRequestTrailersMaker req0 connectionTrailer-                    qcc' = addQUICHook qcc $ setOnResetStreamReceived $ \_strm aerr -> E.throwIO (ApplicationProtocolErrorIsReceived aerr "")+                    qcc' = throwOnReset qcc                 runCReq req qcc' cconf conf0 ms                     `shouldThrow` applicationProtocolErrorsIn [H3MessageError] @@ -392,6 +474,32 @@ requestCancelPush :: [H3Frame] -> [H3Frame] requestCancelPush fs = H3Frame H3FrameCancelPush "" : fs +-- PUSH_PROMISE, push ID 0, and no field section to speak of.  Before+-- HEADERS, since a server need not read what comes after them.+requestPushPromise :: [H3Frame] -> [H3Frame]+requestPushPromise fs = H3Frame H3FramePushPromise "\x00" : fs++-- CANCEL_PUSH for push ID 0, with no MAX_PUSH_ID sent: no push is allowed.+cancelPushNotAllowed :: [H3Frame] -> [H3Frame]+cancelPushNotAllowed fs = fs ++ [H3Frame H3FrameCancelPush "\x00"]++-- MAX_PUSH_ID 5, then 3.+maxPushIdDown :: [H3Frame] -> [H3Frame]+maxPushIdDown fs = fs ++ [H3Frame H3FrameMaxPushId "\x05", H3Frame H3FrameMaxPushId "\x03"]++-- GOAWAY 0, then 4.+goawayUp :: [H3Frame] -> [H3Frame]+goawayUp fs = fs ++ [H3Frame H3FrameGoaway "\x00", H3Frame H3FrameGoaway "\x04"]++-- MAX_PUSH_ID 5, and an octet more.+maxPushIdTrailing :: [H3Frame] -> [H3Frame]+maxPushIdTrailing fs = fs ++ [H3Frame H3FrameMaxPushId "\x05\x00"]++-- A reserved type (0x21), with a payload longer than+-- SETTINGS_MAX_FIELD_SECTION_SIZE.+longUnknownFrame :: [H3Frame] -> [H3Frame]+longUnknownFrame fs = fs ++ [H3Frame (H3FrameUnknown 0x21) (BS.replicate 40000 0)]+ requestIllegalData :: [H3Frame] -> [H3Frame] requestIllegalData fs = H3Frame H3FrameData "" : fs @@ -466,6 +574,34 @@         "\x00\x00\xd1\xd7\x50\x09\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\xc1\x80"     ] +-- [(":method","GET")+-- ,(":scheme","https")+-- ,(":authority","127.0.0.1")+-- ,(":path","/")] ++ age with a value of five octets, of which two are there+--+-- Running off the end of the section raised BufferOverrun, which the server+-- answered with a stream reset of H3_MESSAGE_ERROR.+illegalHeader6 :: [H3Frame] -> [H3Frame]+illegalHeader6 _ =+    [ H3Frame+        H3FrameHeaders+        "\x00\x00\xd1\xd7\x50\x09\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\xc1\x52\x05ab"+    ]++-- [(":method","GET")+-- ,(":scheme","https")+-- ,(":authority","127.0.0.1")+-- ,(":path","/")] ++ age with a one-octet Huffman value of 0x00+--+-- The three bits left over are padding, which must be the start of EOS:+-- ones (RFC 7541, section 5.2).+illegalHeader7 :: [H3Frame] -> [H3Frame]+illegalHeader7 _ =+    [ H3Frame+        H3FrameHeaders+        "\x00\x00\xd1\xd7\x50\x09\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\xc1\x52\x81\x00"+    ]+ {- -- [(SettingsQpackBlockedStreams,100) -- ,(SettingsQpackMaxTableCapacity,4096)@@ -586,6 +722,11 @@ addQUICHook cc modify = cc'   where     cc' = cc{ccHooks = modify $ ccHooks cc}++-- | Turning a RESET_STREAM from the server into the exception the tests expect.+throwOnReset :: ClientConfig -> ClientConfig+throwOnReset qcc = addQUICHook qcc $ setOnResetStreamReceived $ \_strm aerr ->+    E.throwIO (ApplicationProtocolErrorIsReceived aerr "")  setOnResetStreamReceived     :: (Stream -> ApplicationProtocolError -> IO ()) -> Hooks -> Hooks
TransportError.hs view
@@ -45,7 +45,8 @@             $ \_ -> 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+        it+            "MUST send STREAM_LIMIT_ERROR if a stream ID exceeds the limit [Transport 4.6]" $ \_ -> do             let cc = addHook cc0 $ setOnPlainCreated largeStreamId             runCnoOp cc ms `shouldThrow` transportErrorsIn [StreamLimitError]         it@@ -60,7 +61,7 @@                         addHook cc0 $ setOnTransportParametersCreated setOriginalDestinationConnectionId                 runCnoOp cc ms `shouldThrow` transportErrorsIn [TransportParameterError]         it-            "MUST send TRANSPORT_PARAMETER_ERROR if preferred_address, is received [Transport 18.2]"+            "MUST send TRANSPORT_PARAMETER_ERROR if preferred_address is received [Transport 18.2]"             $ \_ -> do                 let cc = addHook cc0 $ setOnTransportParametersCreated setPreferredAddress                 runCnoOp cc ms `shouldThrow` transportErrorsIn [TransportParameterError]@@ -85,7 +86,7 @@                 let cc = addHook cc0 $ setOnTransportParametersCreated setMaxUdpPayloadSize                 runCnoOp cc ms `shouldThrow` transportErrorsIn [TransportParameterError]         it-            "MUST send TRANSPORT_PARAMETER_ERROR if ack_delay_exponen > 20 [Transport 7.4 and 18.2]"+            "MUST send TRANSPORT_PARAMETER_ERROR if ack_delay_exponent > 20 [Transport 7.4 and 18.2]"             $ \_ -> do                 let cc = addHook cc0 $ setOnTransportParametersCreated setAckDelayExponent                 runCnoOp cc ms `shouldThrow` transportErrorsIn [TransportParameterError]@@ -119,6 +120,13 @@             $ \_ -> do                 let cc = addHook cc0 $ setOnPlainCreated unknownFrame                 runCnoOp cc ms `shouldThrow` transportErrorsIn [FrameEncodingError]+        it+            "MUST send FRAME_ENCODING_ERROR if a frame is cut short [Transport 12.4]"+            $ \_ -> do+                let cc = addHook cc0 $ setOnPlainCreated truncatedFrame+                -- Not the generic codes 'transportErrorsIn' lets through:+                -- INTERNAL_ERROR is what the decoder's exception used to give.+                runCnoOp cc ms `shouldThrow` frameEncodingError         it "MUST send PROTOCOL_VIOLATION on no frames [Transport 12.4]" $ \_ -> do             let cc = addHook cc0 $ setOnPlainCreated noFrames             runCnoOp cc ms `shouldThrow` transportError@@ -133,7 +141,7 @@                 let cc = addHook cc0 $ setOnPlainCreated cryptoBeyondBuffer                 runCnoOp cc ms `shouldThrow` transportErrorsIn [CryptoBufferExceeded]         it-            "MUST send PROTOCOL_VIOLATION if PATH_CHALLENGE in Handshake is received [Transport 17.2.4]"+            "MUST send PROTOCOL_VIOLATION if PATH_CHALLENGE in Handshake is received [Transport 12.4]"             $ \_ -> do                 let cc = addHook cc0 $ setOnPlainCreated handshakePathChallenge                 runCnoOp cc ms `shouldThrow` transportError@@ -153,7 +161,7 @@                 let cc = addHook cc0 $ setOnPlainCreated handshakeDataBlocked                 runCnoOp cc ms `shouldThrow` transportError         it-            "MUST send PROTOCOL_VIOLATION if reserved bits in Short are non-zero [Transport 17.2]"+            "MUST send PROTOCOL_VIOLATION if reserved bits in Short are non-zero [Transport 17.3.1]"             $ \_ -> do                 let cc = addHook cc0 $ setOnPlainCreated $ rrBits RTT1Level                 runCnoOp cc ms `shouldThrow` transportError@@ -232,15 +240,15 @@                 let cc = cc0{ccALPN = \_ -> return $ Just ["dummy"]}                 runCnoOp cc ms `shouldThrow` cryptoErrorsIn [TLS.NoApplicationProtocol]         it-            "MUST send missing_extension TLS alert if the quic_transport_parameters extension does not included [TLS 8.2]"+            "MUST send missing_extension TLS alert if the quic_transport_parameters extension is not included [TLS 8.2]"             $ \_ -> do                 let cc = addHook cc0 $ setOnTLSExtensionCreated (const [])                 runCnoOp cc ms `shouldThrow` cryptoErrorsIn [TLS.MissingExtension]         it-            "MUST send missing_extension TLS alert if the quic_transport_parameters extension does not included [TLS 8.2]"+            "MUST send missing_extension TLS alert if only the draft quic_transport_parameters extension (0xffa5) is included [TLS 8.2]"             $ \_ -> do                 let f [ExtensionRaw _ v] = [ExtensionRaw (ExtensionID 0xffa5) v]-                    f _ = error "f"+                    f xs = xs                     cc = addHook cc0 $ setOnTLSExtensionCreated f                 runCnoOp cc ms `shouldThrow` cryptoErrorsIn [TLS.MissingExtension]         it@@ -261,9 +269,8 @@                                     }                         runCnoOp cc ms `shouldThrow` transportError                 _ -> do-                    putStrLn-                        "Warning: 0-RTT is not possible. Skipping this test. Use \"h3spec -s 0-RTT\" next time."                     when (ccDebugLog cc0) $ print mres+                    pendingWith "0-RTT is not possible. Use \"h3spec -s 0-RTT\" next time."  ---------------------------------------------------------------- @@ -379,14 +386,14 @@ impossibleAckRange :: EncryptionLevel -> Plain -> Plain impossibleAckRange lvl plain     | lvl == RTT1Level =-        plain{plainFrames = Ack (AckInfo 5 0 [(10, 0)]) 0 : plainFrames plain}+        plain{plainFrames = Ack (AckInfo 5 0 [(10, 0)]) noDelay : plainFrames plain}     | otherwise = plain  -- Nobody has sent a million packets down this connection. ackForUnsentPacket :: EncryptionLevel -> Plain -> Plain ackForUnsentPacket lvl plain     | lvl == RTT1Level =-        plain{plainFrames = Ack (AckInfo 1000000 0 []) 0 : plainFrames plain}+        plain{plainFrames = Ack (AckInfo 1000000 0 []) noDelay : plainFrames plain}     | otherwise = plain  unknownFrame :: EncryptionLevel -> Plain -> Plain@@ -395,6 +402,19 @@         plain{plainFrames = UnknownFrame 0x20 : plainFrames plain}     | otherwise = plain +-- A CRYPTO frame at offset 0 whose length is an eight-octet integer that+-- starts 0x3f: whatever follows, it runs past the end of the packet.  Put+-- together an octet at a time, since no frame this library builds is cut+-- short.+truncatedFrame :: EncryptionLevel -> Plain -> Plain+truncatedFrame lvl plain+    | lvl == RTT1Level =+        plain+            { plainFrames =+                plainFrames plain ++ [UnknownFrame 0x06, UnknownFrame 0x00, UnknownFrame 0xff]+            }+    | otherwise = plain+ -- CRYPTO frames are outside flow control, so nothing but the buffer limit -- stops a peer from parking a fragment far past where the stream has got to -- and having it held.  One octet at this offset is enough to ask for more@@ -562,6 +582,10 @@ -- it detects an error condition; a generic error code (such as -- PROTOCOL_VIOLATION or INTERNAL_ERROR) can always be used in place -- of specific error codes.+frameEncodingError :: QUICException -> Bool+frameEncodingError (TransportErrorIsReceived FrameEncodingError _) = True+frameEncodingError _ = False+ transportErrorsIn :: [TransportError] -> QUICException -> Bool transportErrorsIn tes qe@(TransportErrorIsReceived te _) = (te `elem` tes) || transportError qe transportErrorsIn _ _ = False
h3spec.cabal view
@@ -1,5 +1,5 @@ name:                h3spec-version:             0.1.14+version:             0.1.15 synopsis:            QUIC description:         Test tool for error cases of QUIC and HTTP/3 license:             BSD3@@ -26,8 +26,7 @@                       , hspec                       , hspec-core                       , http-types-                      , http3 >= 0.1.7 && < 0.2-                      , network-                      , quic >= 0.3.13 && < 0.4-                      , tls+                      , http3 >= 0.1.10 && < 0.2+                      , quic >= 0.3.20 && < 0.4+                      , tls >= 2.3 && < 2.6   default-extensions:   Strict StrictData
h3spec.hs view
@@ -3,7 +3,8 @@ module Main where  import Control.Monad (when)-import Data.List (foldl', intersperse)+import qualified Data.List as L+import Data.Maybe (fromMaybe) import Data.Version (showVersion) import qualified Network.HTTP3.Client as H3 import Network.QUIC.Internal@@ -11,6 +12,7 @@ import System.Environment (getArgs, withArgs) import System.Exit (exitFailure, exitSuccess) import qualified Test.Hspec.Core.Runner as H+import Text.Read (readMaybe)  import HTTP3Error import qualified Paths_h3spec as P@@ -38,7 +40,7 @@         , optSkip = []         , optQLogDir = Nothing         , optKeyLogFile = Nothing-        , optTimeout = 2000 -- 2 milliseconds+        , optTimeout = 2000 -- 2 seconds         }  options :: [OptDescr (Options -> Options)]@@ -76,7 +78,7 @@     , Option         ['t']         ["timeout"]-        (ReqArg (\ms o -> o{optTimeout = read ms}) "<milliseconds>")+        (ReqArg setTimeout "<milliseconds>")         "timeout for each test case (2000)"     , Option         ['n']@@ -85,6 +87,10 @@         "no validating server certificates"     ] +-- An invalid value is reported by main after getOpt.+setTimeout :: String -> Options -> Options+setTimeout ms o = o{optTimeout = fromMaybe (-1) $ readMaybe ms}+ showUsageAndExit :: String -> IO a showUsageAndExit msg = do     putStrLn msg@@ -98,8 +104,10 @@ main = do     args0 <- getArgs     (opts, args) <- case getOpt Permute options args0 of-        (o, n, []) -> return (foldl' (flip id) defaultOptions o, n)+        (o, n, []) -> return (L.foldl' (flip id) defaultOptions o, n)         (_, _, errs) -> showUsageAndExit $ concat errs+    when (optTimeout opts <= 0) $+        showUsageAndExit "timeout must be a positive integer\n"     when (optVersion opts) $ do         putStrLn $ "h3spec " ++ showVersion P.version         exitSuccess@@ -119,15 +127,16 @@         qcArgs0             | null (optMatch opts) = []             | otherwise =-                "--match" : (intersperse "--match" $ reverse $ optMatch opts)+                "--match" : (L.intersperse "--match" $ reverse $ optMatch opts)         qcArgs             | null (optSkip opts) = qcArgs0             | otherwise =-                "--skip" : (intersperse "--skip" $ reverse $ optSkip opts)+                qcArgs0 ++ "--skip" : (L.intersperse "--skip" $ reverse $ optSkip opts)         h3cc = H3.defaultClientConfig{H3.authority = host}         ms = optTimeout opts     H.readConfig H.defaultConfig qcArgs-        >>= withArgs [] . H.runSpec (transportErrorSpec cc ms >> h3ErrorSpec cc h3cc ms (const Nothing))+        >>= withArgs []+            . H.runSpec (transportErrorSpec cc ms >> h3ErrorSpec cc h3cc ms (const Nothing))         >>= H.evaluateSummary  getLogger :: Maybe FilePath -> (String -> IO ())