-- | Settings and parameters pertaining to HTTP\/2
--
-- Intended for unqualified import.

module Network.GRPC.Common.HTTP2Settings
  ( HTTP2Settings(..)
  , defaultHTTP2Settings
  ) where

import Data.Default
import Data.Word

-- | HTTP\/2 settings
data HTTP2Settings = HTTP2Settings {
      -- | Maximum number of concurrent active streams
      --
      -- <https://datatracker.ietf.org/doc/html/rfc7540#section-5.1.2>
      HTTP2Settings -> Word32
http2MaxConcurrentStreams :: Word32

      -- | Window size for streams
      --
      -- <https://datatracker.ietf.org/doc/html/rfc7540#section-6.9.2>
    , HTTP2Settings -> Word32
http2StreamWindowSize :: Word32

      -- | Connection window size
      --
      -- This value is broadcast via a @WINDOW_UDPATE@ frame at the beginning of
      -- a new connection.
      --
      -- If the consumed window space of all streams exceeds this value, the
      -- sender will stop sending data. Therefore, if this value is less than
      -- @'http2MaxConcurrentStreams' * 'http2StreamWindowSize'@, there is risk
      -- of a control flow deadlock, since the connection window space may be
      -- used up by streams that we are not yet processing before we have
      -- received all data on the streams that we /are/ processing. To reduce
      -- this risk, increase
      -- 'Network.GRPC.Server.Run.serverOverrideNumberOfWorkers' for the server.
      -- See <https://github.com/kazu-yamamoto/network-control/pull/4> for more
      -- information.
    , HTTP2Settings -> Word32
http2ConnectionWindowSize :: Word32

      -- | Enable @TCP_NODELAY@
      --
      -- Send out TCP segments as soon as possible, even if there is only a
      -- small amount of data.
      --
      -- When @TCP_NODELAY@ is /NOT/ set, the TCP implementation will wait to
      -- send a TCP segment to the receiving peer until either (1) there is
      -- enough data to fill a certain minimum segment size or (2) we receive an
      -- ACK from the receiving peer for data we sent previously. This adds a
      -- network roundtrip delay to every RPC message we want to send (to
      -- receive the ACK). If the peer uses TCP delayed acknowledgement, which
      -- will typically be the case, then this delay will increase further
      -- still; default for delayed acknowledgement is 40ms, thus resulting in a
      -- theoretical maximum of 25 RPCs/sec.
      --
      -- We therefore enable TCP_NODELAY by default, so that data is sent to the
      -- peer as soon as we have an entire gRPC message serialized and ready to
      -- send (we send the data to the TCP layer only once an entire message is
      -- written, or the @http2@ write buffer is full).
      --
      -- Turning this off /could/ improve throughput, as fewer TCP segments will
      -- be needed, but you probably only want to do this if you send very few
      -- very large RPC messages. In gRPC this is anyway discouraged, because
      -- gRPC messages do not support incremental (de)serialization; if you need
      -- to send large amounts of data, it is preferable to split these into
      -- many, smaller, gRPC messages; this also gives the application the
      -- possibility of reporting on data transmission progress.
      --
      -- TL;DR: leave this at the default unless you know what you are doing.
    , HTTP2Settings -> Bool
http2TcpNoDelay :: Bool

      -- | Set @SO_LINGER@ to a value of 0
      --
      -- Instead of following the normal shutdown sequence to close the TCP
      -- connection, this will just send a @RST@ packet and immediately discard
      -- the connection, freeing the local port.
      --
      -- This should /not/ be enabled in the vast majority of cases. It is only
      -- useful in specific scenarios, such as stress testing, where resource
      -- (e.g. port) exhaustion is a greater concern than protocol adherence.
      -- Even in such scenarios scenarios, it probably only makes sense to
      -- enable this option on the client since they will be using a new
      -- ephemeral port for each connection (unlike the server).
      --
      -- TL;DR: leave this at the default unless you know what you are doing.
    , HTTP2Settings -> Bool
http2TcpAbortiveClose :: Bool

      -- | Ping rate limit
      --
      -- This setting is specific to the [@http2@
      -- package's](https://hackage.haskell.org/package/http2) implementation of
      -- the HTTP\/2 specification. In particular, the library imposes a ping
      -- rate limit as a security measure against
      -- [CVE-2019-9512](https://www.cve.org/CVERecord?id=CVE-2019-9512). By
      -- default (as of version 5.1.2) it sets this limit at 10 pings/second. If
      -- you find yourself being disconnected from a gRPC peer because that peer
      -- is sending too many pings (you will see an
      -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode)
      -- exception, corresponding to the
      -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes)
      -- HTTP\/2 error code), you may wish to increase this limit. If you are
      -- connecting to a peer that you trust, you can set this limit to
      -- 'maxBound' (effectively turning off protection against ping flooding).
    , HTTP2Settings -> Maybe Int
http2OverridePingRateLimit :: Maybe Int

      -- | Empty DATA frame rate limit
      --
      -- This setting is specific to the [@http2@
      -- package's](https://hackage.haskell.org/package/http2) implementation of
      -- the HTTP\/2 specification. In particular, the library imposes a rate
      -- limit for empty DATA frames as a security measure against
      -- [CVE-2019-9518](https://www.cve.org/CVERecord?id=CVE-2019-9518). By
      -- default, it sets this limit at 4 frames/second. If you find yourself
      -- being disconnected from a gRPC peer because that peer is sending too
      -- many empty DATA frames (you will see an
      -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode)
      -- exception, corresponding to the
      -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes)
      -- HTTP\/2 error code), you may wish to increase this limit. If you are
      -- connecting to a peer that you trust, you can set this limit to
      -- 'maxBound' (effectively turning off protection against empty DATA frame
      -- flooding).
    , HTTP2Settings -> Maybe Int
http2OverrideEmptyFrameRateLimit :: Maybe Int

      -- | SETTINGS frame rate limit
      --
      -- This setting is specific to the [@http2@
      -- package's](https://hackage.haskell.org/package/http2) implementation of
      -- the HTTP\/2 specification. In particular, the library imposes a rate
      -- limit for SETTINGS frames as a security measure against
      -- [CVE-2019-9515](https://www.cve.org/CVERecord?id=CVE-2019-9515). By
      -- default, it sets this limit at 4 frames/second. If you find yourself
      -- being disconnected from a gRPC peer because that peer is sending too
      -- many SETTINGS frames (you will see an
      -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode)
      -- exception, corresponding to the
      -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes)
      -- HTTP\/2 error code), you may wish to increase this limit. If you are
      -- connecting to a peer that you trust, you can set this limit to
      -- 'maxBound' (effectively turning off protection against SETTINGS frame
      -- flooding).
    , HTTP2Settings -> Maybe Int
http2OverrideSettingsRateLimit :: Maybe Int

      -- | Reset (RST) frame rate limit
      --
      -- This setting is specific to the [@http2@
      -- package's](https://hackage.haskell.org/package/http2) implementation of
      -- the HTTP\/2 specification. In particular, the library imposes a rate
      -- limit for RST frames as a security measure against
      -- [CVE-2023-44487](https://www.cve.org/CVERecord?id=CVE-2023-44487). By
      -- default, it sets this limit at 4 frames/second. If you find yourself
      -- being disconnected from a gRPC peer because that peer is sending too
      -- many empty RST frames (you will see an
      -- [EnhanceYourCalm](https://hackage.haskell.org/package/http2-5.1.2/docs/Network-HTTP2-Client.html#t:ErrorCode)
      -- exception, corresponding to the
      -- [ENHANCE_YOUR_CALM](https://www.rfc-editor.org/rfc/rfc9113#ErrorCodes)
      -- HTTP\/2 error code), you may wish to increase this limit. If you are
      -- connecting to a peer that you trust, you can set this limit to
      -- 'maxBound' (effectively turning off protection against RST frame
      -- flooding).
    , HTTP2Settings -> Maybe Int
http2OverrideRstRateLimit :: Maybe Int
    }
  deriving (Int -> HTTP2Settings -> ShowS
[HTTP2Settings] -> ShowS
HTTP2Settings -> String
(Int -> HTTP2Settings -> ShowS)
-> (HTTP2Settings -> String)
-> ([HTTP2Settings] -> ShowS)
-> Show HTTP2Settings
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> HTTP2Settings -> ShowS
showsPrec :: Int -> HTTP2Settings -> ShowS
$cshow :: HTTP2Settings -> String
show :: HTTP2Settings -> String
$cshowList :: [HTTP2Settings] -> ShowS
showList :: [HTTP2Settings] -> ShowS
Show)

-- | Default HTTP\/2 settings
--
-- [Section 6.5.2 of the HTTP\/2
-- specification](https://datatracker.ietf.org/doc/html/rfc7540#section-6.5.2)
-- recommends that the @SETTINGS_MAX_CONCURRENT_STREAMS@ parameter be no smaller
-- than 100 "so as not to unnecessarily limit parallelism", so we default to
-- 128.
--
-- The default initial stream window size (corresponding to the
-- @SETTINGS_INITIAL_WINDOW_SIZE@ HTTP\/2 parameter) is 64KB.
--
-- The default connection window size is 128 * 64KB to avoid the control flow
-- deadlock discussed at 'http2ConnectionWindowSize'.
--
-- The ping rate limit imposed by the [@http2@
-- package](https://hackage.haskell.org/package/http2) is overridden to 100
-- PINGs/sec.
defaultHTTP2Settings :: HTTP2Settings
defaultHTTP2Settings :: HTTP2Settings
defaultHTTP2Settings = HTTP2Settings {
      http2MaxConcurrentStreams :: Word32
http2MaxConcurrentStreams        = Word32
defMaxConcurrentStreams
    , http2StreamWindowSize :: Word32
http2StreamWindowSize            = Word32
defInitialStreamWindowSize
    , http2ConnectionWindowSize :: Word32
http2ConnectionWindowSize        = Word32
defInitialConnectionWindowSize
    , http2TcpAbortiveClose :: Bool
http2TcpAbortiveClose            = Bool
False
    , http2TcpNoDelay :: Bool
http2TcpNoDelay                  = Bool
True
    , http2OverridePingRateLimit :: Maybe Int
http2OverridePingRateLimit       = Int -> Maybe Int
forall a. a -> Maybe a
Just Int
100
    , http2OverrideEmptyFrameRateLimit :: Maybe Int
http2OverrideEmptyFrameRateLimit = Maybe Int
forall a. Maybe a
Nothing
    , http2OverrideSettingsRateLimit :: Maybe Int
http2OverrideSettingsRateLimit   = Maybe Int
forall a. Maybe a
Nothing
    , http2OverrideRstRateLimit :: Maybe Int
http2OverrideRstRateLimit        = Maybe Int
forall a. Maybe a
Nothing
    }
  where
    defMaxConcurrentStreams :: Word32
defMaxConcurrentStreams        = Word32
128
    defInitialStreamWindowSize :: Word32
defInitialStreamWindowSize     = Word32
256 Word32 -> Word32 -> Word32
forall a. Num a => a -> a -> a
* Word32
1024        -- 256KiB
    defInitialConnectionWindowSize :: Word32
defInitialConnectionWindowSize = Word32
2   Word32 -> Word32 -> Word32
forall a. Num a => a -> a -> a
* Word32
1024 Word32 -> Word32 -> Word32
forall a. Num a => a -> a -> a
* Word32
1024 -- 2MiB

instance Default HTTP2Settings where
  def :: HTTP2Settings
def = HTTP2Settings
defaultHTTP2Settings