diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -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.
diff --git a/HTTP3Error.hs b/HTTP3Error.hs
--- a/HTTP3Error.hs
+++ b/HTTP3Error.hs
@@ -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
diff --git a/TransportError.hs b/TransportError.hs
--- a/TransportError.hs
+++ b/TransportError.hs
@@ -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
diff --git a/h3spec.cabal b/h3spec.cabal
--- a/h3spec.cabal
+++ b/h3spec.cabal
@@ -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
diff --git a/h3spec.hs b/h3spec.hs
--- a/h3spec.hs
+++ b/h3spec.hs
@@ -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 ())
