quic-0.2.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.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.X509 (CertificateChain)
import Foreign.Marshal.Alloc
import Foreign.Ptr (nullPtr)
import Network.Control (
Rate,
RxFlow,
TxFlow,
newRate,
newRxFlow,
newTxFlow,
)
import Network.Socket (SockAddr, Socket)
import Network.TLS.QUIC
import System.Mem.Weak (Weak)
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
, supplement :: Maybe Supplement
}
initialCoder :: Coder
initialCoder =
Coder
{ encrypt = \_ _ _ _ -> return (-1)
, decrypt = \_ _ _ _ -> return (-1)
, supplement = Nothing
}
data Coder1RTT = Coder1RTT
{ coder1RTT :: Coder
, secretN :: TrafficSecrets ApplicationSecret
}
initialCoder1RTT :: Coder1RTT
initialCoder1RTT =
Coder1RTT
{ coder1RTT = initialCoder
, secretN = (ClientTrafficSecret "", ServerTrafficSecret "")
}
data Protector = Protector
{ setSample :: Buffer -> IO ()
, getMask :: IO Buffer
, unprotect :: Sample -> Mask
}
initialProtector :: Protector
initialProtector =
Protector
{ setSample = \_ -> return ()
, getMask = return nullPtr
, 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
}
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
----------------------------------------------------------------
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
, connSocket :: IORef Socket
, genStatelessResetToken :: CID -> StatelessResetToken
, readers :: IORef (Map Word64 (Weak ThreadId))
, mainThreadId :: ThreadId
, controlRate :: 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
, outputRate :: Rate
, shared :: Shared
, delayedAckCount :: IORef Int
, delayedAckCancel :: IORef (IO ())
, -- State
peerPacketNumber :: IORef PacketNumber -- for RTT1
, streamTable :: IORef StreamTable
, 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
, flowTx :: TVar TxFlow
, flowRx :: IORef RxFlow
, migrationState :: TVar MigrationState
, sentRetirePriorTo :: IORef Bool
, minIdleTimeout :: IORef Microseconds
, bytesTx :: IORef Int
, bytesRx :: IORef Int
, -- TLS
pendingQ :: Array EncryptionLevel (TVar [ReceivedPacket])
, ciphers :: IOArray EncryptionLevel Cipher
, coders :: IOArray EncryptionLevel Coder
, coders1RTT :: IOArray Bool Coder1RTT
, protectors :: IOArray EncryptionLevel Protector
, currentKeyPhase :: IORef (Bool, PacketNumber)
, negotiated :: IORef Negotiated
, connMyAuthCIDs :: IORef AuthCIDs
, connPeerAuthCIDs :: IORef AuthCIDs
, -- Resources
encodeBuf :: Buffer
, encryptRes :: SizedBuffer
, decryptBuf :: Buffer
, connResources :: IORef (IO ())
, -- 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
-> IORef PeerInfo
-> RecvQ
-> Send
-> Recv
-> (CID -> StatelessResetToken)
-> IO Connection
newConnection rl myParameters origVersionInfo myAuthCIDs peerAuthCIDs connDebugLog connQLog connHooks connSocket peerInfo connRecvQ ~connSend ~connRecv genStatelessResetToken = do
connState <- newConnState rl
-- Manage
readers <- newIORef Map.empty
mainThreadId <- myThreadId
controlRate <- newRate
-- Info
roleInfo <- newIORef roleinfo
quicVersionInfo <- newIORef origVersionInfo
-- Mine
myCIDDB <- newIORef (newCIDDB myCID)
-- Peer
peerParameters <- newIORef baseParameters
peerCIDDB <- newTVarIO (newCIDDB peerCID)
-- Queus
inputQ <- newTQueueIO
cryptoQ <- newTQueueIO
outputQ <- newTQueueIO
outputRate <- newRate
shared <- newShared
delayedAckCount <- newIORef 0
delayedAckCancel <- newIORef (return ())
-- State
peerPacketNumber <- newIORef 0
streamTable <- newIORef emptyStreamTable
myStreamId <- newTVarIO (newConcurrency rl Bidirectional 0)
myUniStreamId <- newTVarIO (newConcurrency rl Unidirectional 0)
peerStreamId <- newIORef peerConcurrency
peerUniStreamId <- newIORef 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
-- TLS
pendingQ <- makePendingQ
ciphers <- newArray (InitialLevel, RTT1Level) defaultCipher
coders <- newArray (InitialLevel, HandshakeLevel) initialCoder
coders1RTT <- newArray (False, True) initialCoder1RTT
protectors <- newArray (InitialLevel, RTT1Level) initialProtector
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)
-- Recovery
let put x = atomically $ writeTQueue outputQ $ OutRetrans x
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
-> IORef PeerInfo
-> RecvQ
-> Send
-> Recv
-> (CID -> StatelessResetToken)
-> IO Connection
clientConnection ClientConfig{..} verInfo myAuthCIDs peerAuthCIDs =
newConnection Client ccParameters verInfo myAuthCIDs peerAuthCIDs
serverConnection
:: ServerConfig
-> VersionInfo
-> AuthCIDs
-> AuthCIDs
-> DebugLogger
-> QLogger
-> Hooks
-> IORef Socket
-> IORef PeerInfo
-> RecvQ
-> Send
-> Recv
-> (CID -> StatelessResetToken)
-> IO Connection
serverConnection ServerConfig{..} verInfo myAuthCIDs peerAuthCIDs =
newConnection Server scParameters verInfo myAuthCIDs peerAuthCIDs
----------------------------------------------------------------
newtype Input = InpStream Stream deriving (Show)
data Crypto = InpHandshake EncryptionLevel ByteString deriving (Show)
data Output
= OutControl EncryptionLevel [Frame]
| OutHandshake [(EncryptionLevel, ByteString)]
| OutRetrans PlainPacket
type InputQ = TQueue Input
type CryptoQ = TQueue Crypto
type OutputQ = TQueue Output
----------------------------------------------------------------
type SendStreamQ = TQueue TxStreamData
data Shared = Shared
{ sharedCloseSent :: IORef Bool
, sharedCloseReceived :: IORef Bool
, shared1RTTReady :: IORef Bool
, sharedSendStreamQ :: SendStreamQ
}
newShared :: IO Shared
newShared = do
sharedCloseSent <- newIORef False
sharedCloseReceived <- newIORef False
shared1RTTReady <- newIORef False
sharedSendStreamQ <- newTQueueIO
return Shared{..}