packages feed

quic-0.3.5: Network/QUIC/Parameters.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}

module Network.QUIC.Parameters (
    Parameters (..),
    defaultParameters,
    baseParameters, -- only for Connection
    encodeParameters,
    decodeParameters,
    AuthCIDs (..),
    defaultAuthCIDs,
    setCIDsToParameters,
    getCIDsToParameters,
) where

import qualified Control.Exception as E
import qualified Data.ByteString as BS
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
import qualified Data.ByteString.Short as Short
import Network.Control
import System.IO.Unsafe (unsafeDupablePerformIO)

import Network.QUIC.Imports
import Network.QUIC.Types

encodeParameters :: Parameters -> ByteString
encodeParameters = encodeParameterList . toParameterList

decodeParameters :: ByteString -> Maybe Parameters
decodeParameters bs = decodeParameterList bs >>= fromParameterList

newtype Key = Key Word32 deriving (Eq, Show)
type Value = ByteString

type ParameterList = [(Key, Value)]

{- FOURMOLU_DISABLE -}
pattern OriginalDestinationConnectionId :: Key
pattern OriginalDestinationConnectionId  = Key 0x00
pattern MaxIdleTimeout                  :: Key
pattern MaxIdleTimeout                   = Key 0x01
pattern StateLessResetToken             :: Key
pattern StateLessResetToken              = Key 0x02
pattern MaxUdpPayloadSize               :: Key
pattern MaxUdpPayloadSize                = Key 0x03
pattern InitialMaxData                  :: Key
pattern InitialMaxData                   = Key 0x04
pattern InitialMaxStreamDataBidiLocal   :: Key
pattern InitialMaxStreamDataBidiLocal    = Key 0x05
pattern InitialMaxStreamDataBidiRemote  :: Key
pattern InitialMaxStreamDataBidiRemote   = Key 0x06
pattern InitialMaxStreamDataUni         :: Key
pattern InitialMaxStreamDataUni          = Key 0x07
pattern InitialMaxStreamsBidi           :: Key
pattern InitialMaxStreamsBidi            = Key 0x08
pattern InitialMaxStreamsUni            :: Key
pattern InitialMaxStreamsUni             = Key 0x09
pattern AckDelayExponent                :: Key
pattern AckDelayExponent                 = Key 0x0a
pattern MaxAckDelay                     :: Key
pattern MaxAckDelay                      = Key 0x0b
pattern DisableActiveMigration          :: Key
pattern DisableActiveMigration           = Key 0x0c
pattern PreferredAddress                :: Key
pattern PreferredAddress                 = Key 0x0d
pattern ActiveConnectionIdLimit         :: Key
pattern ActiveConnectionIdLimit          = Key 0x0e
pattern InitialSourceConnectionId       :: Key
pattern InitialSourceConnectionId        = Key 0x0f
pattern RetrySourceConnectionId         :: Key
pattern RetrySourceConnectionId          = Key 0x10
pattern VersionInformation              :: Key
pattern VersionInformation               = Key 0x11
pattern MaxDatagramFrameSize            :: Key
pattern MaxDatagramFrameSize             = Key 0x20
pattern Grease                          :: Key
pattern Grease                           = Key 0xff
pattern GreaseQuicBit                   :: Key
pattern GreaseQuicBit                    = Key 0x2ab2
{- FOURMOLU_ENABLE -}

-- | QUIC transport parameters.
data Parameters = Parameters
    { originalDestinationConnectionId :: Maybe CID
    , maxIdleTimeout :: Milliseconds
    , statelessResetToken :: Maybe StatelessResetToken -- 16 bytes
    , maxUdpPayloadSize :: Int
    , initialMaxData :: Int
    , initialMaxStreamDataBidiLocal :: Int
    , initialMaxStreamDataBidiRemote :: Int
    , initialMaxStreamDataUni :: Int
    , initialMaxStreamsBidi :: Int
    , initialMaxStreamsUni :: Int
    , ackDelayExponent :: Int
    , maxAckDelay :: Milliseconds
    , disableActiveMigration :: Bool
    , preferredAddress :: Maybe ByteString -- fixme
    , activeConnectionIdLimit :: Int
    , initialSourceConnectionId :: Maybe CID
    , retrySourceConnectionId :: Maybe CID
    , grease :: Maybe ByteString
    , greaseQuicBit :: Bool
    , versionInformation :: Maybe VersionInfo
    , maxDatagramFrameSize :: Int
    }
    deriving (Eq, Show)

