http2-5.4.1: Network/HTTP2/H2/Settings.hs
module Network.HTTP2.H2.Settings where
import Network.Control
import Imports
import Network.HTTP2.Frame
import Network.HTTP2.H2.EncodeFrame
----------------------------------------------------------------
-- | HTTP\/2 settings. See <https://datatracker.ietf.org/doc/html/rfc9113#name-defined-settings>.
data Settings = Settings
{ headerTableSize :: Int
-- ^ SETTINGS_HEADER_TABLE_SIZE
, enablePush :: Bool
-- ^ SETTINGS_ENABLE_PUSH
, maxConcurrentStreams :: Maybe Int
-- ^ SETTINGS_MAX_CONCURRENT_STREAMS
, initialWindowSize :: WindowSize
-- ^ SETTINGS_INITIAL_WINDOW_SIZE
, maxFrameSize :: Int
-- ^ SETTINGS_MAX_FRAME_SIZE
, maxHeaderListSize :: Maybe Int
-- ^ SETTINGS_MAX_HEADER_LIST_SIZE
, pingRateLimit :: Int
-- ^ Maximum number of pings allowed per second (CVE-2019-9512)
, emptyFrameRateLimit :: Int
-- ^ Maximum number of empty data frames allowed per second (CVE-2019-9518)
, settingsRateLimit :: Int
-- ^ Maximum number of settings frames allowed per second (CVE-2019-9515)
, rstRateLimit :: Int
-- ^ Maximum number of reset frames allowed per second (CVE-2023-44487)
}
deriving (Eq, Show)
-- | The default settings.
--
-- >>> baseSettings
-- Settings {headerTableSize = 4096, enablePush = True, maxConcurrentStreams = Nothing, initialWindowSize = 65535, maxFrameSize = 16384, maxHeaderListSize = Nothing, pingRateLimit = 10, emptyFrameRateLimit = 4, settingsRateLimit = 4, rstRateLimit = 4}
baseSettings :: Settings
baseSettings =
Settings
{ headerTableSize = 4096 -- defaultDynamicTableSize
, enablePush = True
, maxConcurrentStreams = Nothing
, initialWindowSize = defaultWindowSize -- 64K (65,535)
, maxFrameSize = defaultPayloadLength -- 2^14 (16,384)
, maxHeaderListSize = Nothing
, pingRateLimit = 10
, emptyFrameRateLimit = 4
, settingsRateLimit = 4
, rstRateLimit = 4
}
-- | The default settings.
--
-- >>> defaultSettings
-- Settings {headerTableSize = 4096, enablePush = True, maxConcurrentStreams = Just 64, initialWindowSize = 262144, maxFrameSize = 16384, maxHeaderListSize = Nothing, pingRateLimit = 10, emptyFrameRateLimit = 4, settingsRateLimit = 4, rstRateLimit = 4}
defaultSettings :: Settings
defaultSettings =
baseSettings
{ maxConcurrentStreams = Just defaultMaxStreams
, initialWindowSize = defaultMaxStreamData
}
----------------------------------------------------------------
-- | Updating settings.
--
-- >>> fromSettingsList defaultSettings [(SettingsEnablePush,0),(SettingsMaxHeaderListSize,200)]
-- Settings {headerTableSize = 4096, enablePush = False, maxConcurrentStreams = Just 64, initialWindowSize = 262144, maxFrameSize = 16384, maxHeaderListSize = Just 200, pingRateLimit = 10, emptyFrameRateLimit = 4, settingsRateLimit = 4, rstRateLimit = 4}
{- FOURMOLU_DISABLE -}
fromSettingsList :: Settings -> SettingsList -> Settings
fromSettingsList settings kvs = foldl' update settings kvs
where
update def (SettingsTokenHeaderTableSize,x) = def { headerTableSize = x }
-- fixme: x should be 0 or 1
update def (SettingsEnablePush,x) = def { enablePush = x > 0 }
update def (SettingsMaxConcurrentStreams,x) = def { maxConcurrentStreams = Just x }
update def (SettingsInitialWindowSize,x) = def { initialWindowSize = x }
update def (SettingsMaxFrameSize,x) = def { maxFrameSize = x }
update def (SettingsMaxHeaderListSize,x) = def { maxHeaderListSize = Just x }
update def _ = def
{- FOURMOLU_ENABLE -}
----------------------------------------------------------------
diff
:: Eq a
=> Settings
-> Settings
-> (Settings -> a)
-> SettingsKey
-> (a -> SettingsValue)
-> Maybe (SettingsKey, SettingsValue)
diff settings settings0 label key enc
| val == val0 = Nothing
| otherwise = Just (key, enc val)
where
val = label settings
val0 = label settings0
toSettingsList :: Settings -> Settings -> SettingsList
toSettingsList s s0 =
catMaybes
[ diff
s
s0
headerTableSize
SettingsTokenHeaderTableSize
id
, diff
s
s0
enablePush
SettingsEnablePush
(const 0) -- fixme
, diff
s
s0
maxConcurrentStreams
SettingsMaxConcurrentStreams
fromJust
, diff
s
s0
initialWindowSize
SettingsInitialWindowSize
id
, diff
s
s0
maxFrameSize
SettingsMaxFrameSize
id
, diff
s
s0
maxHeaderListSize
SettingsMaxHeaderListSize
fromJust
]
----------------------------------------------------------------
makeNegotiationFrames :: Settings -> WindowSize -> [ByteString]
makeNegotiationFrames settings connWindowSize = frame1 : frames
where
alist = toSettingsList settings baseSettings
frame1 = settingsFrame id alist
frames =
if connWindowSize /= defaultWindowSize
then [windowUpdateFrame 0 (connWindowSize - defaultWindowSize)]
else []
----------------------------------------------------------------