quic-0.0.0: Network/QUIC/Connection/Types.hs
{-# 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.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.Ptr
import Network.Socket (Socket, SockAddr)
import Network.TLS.QUIC
import Network.QUIC.Config
import Network.QUIC.Connector
import Network.QUIC.Crypto
import Network.QUIC.CryptoFusion
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
, serverAddr :: Maybe SockAddr
}
| ServerInfo { tokenManager :: ~CT.TokenManager
, registerCID :: CID -> Connection -> IO ()
, unregisterCID :: CID -> IO ()
, askRetry :: Bool
, baseThreadId :: ~ThreadId
, certChain :: Maybe CertificateChain
}
defaultClientRoleInfo :: RoleInfo
defaultClientRoleInfo = ClientInfo {
clientInitialToken = emptyToken
, resumptionInfo = defaultResumptionInfo
, serverAddr = Nothing
}
defaultServerRoleInfo :: RoleInfo
defaultServerRoleInfo = ServerInfo {
tokenManager = undefined
, registerCID = \_ _ -> return ()
, unregisterCID = \_ -> return ()
, askRetry = False
, baseThreadId = undefined
, certChain = Nothing
}
-- fixme: limitation
data CIDDB = CIDDB {
usedCIDInfo :: CIDInfo
, cidInfos :: IntMap CIDInfo
, revInfos :: Map CID Int
, nextSeqNum :: Int -- only for mine (new)
, 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
, triggeredByMe = False
}
where
cidInfo = CIDInfo 0 cid (StatelessResetToken "")
----------------------------------------------------------------
data MigrationState = NonMigration
| MigrationStarted
| SendChallenge [PathData]
| RecvResponse
deriving (Eq, Show)
data Coder = Coder {
encrypt :: Buffer -> Int -> Buffer -> Int -> PacketNumber -> Buffer -> IO Int
, decrypt :: Buffer -> Int -> Buffer -> Int -> PacketNumber -> Buffer -> IO Int
}
initialCoder :: Coder
initialCoder = Coder {
encrypt = \_ _ _ _ _ _ -> return (-1)
, decrypt = \_ _ _ _ _ _ -> return (-1)
}
data Coder1RTT = Coder1RTT {
coder1RTT :: Coder
, secretN :: TrafficSecrets ApplicationSecret
, supplement :: ~Supplement
}
initialCoder1RTT :: Coder1RTT
initialCoder1RTT = Coder1RTT {
coder1RTT = initialCoder
, secretN = (ClientTrafficSecret "", ServerTrafficSecret "")
, supplement = undefined
}
data Protector = Protector {
setSample :: Ptr Word8 -> IO ()
, getMask :: IO (Ptr Word8)
, 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
}
----------------------------------------------------------------
data Concurrency = Concurrency {
currentStream :: Int
, streamType :: Int
, maxStreams :: Int
}
newConcurrency :: Role -> Direction -> Int -> Concurrency
newConcurrency rl dir n = Concurrency typ typ n
where
bidi = dir == Bidirectional
typ | rl == Client = if bidi then 0 else 2
| otherwise = if bidi then 1 else 3
----------------------------------------------------------------
-- | A quic connection to carry multiple streams.
data Connection = Connection {
connState :: ConnState
-- Actions
, connDebugLog :: DebugLogger -- ^ A logger for debugging.
, connQLog :: QLogger
, connHooks :: Hooks
-- Manage
, connRecvQ :: RecvQ
, sockets :: IORef [Socket]
, readers :: IORef (IO ())
, tmouter :: IORef (IO ())
, mainThreadId :: ThreadId
-- Info
, roleInfo :: IORef RoleInfo
, quicVersion :: IORef Version
-- Mine
, myParameters :: Parameters
, myCIDDB :: IORef CIDDB
-- Peer
, peerParameters :: IORef Parameters
, peerCIDDB :: TVar CIDDB
-- Queues
, inputQ :: InputQ
, cryptoQ :: CryptoQ
, outputQ :: OutputQ
, migrationQ :: MigrationQ
, shared :: Shared
, delayedAckCount :: IORef Int
, delayedAckCancel :: IORef (IO ())
-- State
, peerPacketNumber :: IORef PacketNumber -- for RTT1
, streamTable :: IORef StreamTable
, myStreamId :: TVar Concurrency
, myUniStreamId :: TVar Concurrency
, peerStreamId :: IORef Concurrency
, flowTx :: TVar Flow
, flowRx :: IORef Flow
, migrationState :: TVar MigrationState
, minIdleTimeout :: IORef Microseconds
, bytesTx :: TVar Int
, bytesRx :: TVar Int
, addressValidated :: TVar Bool
-- 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
, handshakeCIDs :: IORef AuthCIDs
-- Resources
, connResources :: IORef (IO ())
-- Recovery
, connLDCC :: LDCC
}
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
newConnection :: Role
-> Parameters
-> Version -> AuthCIDs -> AuthCIDs
-> DebugLogger -> QLogger -> Hooks
-> IORef [Socket]
-> RecvQ
-> IO Connection
newConnection rl myparams ver myAuthCIDs peerAuthCIDs debugLog qLog hooks sref recvQ = do
outQ <- newTQueueIO
let put x = atomically $ writeTQueue outQ $ OutRetrans x
connstate <- newConnState rl
Connection connstate debugLog qLog hooks recvQ sref
<$> newIORef (return ())
<*> newIORef (return ())
<*> myThreadId
-- Info
<*> newIORef initialRoleInfo
<*> newIORef ver
-- Mine
<*> return myparams
<*> newIORef (newCIDDB myCID)
-- Peer
<*> newIORef baseParameters
<*> newTVarIO (newCIDDB peerCID)
-- Queues
<*> newTQueueIO
<*> newTQueueIO
<*> return outQ
<*> newTQueueIO
<*> newShared
<*> newIORef 0
<*> newIORef (return ())
-- State
<*> newIORef 0
<*> newIORef emptyStreamTable
<*> newTVarIO (newConcurrency rl Bidirectional 0)
<*> newTVarIO (newConcurrency rl Unidirectional 0)
<*> newIORef peerConcurrency
<*> newTVarIO defaultFlow
<*> newIORef defaultFlow { flowMaxData = initialMaxData myparams }
<*> newTVarIO NonMigration
<*> newIORef (milliToMicro $ maxIdleTimeout myparams)
<*> newTVarIO 0
<*> newTVarIO 0
<*> newTVarIO False
-- TLS
<*> makePendingQ
<*> newArray (InitialLevel,RTT1Level) defaultCipher
<*> newArray (InitialLevel,HandshakeLevel) initialCoder
<*> newArray (False,True) initialCoder1RTT
<*> newArray (InitialLevel,RTT1Level) initialProtector
<*> newIORef (False,0)
<*> newIORef initialNegotiated
<*> newIORef peerAuthCIDs
-- Resources
<*> newIORef (return ())
-- Recovery
<*> newLDCC connstate qLog put
where
isclient = rl == Client
initialRoleInfo
| isclient = defaultClientRoleInfo
| otherwise = defaultServerRoleInfo
Just myCID = initSrcCID myAuthCIDs
Just peerCID = initSrcCID peerAuthCIDs
peer | isclient = Server
| otherwise = Client
peerConcurrency = newConcurrency peer Bidirectional (initialMaxStreamsBidi myparams)
defaultTrafficSecrets :: (ClientTrafficSecret a, ServerTrafficSecret a)
defaultTrafficSecrets = (ClientTrafficSecret "", ServerTrafficSecret "")
----------------------------------------------------------------
clientConnection :: ClientConfig
-> Version -> AuthCIDs -> AuthCIDs
-> DebugLogger -> QLogger -> Hooks
-> IORef [Socket]
-> RecvQ
-> IO Connection
clientConnection ClientConfig{..} ver myAuthCIDs peerAuthCIDs =
newConnection Client ccParameters ver myAuthCIDs peerAuthCIDs
serverConnection :: ServerConfig
-> Version -> AuthCIDs -> AuthCIDs
-> DebugLogger -> QLogger -> Hooks
-> IORef [Socket]
-> RecvQ
-> IO Connection
serverConnection ServerConfig{..} ver myAuthCIDs peerAuthCIDs =
newConnection Server scParameters ver myAuthCIDs peerAuthCIDs
----------------------------------------------------------------
newtype Input = InpStream Stream deriving Show
data Crypto = InpHandshake EncryptionLevel ByteString deriving Show
data Output = OutControl EncryptionLevel [Frame] (IO ())
| OutHandshake [(EncryptionLevel,ByteString)]
| OutRetrans PlainPacket
type InputQ = TQueue Input
type CryptoQ = TQueue Crypto
type OutputQ = TQueue Output
type MigrationQ = TQueue ReceivedPacket
----------------------------------------------------------------
type SendStreamQ = TQueue TxStreamData
data Shared = Shared {
sharedCloseSent :: IORef Bool
, sharedCloseReceived :: IORef Bool
, shared1RTTReady :: IORef Bool
, sharedSendStreamQ :: SendStreamQ
}
newShared :: IO Shared
newShared = Shared <$> newIORef False
<*> newIORef False
<*> newIORef False
<*> newTQueueIO