-- | The default value for QUIC transport parameters.
baseParameters :: Parameters
baseParameters =
    Parameters
        { originalDestinationConnectionId = Nothing
        , maxIdleTimeout = Milliseconds 0 -- disabled
        , statelessResetToken = Nothing
        , maxUdpPayloadSize = 65527
        , initialMaxData = 0
        , initialMaxStreamDataBidiLocal = 0
        , initialMaxStreamDataBidiRemote = 0
        , initialMaxStreamDataUni = 0
        , initialMaxStreamsBidi = 0
        , initialMaxStreamsUni = 0
        , ackDelayExponent = 3
        , maxAckDelay = Milliseconds 25
        , disableActiveMigration = False
        , preferredAddress = Nothing
        , activeConnectionIdLimit = 2
        , initialSourceConnectionId = Nothing
        , retrySourceConnectionId = Nothing
        , grease = Nothing
        , greaseQuicBit = False
        , versionInformation = Nothing
        , maxDatagramFrameSize = 0
        }

-- | The value of an integer transport parameter, or 'Nothing' if the octets
--   given are not one.
--
-- RFC 9000 section 18 gives these values as a single variable-length integer,
-- so anything else is malformed: a value too short to hold the integer it
-- announces, an empty one, or one with octets left over behind the integer it
-- does hold.  'decodeInt' answers the first two by reading off the end, which
-- from inside 'unsafeDupablePerformIO' means an exception out of a pure value
-- -- raised wherever the field is first forced, which is nowhere near here.
decInt :: ByteString -> Maybe Int
decInt bs = unsafeDupablePerformIO $
    E.handle (\BufferOverrun -> return Nothing) $
        withReadBuffer bs $ \rbuf -> do
            n <- decodeInt' rbuf
            rest <- remainingSize rbuf
            return $ if rest == 0 then Just (fromIntegral n) else Nothing

encInt :: Int -> ByteString
encInt = encodeInt . fromIntegral

decMilliseconds :: ByteString -> Maybe Milliseconds
decMilliseconds bs = Milliseconds . fromIntegral <$> decInt bs

encMilliseconds :: Milliseconds -> ByteString
encMilliseconds (Milliseconds n) = encodeInt $ fromIntegral n

fromVersionInfo :: Maybe VersionInfo -> Value
fromVersionInfo Nothing = "" -- never reach
fromVersionInfo (Just VersionInfo{..}) = unsafeDupablePerformIO $
    withWriteBuffer len $ \wbuf -> do
        let putVersion (Version ver) = write32 wbuf ver
        putVersion chosenVersion
        mapM_ putVersion otherVersions
  where
    len = 4 * (length otherVersions + 1)

toVersionInfo :: Value -> Maybe VersionInfo
toVersionInfo bs
    | len < 3 || remainder /= 0 = Just brokenVersionInfo
    | otherwise = Just $
        unsafeDupablePerformIO $
            withReadBuffer bs $ \rbuf -> do
                let getVersion = Version <$> read32 rbuf
                VersionInfo <$> getVersion <*> replicateM (cnt - 1) getVersion
  where
    len = BS.length bs
    (cnt, remainder) = len `divMod` 4

