quic-0.0.1: Network/QUIC/Server/Run.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Network.QUIC.Server.Run (
run
, stop
) where
import qualified Network.Socket as NS
import System.Log.FastLogger
import UnliftIO.Async
import UnliftIO.Concurrent
import qualified UnliftIO.Exception as E
import Network.QUIC.Closer
import Network.QUIC.Common
import Network.QUIC.Config
import Network.QUIC.Connection
import Network.QUIC.Crypto
import Network.QUIC.Exception
import Network.QUIC.Handshake
import Network.QUIC.Imports
import Network.QUIC.Logger
import Network.QUIC.Packet
import Network.QUIC.Parameters
import Network.QUIC.QLogger
import Network.QUIC.Qlog
import Network.QUIC.Receiver
import Network.QUIC.Recovery
import Network.QUIC.Sender
import Network.QUIC.Server.Reader
import Network.QUIC.Socket
import Network.QUIC.Types
----------------------------------------------------------------
-- | Running a QUIC server.
-- The action is executed with a new connection
-- in a new lightweight thread.
run :: ServerConfig -> (Connection -> IO ()) -> IO ()
run conf server = handleLogUnit debugLog $ do
baseThreadId <- myThreadId
E.bracket setup teardown $ \(dispatch,_) -> forever $ do
acc <- accept dispatch
void $ forkIO (runServer conf server dispatch baseThreadId acc)
where
doDebug = isJust $ scDebugLog conf
debugLog msg | doDebug = stdoutLogger ("run: " <> msg)
| otherwise = return ()
setup = do
dispatch <- newDispatch
-- fixme: the case where sockets cannot be created.
ssas <- mapM udpServerListenSocket $ scAddresses conf
tids <- mapM (runDispatcher dispatch conf) ssas
ttid <- forkIO timeouter -- fixme
return (dispatch, ttid:tids)
teardown (dispatch, tids) = do
clearDispatch dispatch
mapM_ killThread tids
-- Typically, ConnectionIsClosed breaks acceptStream.
-- And the exception should be ignored.
runServer :: ServerConfig -> (Connection -> IO ()) -> Dispatch -> ThreadId -> Accept -> IO ()
runServer conf server0 dispatch baseThreadId acc =
E.bracket open clse $ \(ConnRes conn send recv myAuthCIDs reader) ->
handleLogUnit (debugLog conn) $ do
forkIO reader >>= addReader conn
handshaker <- handshakeServer conf conn myAuthCIDs
let server = do
wait1RTTReady conn
afterHandshakeServer conn
server0 conn
ldcc = connLDCC conn
supporters = foldr1 concurrently_ [handshaker
,sender conn send
,receiver conn recv
,resender ldcc
,ldccTimer ldcc
]
runThreads = do
er <- race supporters server
case er of
Left () -> E.throwIO MustNotReached
Right r -> return r
E.trySyncOrAsync runThreads >>= closure conn ldcc
where
open = createServerConnection conf dispatch acc baseThreadId
clse connRes = do
let conn = connResConnection connRes
setDead conn
freeResources conn
killReaders conn
socks <- getSockets conn
mapM_ NS.close socks
debugLog conn msg = do
connDebugLog conn ("runServer: " <> msg)
qlogDebug conn $ Debug $ toLogStr msg
createServerConnection :: ServerConfig -> Dispatch -> Accept -> ThreadId
-> IO ConnRes
createServerConnection conf@ServerConfig{..} dispatch Accept{..} baseThreadId = do
s0 <- udpServerConnectedSocket accMySockAddr accPeerSockAddr
sref <- newIORef [s0]
let send buf siz = void $ do
s:_ <- readIORef sref
NS.sendBuf s buf siz
recv = recvServer accRecvQ
let Just myCID = initSrcCID accMyAuthCIDs
Just ocid = origDstCID accMyAuthCIDs
(qLog, qclean) <- dirQLogger scQLog accTime ocid "server"
(debugLog, dclean) <- dirDebugLogger scDebugLog ocid
debugLog $ "Original CID: " <> bhow ocid
conn <- serverConnection conf accVersion accMyAuthCIDs accPeerAuthCIDs debugLog qLog scHooks sref accRecvQ
addResource conn qclean
addResource conn dclean
let cid = fromMaybe ocid $ retrySrcCID accMyAuthCIDs
initializeCoder conn InitialLevel $ initialSecrets accVersion cid
setupCryptoStreams conn -- fixme: cleanup
let pktSiz = (defaultPacketSize accMySockAddr `max` accPacketSize) `min` maximumPacketSize accMySockAddr
setMaxPacketSize conn pktSiz
setInitialCongestionWindow (connLDCC conn) pktSiz
debugLog $ "Packet size: " <> bhow pktSiz <> " (" <> bhow accPacketSize <> ")"
addRxBytes conn accPacketSize
when accAddressValidated $ setAddressValidated conn
--
let retried = isJust $ retrySrcCID accMyAuthCIDs
when retried $ do
qlogRecvInitial conn
qlogSentRetry conn
--
let mgr = tokenMgr dispatch
setTokenManager conn mgr
--
setBaseThreadId conn baseThreadId
--
setRegister conn accRegister accUnregister
accRegister myCID conn
addResource conn $ do
myCIDs <- getMyCIDs conn
mapM_ accUnregister myCIDs
--
let reader = readerServer s0 conn -- dies when s0 is closed.
return $ ConnRes conn send recv accMyAuthCIDs reader
afterHandshakeServer :: Connection -> IO ()
afterHandshakeServer conn = handleLogT logAction $ do
--
cidInfo <- getNewMyCID conn
register <- getRegister conn
register (cidInfoCID cidInfo) conn
--
cryptoToken <- generateToken =<< getVersion conn
mgr <- getTokenManager conn
token <- encryptToken mgr cryptoToken
let ncid = NewConnectionID cidInfo 0
sendFrames conn RTT1Level [NewToken token,ncid,HandshakeDone]
where
logAction msg = connDebugLog conn $ "afterHandshakeServer: " <> msg
-- | Stopping the base thread of the server.
stop :: Connection -> IO ()
stop conn = getBaseThreadId conn >>= killThread