packages feed

quic-0.3.20: Network/QUIC/Connection/Types.hs

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StrictData #-}

module Network.QUIC.Connection.Types where

import Control.Concurrent
import Control.Concurrent.STM
import qualified Crypto.Token as CT
import Data.Array.IO
import Data.ByteString.Internal
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IntMap
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.X509 (CertificateChain)
import Foreign.Marshal.Alloc
import Network.Control (
    Rate,
    RxFlow,
    TxFlow,
    newRate,
    newRxFlow,
    newTxFlow,
 )
import Network.Socket (SockAddr, Socket)
import Network.TLS.QUIC
import qualified System.ThreadManager as T

import Network.QUIC.Config
import Network.QUIC.Connector
import Network.QUIC.Crypto
import Network.QUIC.Imports
import Network.QUIC.Logger
import Network.QUIC.Parameters
import Network.QUIC.Qlog
import Network.QUIC.Recovery
import Network.QUIC.Stream
import Network.QUIC.Types

----------------------------------------------------------------

dummySecrets :: TrafficSecrets a
dummySecrets = (ClientTrafficSecret "", ServerTrafficSecret "")

----------------------------------------------------------------

data RoleInfo
    = ClientInfo
        { clientInitialToken :: Token -- new or retry token
        , resumptionInfo :: ResumptionInfo
        , incompatibleVN :: Bool
        , sockConnected :: Bool
        }
    | ServerInfo
        { tokenManager :: ~CT.TokenManager
        , registerCID :: CID -> Connection -> IO ()
        , unregisterCID :: CID -> IO ()
        , askRetry :: Bool
        , stopServer :: IO ()
        , certChain :: Maybe CertificateChain
        }

defaultClientRoleInfo :: RoleInfo
defaultClientRoleInfo =
    ClientInfo
        { clientInitialToken = emptyToken
        , resumptionInfo = defaultResumptionInfo
        , incompatibleVN = False
        , sockConnected = False
        }

defaultServerRoleInfo :: RoleInfo
defaultServerRoleInfo =
    ServerInfo
        { tokenManager = undefined
        , registerCID = \_ _ -> return ()
        , unregisterCID = \_ -> return ()
        , askRetry = False
        , stopServer = return ()
        , certChain = Nothing
        }

-- cidInfoSRT in CIDInfo is only used in client
-- which accepts stateless reset.
data CIDDB = CIDDB
    { usedCIDInfo :: CIDInfo
    , cidInfos :: IntMap CIDInfo
    , revInfos :: Map CID Int
    , nextSeqNum :: Int -- only for mine (new)
    , retirePriorTo :: Int
    , triggeredByMe :: Bool -- only for peer's
    }
    deriving (Show)

newCIDDB :: CID -> CIDDB
newCIDDB cid =
    CIDDB
        { usedCIDInfo = cidInfo
        , cidInfos = IntMap.singleton 0 cidInfo
        , revInfos = Map.singleton cid 0
        , nextSeqNum = 1
        , retirePriorTo = 1
        , triggeredByMe = False
        }
  where
    cidInfo = newCIDInfo 0 cid $ StatelessResetToken ""

----------------------------------------------------------------

data MigrationState
    = NonMigration
    | MigrationStarted
    | SendChallenge PathInfo PathData
    | RecvResponse

{- FOURMOLU_DISABLE -}
instance Eq MigrationState where
    NonMigration     == NonMigration     = True
    MigrationStarted == MigrationStarted = True
    RecvResponse     == RecvResponse     = True
    _                == _                = False
{- FOURMOLU_ENABLE -}

----------------------------------------------------------------

data Coder = Coder
    { encrypt :: Buffer -> PlainText -> AssDat -> PacketNumber -> IO Int
    , decrypt :: Buffer -> CipherText -> AssDat -> PacketNumber -> IO Int
    }

initialCoder :: Coder
initialCoder =
    Coder
        { encrypt = \_ _ _ _ -> return (-1)
        , decrypt = \_ _ _ _ -> return (-1)
        }