-- | 'Nothing' if any parameter's value is malformed, or if any parameter is
--   sent twice.  An unknown /key/ is neither: RFC 9000 section 18.1 says to
--   ignore one.
--
-- Section 7.4.2 on the repetition: \"An endpoint MUST treat receipt of a
-- duplicate transport parameter as a connection error of type
-- TRANSPORT_PARAMETER_ERROR.\"  Being unknown is not an exemption -- an
-- unknown parameter is ignored once, not permitted twice -- so the check is
-- on the key as it arrived, before anything decides whether it means
-- something here.
fromParameterList :: ParameterList -> Maybe Parameters
fromParameterList kvs0 = go IntSet.empty params kvs0
  where
    params = baseParameters
    go :: IntSet -> Parameters -> ParameterList -> Maybe Parameters
    go _ x [] = Just x
    go seen x (kv@(Key k, _) : kvs)
        | key `IntSet.member` seen = Nothing
        | otherwise = do
            x' <- update x kv
            go (IntSet.insert key seen) x' kvs
      where
        key = fromIntegral k
    update x (OriginalDestinationConnectionId, v) =
        Just x{originalDestinationConnectionId = Just (toCID v)}
    update x (MaxIdleTimeout, v) =
        (\n -> x{maxIdleTimeout = n}) <$> decMilliseconds v
    update x (StateLessResetToken, v) =
        Just x{statelessResetToken = Just (StatelessResetToken $ Short.toShort v)}
    update x (MaxUdpPayloadSize, v) =
        (\n -> x{maxUdpPayloadSize = n}) <$> decInt v
    update x (InitialMaxData, v) =
        (\n -> x{initialMaxData = n}) <$> decInt v
    update x (InitialMaxStreamDataBidiLocal, v) =
        (\n -> x{initialMaxStreamDataBidiLocal = n}) <$> decInt v
    update x (InitialMaxStreamDataBidiRemote, v) =
        (\n -> x{initialMaxStreamDataBidiRemote = n}) <$> decInt v
    update x (InitialMaxStreamDataUni, v) =
        (\n -> x{initialMaxStreamDataUni = n}) <$> decInt v
    update x (InitialMaxStreamsBidi, v) =
        (\n -> x{initialMaxStreamsBidi = n}) <$> decInt v
    update x (InitialMaxStreamsUni, v) =
        (\n -> x{initialMaxStreamsUni = n}) <$> decInt v
    update x (AckDelayExponent, v) =
        (\n -> x{ackDelayExponent = n}) <$> decInt v
    update x (MaxAckDelay, v) =
        (\n -> x{maxAckDelay = n}) <$> decMilliseconds v
    update x (DisableActiveMigration, _) =
        Just x{disableActiveMigration = True}
    update x (PreferredAddress, v) =
        Just x{preferredAddress = Just v}
    update x (ActiveConnectionIdLimit, v) =
        (\n -> x{activeConnectionIdLimit = n}) <$> decInt v
    update x (InitialSourceConnectionId, v) =
        Just x{initialSourceConnectionId = Just (toCID v)}
    update x (RetrySourceConnectionId, v) =
        Just x{retrySourceConnectionId = Just (toCID v)}
    update x (Grease, v) =
        Just x{grease = Just v}
    update x (GreaseQuicBit, _) =
        Just x{greaseQuicBit = True}
    update x (VersionInformation, v) =
        Just x{versionInformation = toVersionInfo v}
    update x (MaxDatagramFrameSize, v) =
        (\n -> x{maxDatagramFrameSize = n}) <$> decInt v
    update x _ = Just x

diff
    :: Eq a
    => Parameters
    -> (Parameters -> a)
    -> Key
    -> (a -> Value)
    -> Maybe (Key, Value)
diff params label key enc
    | val == val0 = Nothing
    | otherwise = Just (key, enc val)
  where
    val = label params
    val0 = label baseParameters

toParameterList :: Parameters -> ParameterList
toParameterList p =
    catMaybes
        [ diff
            p
            originalDestinationConnectionId
            OriginalDestinationConnectionId
            (fromCID . fromJust)
        , diff p maxIdleTimeout MaxIdleTimeout encMilliseconds
        , diff p statelessResetToken StateLessResetToken encSRT
        , diff p maxUdpPayloadSize MaxUdpPayloadSize encInt
        , diff p initialMaxData InitialMaxData encInt
        , diff p initialMaxStreamDataBidiLocal InitialMaxStreamDataBidiLocal encInt
        , diff p initialMaxStreamDataBidiRemote InitialMaxStreamDataBidiRemote encInt
        , diff p initialMaxStreamDataUni InitialMaxStreamDataUni encInt
        , diff p initialMaxStreamsBidi InitialMaxStreamsBidi encInt
        , diff p initialMaxStreamsUni InitialMaxStreamsUni encInt
        , diff p ackDelayExponent AckDelayExponent encInt
        , diff p maxAckDelay MaxAckDelay encMilliseconds
        , diff p disableActiveMigration DisableActiveMigration (const "")
        , diff p preferredAddress PreferredAddress fromJust
        , diff p activeConnectionIdLimit ActiveConnectionIdLimit encInt
        , diff
            p
            initialSourceConnectionId
            InitialSourceConnectionId
            (fromCID . fromJust)
        , diff
            p
            retrySourceConnectionId
            RetrySourceConnectionId
            (fromCID . fromJust)
        , diff p greaseQuicBit GreaseQuicBit (const "")
        , diff p grease Grease fromJust
        , diff p versionInformation VersionInformation fromVersionInfo
        , diff p maxDatagramFrameSize MaxDatagramFrameSize encInt
        ]

