packages feed

http3-0.1.12: test/HTTP3/LimitSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module HTTP3.LimitSpec where

import qualified Control.Exception as E
import qualified Data.ByteString as B
import Data.Bits (shiftR, (.&.), (.|.))
import Network.HTTP.Types
import qualified Network.HTTP3.Client as C
import Network.HTTP3.Internal (H3Frame (..), H3FrameType (..))
import Network.HTTP3.Server
import Network.QPACK (QDecoderConfig (..))
import qualified Network.QUIC.Client as QUIC
import Test.Hspec

import HTTP3.Config
import HTTP3.Server

-- | A server that keeps to its SETTINGS_MAX_FIELD_SECTION_SIZE without
-- announcing it.
--
-- Our client keeps to the limit a server announces and sends nothing over
-- it, so a server that announces its limit never sees a section over it from
-- us.  Such a section has to be sent all the same to see what the server
-- does with it, and a client that has not been told of a limit sends it.
spec :: Spec
spec = do
    describe "H3 server keeping to a limit it does not announce" $ do
        it "answers 431 to a request header section over its limit" $
            E.bracket (setupWith hideLimit server 4096) teardown $
                \_ -> runTooLargeClient
        it "answers 431 to a HEADERS frame longer than its limit" $
            E.bracket (setupWith hideLimit server 4096) teardown $
                \_ -> runTooLongFrameClient
    describe "H3 server with a small field section limit" $
        it "takes SETTINGS longer than that limit" $
            E.bracket (setupWith smallLimit server 4096) teardown $
                \_ -> runLargeSettingsClient

-- | A server taking field sections of 300 octets at most, and a client whose
-- SETTINGS come to 450 octets or so.  The cap on control frames was the
-- field section limit, and the server closed the connection with
-- H3_EXCESSIVE_LOAD.
runLargeSettingsClient :: IO ()
runLargeSettingsClient = QUIC.run testClientConfig $ \conn ->
    E.bracket allocSimpleConfig freeSimpleConfig $ \conf0 -> do
        let conf =
                conf0
                    { confHooks =
                        (confHooks conf0){onControlFrameCreated = map grease}
                    }
        C.run conn testH3ClientConfig conf $ \sendRequest _aux -> do
            let hello =
                    sendRequest (C.requestNoBody methodGet "/" []) $ \rsp ->
                        C.responseStatus rsp `shouldBe` Just ok200
            -- The first is answered before the server reads our SETTINGS.
            hello
            waitForSettings
            hello
  where
    -- 150 reserved settings (RFC 9114, section 7.2.4.1), of two-octet IDs
    -- and the value 0.
    grease (H3Frame H3FrameSettings payload) =
        H3Frame H3FrameSettings $ payload <> B.concat (map reserved [0 .. 149])
    grease frame = frame
    reserved :: Int -> B.ByteString
    reserved n =
        let i = 0x1f * n + 0x21
         in B.pack [fromIntegral (0x40 .|. (i `shiftR` 8)), fromIntegral (i .&. 0xff), 0]

smallLimit :: Config -> Config
smallLimit conf =
    conf
        { confQDecoderConfig =
            (confQDecoderConfig conf){dcMaxFieldSectionSize = 300}
        }

-- | 150 copies of one field with a 300-octet value.  After the first two,
-- each is a reference to the dynamic table of an octet or so, so the HEADERS
-- frame comes to around 600 octets, well within the cap on its length.  As
-- the limit counts them, though, they are 335 octets each, 50250 in all
-- against the server's 32768.
runTooLargeClient :: IO ()
runTooLargeClient = QUIC.run testClientConfig $ \conn ->
    E.bracket allocSimpleConfig freeSimpleConfig $ \conf ->
        C.run conn testH3ClientConfig conf $ \sendRequest _aux -> do
            -- One round trip first, so that the server's SETTINGS are in and
            -- the encoder may use the dynamic table.  Without it every copy
            -- goes as a literal and the frame is over the cap.
            sendRequest (C.requestNoBody methodGet "/" []) $ \_ -> return ()
            waitForSettings
            let hdr = replicate 150 ("x-a", B.replicate 300 0x62)
                req = C.requestNoBody methodGet "/" hdr
            sendRequest req $ \rsp ->
                C.responseStatus rsp `shouldBe` Just requestHeaderFieldsTooLarge431

-- | One field with a 40000-octet value of "~", which Huffman makes no shorter: the
-- HEADERS frame itself is longer than the server's 32768.  The section was
-- refused with the 431 above only once decoded, and a frame this long closed
-- the whole connection with H3_EXCESSIVE_LOAD instead.
runTooLongFrameClient :: IO ()
runTooLongFrameClient = QUIC.run testClientConfig $ \conn ->
    E.bracket allocSimpleConfig freeSimpleConfig $ \conf ->
        C.run conn testH3ClientConfig conf $ \sendRequest _aux -> do
            let req = C.requestNoBody methodGet "/" [("x-a", B.replicate 40000 0x7e)]
            sendRequest req $ \rsp ->
                C.responseStatus rsp `shouldBe` Just requestHeaderFieldsTooLarge431