data Coder1RTT = Coder1RTT
    { coder1RTT :: Coder
    , secretN :: TrafficSecrets ApplicationSecret
    }

initialCoder1RTT :: Coder1RTT
initialCoder1RTT =
    Coder1RTT
        { coder1RTT = initialCoder
        , secretN = (ClientTrafficSecret "", ServerTrafficSecret "")
        }

data Protector = Protector
    { setSample :: Buffer -> IO ()
    , withMask :: WithMask
    , unprotect :: Sample -> Mask
    }

initialProtector :: Protector
initialProtector =
    Protector
        { setSample = \_ -> return ()
        , withMask = \_ -> return False
        , unprotect = \_ -> Mask ""
        }

----------------------------------------------------------------

data Negotiated = Negotiated
    { tlsHandshakeMode :: HandshakeMode13
    , applicationProtocol :: Maybe NegotiatedProtocol
    , applicationSecretInfo :: ApplicationSecretInfo
    }

initialNegotiated :: Negotiated
initialNegotiated =
    Negotiated
        { tlsHandshakeMode = FullHandshake
        , applicationProtocol = Nothing
        , applicationSecretInfo = ApplicationSecretInfo defaultTrafficSecrets
        }

----------------------------------------------------------------

newtype StreamIdBase = StreamIdBase {fromStreamIdBase :: Int}
    deriving (Eq, Show)

data Concurrency = Concurrency
    { currentStream :: StreamId
    , maxStreams :: StreamIdBase
    , closedStreams :: Int
    -- ^ For the peer's streams: how many of them we are done with
    }
    deriving (Show)

newConcurrency :: Role -> Direction -> Int -> Concurrency
newConcurrency rl dir n = Concurrency{..}
  where
    bidi = dir == Bidirectional
    currentStream
        | rl == Client = if bidi then 0 else 2
        | otherwise = if bidi then 1 else 3
    maxStreams = StreamIdBase n
    closedStreams = 0

-- | The peer-initiated streams of one type that have been opened so far.
--
-- Every id below 'openedNext' has been opened, save those in
-- 'openedSkipped': the peer used a higher-numbered stream first, which opens
-- these too (RFC 9000 Sec 3.2), but nothing has arrived for them yet.  A
-- stream that has been opened and is no longer in the stream table has been
-- closed, and a frame for it -- typically a retransmission that crossed our
-- acknowledgement -- must not open it again.
data OpenedStreams = OpenedStreams
    { openedNext :: StreamId
    , openedSkipped :: IntSet
    }
    deriving (Show)

newOpenedStreams :: Concurrency -> OpenedStreams
newOpenedStreams conc = OpenedStreams (currentStream conc) IntSet.empty

----------------------------------------------------------------

type Send = Buffer -> Int -> IO ()
type Recv = IO ReceivedPacket

-- For migration, two SockAddr for the peer are contained.
data PeerInfo = PeerInfo
    { currPathInfo :: PathInfo
    , prevPathInfo :: Maybe PathInfo
    }

data PathInfo = PathInfo
    { peerSockAddr :: SockAddr
    , pathBytesTx :: TVar Int -- TVar for anti amplification
    , pathBytesRx :: TVar Int -- TVar for anti amplification
    , addressValidated :: TVar Bool
    }

newPathInfo :: SockAddr -> IO PathInfo
newPathInfo peerSockAddr = do
    pathBytesTx <- newTVarIO 0
    pathBytesRx <- newTVarIO 0
    addressValidated <- newTVarIO False
    return PathInfo{..}

----------------------------------------------------------------