encSRT :: Maybe StatelessResetToken -> ByteString
encSRT (Just (StatelessResetToken srt)) = Short.fromShort srt
encSRT _ = error "encSRT"

encodeParameterList :: ParameterList -> ByteString
encodeParameterList kvs = unsafeDupablePerformIO $
    withWriteBuffer 4096 $ \wbuf -> do
        -- for grease
        mapM_ (put wbuf) kvs
  where
    put wbuf (Key k, v) = do
        encodeInt' wbuf $ fromIntegral k
        encodeInt' wbuf $ fromIntegral $ BS.length v
        copyByteString wbuf v

-- | The transport parameters a peer sent, or 'Nothing' if they are not a
--   whole list.  A key, a length and that many octets, repeated until the
--   octets run out; anything that stops in the middle of one of those reads
--   off the end.
decodeParameterList :: ByteString -> Maybe ParameterList
decodeParameterList bs =
    unsafeDupablePerformIO $
        E.handle (\BufferOverrun -> return Nothing) $
            withReadBuffer bs (`go` id)
  where
    go rbuf build = do
        rest1 <- remainingSize rbuf
        if rest1 == 0
            then return $ Just (build [])
            else do
                key <- fromIntegral <$> decodeInt' rbuf
                len <- fromIntegral <$> decodeInt' rbuf
                val <- extractByteString rbuf len
                go rbuf (build . ((Key key, val) :))

-- | An example parameters obsoleted in the near future.
--
-- >>> defaultParameters
-- Parameters {originalDestinationConnectionId = Nothing, maxIdleTimeout = 30000, statelessResetToken = Nothing, maxUdpPayloadSize = 2048, initialMaxData = 16777216, initialMaxStreamDataBidiLocal = 262144, initialMaxStreamDataBidiRemote = 262144, initialMaxStreamDataUni = 262144, initialMaxStreamsBidi = 64, initialMaxStreamsUni = 3, ackDelayExponent = 3, maxAckDelay = 25, disableActiveMigration = False, preferredAddress = Nothing, activeConnectionIdLimit = 5, initialSourceConnectionId = Nothing, retrySourceConnectionId = Nothing, grease = Nothing, greaseQuicBit = True, versionInformation = Nothing, maxDatagramFrameSize = 0}
defaultParameters :: Parameters
defaultParameters =
    baseParameters
        { maxIdleTimeout = idleTimeout -- 30000
        , maxUdpPayloadSize = maximumUdpPayloadSize -- 2048
        , initialMaxData = defaultMaxData -- !M
        , initialMaxStreamDataBidiLocal = defaultMaxStreamData -- 256K
        , initialMaxStreamDataBidiRemote = defaultMaxStreamData -- 256K
        , initialMaxStreamDataUni = defaultMaxStreamData -- 256K
        , initialMaxStreamsBidi = defaultMaxStreams -- 64
        , initialMaxStreamsUni = 3
        , activeConnectionIdLimit = 5
        , greaseQuicBit = True
        , maxDatagramFrameSize = 0
        }

data AuthCIDs = AuthCIDs
    { initSrcCID :: Maybe CID
    , origDstCID :: Maybe CID
    , retrySrcCID :: Maybe CID
    }
    deriving (Eq, Show)

defaultAuthCIDs :: AuthCIDs
defaultAuthCIDs = AuthCIDs Nothing Nothing Nothing

setCIDsToParameters :: AuthCIDs -> Parameters -> Parameters
setCIDsToParameters AuthCIDs{..} params =
    params
        { originalDestinationConnectionId = origDstCID
        , initialSourceConnectionId = initSrcCID
        , retrySourceConnectionId = retrySrcCID
        }

getCIDsToParameters :: Parameters -> AuthCIDs
getCIDsToParameters Parameters{..} =
    AuthCIDs
        { origDstCID = originalDestinationConnectionId
        , initSrcCID = initialSourceConnectionId
        , retrySrcCID = retrySourceConnectionId
        }