{- FOURMOLU_DISABLE -}
-- | A quic connection to carry multiple streams.
data Connection = Connection
    { connState         :: ConnState
    , -- Actions
      connDebugLog      :: DebugLogger
    -- ^ A logger for debugging.
    , connQLog          :: QLogger
    , connHooks         :: Hooks
    , connSend          :: ~Send -- ~ for testing
    , connRecv          :: ~Recv -- ~ for testing
    -- Manage
    , connRecvQ         :: RecvQ
    , connRecvDatagramQ :: DatagramQ
    , connSocket        :: IORef (Socket, TVar Bool)
    -- ^ The socket and the variable which retires its reader: setting it
    --   tells the reader that the socket is not its to read any more.
    --   One reference, so that the two cannot be swapped apart.
    , connRetiredSocks  :: IORef [Socket]
    -- ^ Sockets a reader has left and nobody is in any more.  The sender
    --   closes them, because it is the one thread which could otherwise
    --   be inside a send on one, and the connection's teardown closes
    --   what is left.
    , genStatelessResetToken :: CID -> StatelessResetToken
    , readers           :: T.ThreadManager
    , mainThreadId      :: ThreadId
    , pathAndCIDFrameRate :: Rate
    , -- Info
      roleInfo          :: IORef RoleInfo
    , quicVersionInfo   :: IORef VersionInfo
    , origVersionInfo   :: VersionInfo -- chosenVersion is client's ver in Initial
    -- Mine
    , myParameters      :: Parameters
    , myCIDDB           :: IORef CIDDB
    , -- Peer
      peerParameters    :: IORef Parameters
    , peerCIDDB         :: TVar CIDDB
    , peerInfo          :: IORef PeerInfo
    , -- Queues
      inputQ            :: InputQ
    , cryptoQ           :: CryptoQ
    , outputQ           :: OutputQ
    , pathResponseRate  :: Rate
    , queuedControls    :: TVar Int
    -- ^ Control frames in 'outputQ' which the sender has not taken yet.
    , shared            :: Shared
    , delayedAckCount   :: IORef Int
    , delayedAckCancel  :: IORef (IO ())
    , -- State
      peerPacketNumber  :: Array EncryptionLevel (IORef PacketNumber)
    -- ^ The largest the peer has sent, by level.  0-RTT and 1-RTT share
    --   one, as they share a packet number space.
    , streamTable       :: TVar StreamTable
    , -- | What a stream the application has closed still owes the
      -- connection's window, for as long as the peer has not finished it.
      -- See 'Network.QUIC.Connection.StreamTable.departStream'.
      departedStreams   :: TVar (IntMap Departed)
    , myStreamId        :: TVar Concurrency -- C:0 S:1
    , myUniStreamId     :: TVar Concurrency -- C:2 S:3
    , peerStreamId      :: IORef Concurrency -- C:1 S:0
    , peerUniStreamId   :: IORef Concurrency -- C:3 S:2
    , peerOpened        :: IORef OpenedStreams -- C:1 S:0
    , peerUniOpened     :: IORef OpenedStreams -- C:3 S:2
    , flowTx            :: TVar TxFlow
    , flowRx            :: IORef RxFlow
    , migrationState    :: TVar MigrationState
    , sentRetirePriorTo :: IORef Bool
    , minIdleTimeout    :: IORef Microseconds
    , bytesTx           :: IORef Int
    , bytesRx           :: IORef Int
    , connDone          :: TVar Bool
    , connAbortRequest  :: TVar (Maybe (ApplicationProtocolError, ReasonPhrase))
    -- ^ What 'Network.QUIC.Connection.Misc.abortConnection' asked to close
    --   the connection with, for its protocol threads to act on.
    , -- TLS
      pendingQ          :: Array EncryptionLevel (TVar [ReceivedPacket])
    , ciphers           :: IOArray EncryptionLevel Cipher
    , coders            :: IOArray EncryptionLevel Coder
    , coders1RTT        :: IOArray Bool Coder1RTT
    , protectors        :: IOArray EncryptionLevel Protector
    , prevInitialKeys   :: IORef (Maybe (Coder, Protector))
    -- ^ The Initial keys for the version the peer addressed us in, kept
    --   over a compatible version change (RFC 9368)
    , currentKeyPhase   :: IORef (Bool, PacketNumber)
    , negotiated        :: IORef Negotiated
    , connMyAuthCIDs    :: IORef AuthCIDs
    , connPeerAuthCIDs  :: IORef AuthCIDs
    , -- Resources
      encodeBuf         :: Buffer
    , encryptRes        :: SizedBuffer
    , decryptBuf        :: Buffer
    , connResources     :: IORef (IO ())
    , connCloseSent     :: IORef Bool
    -- ^ Whether the peer has been told the connection is over
    , connCloserOwnsSocket :: IORef Bool
    -- ^ Whether the closer has taken the client's socket to close
    , connAuthFailures  :: IORef Int
    -- ^ Packets that have failed authentication, over the whole connection
    , connKeyPackets    :: IORef Int
    -- ^ Packets the 1-RTT key in use has protected
    , -- Recovery
      connLDCC          :: LDCC
    }
{- FOURMOLU_ENABLE -}

instance KeepQlog Connection where
    keepQlog conn = connQLog conn

instance Connector Connection where
    getRole = role . connState
    getEncryptionLevel = readTVarIO . encryptionLevel . connState
    getMaxPacketSize = readIORef . maxPacketSize . connState
    getConnectionState = readTVarIO . connectionState . connState
    getPacketNumber = readIORef . packetNumber . connState
    getAlive = readIORef . connectionAlive . connState

setDead :: Connection -> IO ()
setDead conn = writeIORef (connectionAlive $ connState conn) False

makePendingQ :: IO (Array EncryptionLevel (TVar [ReceivedPacket]))
makePendingQ = do
    q1 <- newTVarIO []
    q2 <- newTVarIO []
    q3 <- newTVarIO []
    let lst = [(RTT0Level, q1), (HandshakeLevel, q2), (RTT1Level, q3)]
        arr = array (RTT0Level, RTT1Level) lst
    return arr

{- FOURMOLU_DISABLE -}
newConnection
    :: Role
    -> Parameters
    -> VersionInfo
    -> AuthCIDs
    -> AuthCIDs
    -> DebugLogger
    -> QLogger
    -> Hooks
    -> IORef (Socket, TVar Bool)
    -> IORef PeerInfo
    -> RecvQ
    -> DatagramQ
    -> Send
    -> Recv
    -> (CID -> StatelessResetToken)
    -> IO Connection
newConnection rl myParameters origVersionInfo myAuthCIDs peerAuthCIDs connDebugLog connQLog connHooks connSocket peerInfo connRecvQ connRecvDatagramQ ~connSend ~connRecv genStatelessResetToken = do
    connState         <- newConnState rl
    -- Manage
    readers           <- T.newThreadManager
    mainThreadId      <- myThreadId
    pathAndCIDFrameRate <- newRate
    -- Info
    roleInfo          <- newIORef roleinfo
    quicVersionInfo   <- newIORef origVersionInfo
    -- Mine
    myCIDDB           <- newIORef (newCIDDB myCID)
    -- Peer
    peerParameters    <- newIORef baseParameters
    peerCIDDB         <- newTVarIO (newCIDDB peerCID)
    -- Queues
    inputQ            <- newTQueueIO
    cryptoQ           <- newTQueueIO
    outputQ           <- newTQueueIO
    pathResponseRate  <- newRate
    queuedControls    <- newTVarIO 0
    shared            <- newShared
    delayedAckCount   <- newIORef 0
    delayedAckCancel  <- newIORef (return ())
    -- State
    peerPacketNumber  <- newPeerPacketNumber
    streamTable       <- newTVarIO emptyStreamTable
    departedStreams   <- newTVarIO IntMap.empty
    myStreamId        <- newTVarIO (newConcurrency rl Bidirectional 0)
    myUniStreamId     <- newTVarIO (newConcurrency rl Unidirectional 0)
    peerStreamId      <- newIORef peerConcurrency
    peerUniStreamId   <- newIORef peerUniConcurrency
    peerOpened        <- newIORef (newOpenedStreams peerConcurrency)
    peerUniOpened     <- newIORef (newOpenedStreams peerUniConcurrency)
    flowTx            <- newTVarIO (newTxFlow 0) -- limit is set in Handshake
    flowRx            <- newIORef (newRxFlow $ initialMaxData myParameters)
    migrationState    <- newTVarIO NonMigration
    sentRetirePriorTo <- newIORef False
    minIdleTimeout    <- newIORef (milliToMicro $ maxIdleTimeout myParameters)
    bytesTx           <- newIORef 0
    bytesRx           <- newIORef 0
    connDone          <- newTVarIO False
    connAbortRequest  <- newTVarIO Nothing
    connRetiredSocks  <- newIORef []
    -- TLS
    pendingQ          <- makePendingQ
    ciphers           <- newArray (InitialLevel, RTT1Level) defaultCipher
    coders            <- newArray (InitialLevel, HandshakeLevel) initialCoder
    coders1RTT        <- newArray (False, True) initialCoder1RTT
    protectors        <- newArray (InitialLevel, RTT1Level) initialProtector
    prevInitialKeys   <- newIORef Nothing
    currentKeyPhase   <- newIORef (False, 0)
    negotiated        <- newIORef initialNegotiated
    connMyAuthCIDs    <- newIORef myAuthCIDs
    connPeerAuthCIDs  <- newIORef peerAuthCIDs
    -- Resources
    encodeBuf         <- mallocBytes bufsiz -- used sender or closere
    encryptBuf        <- mallocBytes bufsiz
    let encryptRes = SizedBuffer encryptBuf bufsiz -- used sender
    decryptBuf        <- mallocBytes bufsiz -- used receiver
    connResources     <- newIORef (free encodeBuf >> free encryptBuf >> free decryptBuf)
    connCloseSent     <- newIORef False
    connCloserOwnsSocket <- newIORef False
    connAuthFailures  <- newIORef 0
    connKeyPackets    <- newIORef 0
    -- Recovery
    -- Counted as 'putOutput' does, which this cannot use.
    let put x = atomically $ do
            let out = OutRetrans x
            writeTQueue outputQ out
            modifyTVar' queuedControls (+ countControls out)
    connLDCC          <- newLDCC connState connQLog put
    return Connection{..}
  where
    isclient = rl == Client
    roleinfo
        | isclient = defaultClientRoleInfo
        | otherwise = defaultServerRoleInfo
    myCID = fromJust $ initSrcCID myAuthCIDs
    peerCID = fromJust $ initSrcCID peerAuthCIDs
    peer
        | isclient = Server
        | otherwise = Client
    peerConcurrency = newConcurrency peer Bidirectional (initialMaxStreamsBidi myParameters)
    peerUniConcurrency = newConcurrency peer Unidirectional (initialMaxStreamsUni myParameters)
    bufsiz = maximumUdpPayloadSize
{- FOURMOLU_ENABLE -}

defaultTrafficSecrets :: (ClientTrafficSecret a, ServerTrafficSecret a)
defaultTrafficSecrets = (ClientTrafficSecret "", ServerTrafficSecret "")

----------------------------------------------------------------

clientConnection
    :: ClientConfig
    -> VersionInfo
    -> AuthCIDs
    -> AuthCIDs
    -> DebugLogger
    -> QLogger
    -> Hooks
    -> IORef (Socket, TVar Bool)
    -> IORef PeerInfo
    -> RecvQ
    -> DatagramQ
    -> Send
    -> Recv
    -> (CID -> StatelessResetToken)
    -> IO Connection
clientConnection ClientConfig{..} verInfo myAuthCIDs peerAuthCIDs =
    newConnection Client ccParameters' verInfo myAuthCIDs peerAuthCIDs
  where
    ccParameters' =
        ccParameters
            { maxDatagramFrameSize = ccMaxDatagramFrameSize
            }

serverConnection
    :: ServerConfig
    -> VersionInfo
    -> AuthCIDs
    -> AuthCIDs
    -> DebugLogger
    -> QLogger
    -> Hooks
    -> IORef (Socket, TVar Bool)
    -> IORef PeerInfo
    -> RecvQ
    -> DatagramQ
    -> Send
    -> Recv
    -> (CID -> StatelessResetToken)
    -> IO Connection
serverConnection ServerConfig{..} verInfo myAuthCIDs peerAuthCIDs =
    newConnection Server scParameters' verInfo myAuthCIDs peerAuthCIDs
  where
    scParameters' =
        scParameters
            { maxDatagramFrameSize = scMaxDatagramFrameSize
            }

----------------------------------------------------------------

newPeerPacketNumber :: IO (Array EncryptionLevel (IORef PacketNumber))
newPeerPacketNumber = do
    ini <- newIORef 0
    app <- newIORef 0
    hnd <- newIORef 0
    return $
        listArray
            (InitialLevel, RTT1Level)
            [ini, app, hnd, app] -- Initial, 0-RTT, Handshake, 1-RTT

----------------------------------------------------------------

newtype Input = InpStream Stream deriving (Show)
data Crypto = InpHandshake EncryptionLevel ByteString deriving (Show)

data Output
    = OutControl EncryptionLevel [Frame]
    | OutHandshake [(EncryptionLevel, ByteString)]
    | OutRetrans PlainPacket

-- | The control frames of an output, which 'queuedControls' counts.
--   Not data, which flow control bounds, nor what is never resent.
countControls :: Output -> Int
countControls (OutControl _ frames) = length $ filter isControl frames
countControls (OutRetrans (PlainPacket _ plain)) = length $ filter isControl $ plainFrames plain
countControls OutHandshake{} = 0

isControl :: Frame -> Bool
isControl Padding{} = False
isControl Ack{} = False
isControl StreamF{} = False
isControl CryptoF{} = False
isControl Datagram{} = False
isControl PathResponse{} = False
isControl _ = True

type InputQ = TQueue Input
type CryptoQ = TQueue Crypto
type OutputQ = TQueue Output

----------------------------------------------------------------

type SendStreamQ = TQueue TxStreamData

type DatagramQ = TQueue ByteString

data Shared = Shared
    { sharedCloseSent :: IORef Bool
    , sharedCloseReceived :: TVar (Maybe ReasonPhrase)
    -- ^ The reason of a CONNECTION_CLOSE(NO_ERROR) from the peer
    , shared1RTTReady :: IORef Bool
    , sharedSendStreamQ :: SendStreamQ
    }

newShared :: IO Shared
newShared = do
    sharedCloseSent <- newIORef False
    sharedCloseReceived <- newTVarIO Nothing
    shared1RTTReady <- newIORef False
    sharedSendStreamQ <- newTQueueIO
    return Shared{..}

----------------------------------------------------------------

mainDone :: Connection -> IO ()
mainDone conn = atomically $ writeTVar (connDone conn) True

-- | The peer has closed the connection: we drain, so nothing is sent back
--   (RFC 9000 Sec 10.2.2), and what is waiting on the peer gives up.
setPeerClosed :: Connection -> ReasonPhrase -> IO ()
setPeerClosed conn reason = do
    writeIORef (connCloseSent conn) True
    atomically $ writeTVar (sharedCloseReceived $ shared conn) $ Just reason

isPeerClosed :: Connection -> IO Bool
isPeerClosed conn = isJust <$> readTVarIO (sharedCloseReceived $ shared conn)

-- | Waiting for the peer to close, then throwing 'ConnectionIsClosed'.
--   An alternative for a wait on the peer.
peerClosedSTM :: Connection -> STM a
peerClosedSTM conn = do
    mr <- readTVar $ sharedCloseReceived $ shared conn
    case mr of
        Nothing -> retry
        Just reason -> throwSTM $ ConnectionIsClosed reason

-- | Throwing 'ConnectionIsClosed' if the peer has closed.
checkPeerClosedSTM :: Connection -> STM ()
checkPeerClosedSTM conn = peerClosedSTM conn `orElse` return ()