quic-0.3.20: Network/QUIC/Stream/Misc.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Network.QUIC.Stream.Misc (
resetTxStream,
isTxStreamReset,
getTxStreamOffset,
isTxStreamClosed,
setTxStreamClosed,
getRxStreamOffset,
isRxStreamClosed,
setRxStreamClosed,
resetReceived,
setResetReceived,
markReleased,
FinalSizeProblem (..),
getRxBounds,
noteRxFrame,
noteRxFrameSTM,
noteRxFinalSize,
getRxBoundsSTM,
addRxCredited,
addRxCreditedSTM,
takeRxOwedSTM,
getRxConsumed,
--
readStreamFlowTx,
addTxStreamData,
setTxMaxStreamData,
--
getRxMaxStreamData,
addRxStreamData,
updateStreamFlowRx,
) where
import Control.Concurrent.STM
import Network.Control
import Network.QUIC.Imports
import Network.QUIC.Stream.Queue
import Network.QUIC.Stream.Types
import Network.QUIC.Types (ApplicationProtocolError)
----------------------------------------------------------------
-- | Reset the sending part and return its final size: every octet handed
-- to the sender, sent or still queued. The sender drops what is queued,
-- so nothing goes out past it.
resetTxStream :: Stream -> IO Offset
resetTxStream s@Stream{..} = do
-- Before the read: what is queued after it is dropped, not counted.
atomicWriteIORef streamResetTx True
setTxStreamClosed s
txfSent <$> readTVarIO streamFlowTx
isTxStreamReset :: Stream -> IO Bool
isTxStreamReset Stream{..} = readIORef streamResetTx
getTxStreamOffset :: Stream -> Int -> IO Offset
getTxStreamOffset Stream{..} len = atomicModifyIORef' streamStateTx get
where
get (StreamState off fin) = (StreamState (off + len) fin, off)
isTxStreamClosed :: Stream -> IO Bool
isTxStreamClosed Stream{..} = do
StreamState _ fin <- readIORef streamStateTx
return fin
setTxStreamClosed :: Stream -> IO ()
setTxStreamClosed Stream{..} = atomicModifyIORef'' streamStateTx set
where
set (StreamState off _) = StreamState off True
----------------------------------------------------------------
getRxStreamOffset :: Stream -> Int -> IO Offset
getRxStreamOffset Stream{..} len = atomicModifyIORef' streamStateRx get
where
get (StreamState off fin) = (StreamState (off + len) fin, off)
isRxStreamClosed :: Stream -> IO Bool
isRxStreamClosed Stream{..} = do
StreamState _ fin <- readIORef streamStateRx
return fin
setRxStreamClosed :: Stream -> IO ()
setRxStreamClosed strm@Stream{..} = do
atomicModifyIORef'' streamStateRx set
-- Sending a pseudo FIN so that recvStream doesn't block.
-- See https://github.com/kazu-yamamoto/quic/pull/54
putRecvStreamQ strm ""
where
set (StreamState off _) = StreamState off True
-- | The error code of the RESET_STREAM the peer sent for this stream, if it
-- sent one.
--
-- After a RESET_STREAM, 'recvStream' returns an empty 'ByteString', just as
-- it does at the end of the stream. This tells the two apart: HTTP/3, for
-- one, has to tell its QPACK encoder about a request whose reset it saw
-- (RFC 9204, section 4.4.2), and had no way to know that it was reset.
resetReceived :: Stream -> IO (Maybe ApplicationProtocolError)
resetReceived Stream{..} = readIORef streamResetRx
setResetReceived :: Stream -> ApplicationProtocolError -> IO ()
setResetReceived Stream{..} aerr = writeIORef streamResetRx $ Just aerr
-- | Marking the stream as done with; 'True' only the first time.
markReleased :: Stream -> IO Bool
markReleased Stream{..} = atomicModifyIORef' streamReleased $ \done -> (True, not done)
----------------------------------------------------------------
readStreamFlowTx :: Stream -> STM TxFlow
readStreamFlowTx Stream{..} = readTVar streamFlowTx
----------------------------------------------------------------
addTxStreamData :: Stream -> Int -> STM ()
addTxStreamData Stream{..} n = modifyTVar' streamFlowTx add
where
add flow = flow{txfSent = txfSent flow + n}
setTxMaxStreamData :: Stream -> Int -> IO ()
setTxMaxStreamData Stream{..} n = atomically $ modifyTVar' streamFlowTx set
where
set flow
| txfLimit flow < n = flow{txfLimit = n}
| otherwise = flow
----------------------------------------------------------------
getRxMaxStreamData :: Stream -> IO Int
getRxMaxStreamData Stream{..} = rxfLimit <$> readIORef streamFlowRx
addRxStreamData :: Stream -> Int -> IO ()
addRxStreamData Stream{..} n = atomicModifyIORef'' streamFlowRx add
where
add flow = flow{rxfReceived = rxfReceived flow + n}
updateStreamFlowRx :: Stream -> Int -> IO (Maybe Int)
updateStreamFlowRx Stream{..} consumed =
atomicModifyIORef' streamFlowRx $ maybeOpenRxWindow consumed FCTMaxData
{- cannot be used due to reassemble.
checkRxMaxStreamData :: Stream -> Int -> IO Bool
checkRxMaxStreamData Stream{..} len =
atomicModifyIORef' streamFlowRx $ checkRxLimit len
-}
----------------------------------------------------------------
-- | What is wrong with where a peer says its stream ends.
--
-- RFC 9000 Sec 4.5: "Once a final size for a stream is known, it cannot
-- change. If a RESET_STREAM or STREAM frame is received indicating a change
-- in the final size for the stream, an endpoint SHOULD respond with an error
-- of type FINAL_SIZE_ERROR. ... A receiver SHOULD treat receipt of data at or
-- beyond the final size as an error of type FINAL_SIZE_ERROR, even after a
-- stream is closed."
data FinalSizeProblem
= -- | A final size that is not the one already known
FinalSizeChanged
| -- | A final size below what has already been seen of the stream
FinalSizeTooSmall
| -- | Data at or beyond a final size already known
DataPastFinalSize
deriving (Eq, Show)
-- | What the receiving side has seen of where this stream ends.
getRxBounds :: Stream -> IO RxBounds
getRxBounds Stream{..} = readTVarIO streamRxBounds
-- | Answering with how far the frame takes the stream past what has been
-- seen of it: what the connection's flow controller is to count as
-- received for it (RFC 9000 Sec 4.1). A copy of what was seen is 0.
noteRxFrame :: Stream -> Int -> Bool -> IO (Either FinalSizeProblem Int)
noteRxFrame s end fin = atomically $ noteRxFrameSTM s end fin
-- | 'noteRxFrame' as part of a transaction: see
-- 'Network.QUIC.Connection.StreamTable.noteRxArrival'.
noteRxFrameSTM :: Stream -> Int -> Bool -> STM (Either FinalSizeProblem Int)
noteRxFrameSTM Stream{..} end fin = stateTVar streamRxBounds $ \b -> note b
where
note b@RxBounds{..}
| fin = case rxFinal of
Just f
| f /= end -> (Left FinalSizeChanged, b)
_
| end < rxHighest -> (Left FinalSizeTooSmall, b)
| otherwise -> (Right (end - rxHighest), b{rxFinal = Just end, rxHighest = end})
| otherwise = case rxFinal of
Just f
| end > f -> (Left DataPastFinalSize, b)
_ -> (Right (max 0 (end - rxHighest)), b{rxHighest = max rxHighest end})
-- | The same for the final size a RESET_STREAM carries.
noteRxFinalSize :: Stream -> Int -> IO (Either FinalSizeProblem Int)
noteRxFinalSize s end = noteRxFrame s end True
-- | 'getRxBounds' as part of a transaction.
getRxBoundsSTM :: Stream -> STM RxBounds
getRxBoundsSTM Stream{..} = readTVar streamRxBounds
-- | Counting octets as consumed on the stream's behalf though the
-- application has not read them.
addRxCredited :: Stream -> Int -> IO ()
addRxCredited s n = atomically $ addRxCreditedSTM s n
addRxCreditedSTM :: Stream -> Int -> STM ()
addRxCreditedSTM Stream{..} n =
modifyTVar' streamRxBounds $ \b -> b{rxCredited = rxCredited b + n}
-- | What the connection's flow controller has counted as received for the
-- stream and nobody has counted as consumed, given what the application
-- has consumed: what is owed back once the application is done with it.
--
-- What it read it has already counted itself, in 'recvStream'. The rest --
-- a request body a server answers without reading, or what was lost on the
-- way of a stream whose end is known -- was spent by the peer all the same,
-- and left uncounted the window we advertise stays that much smaller for
-- the rest of the connection.
--
-- Answered once for each octet: what is answered here is counted as
-- credited.
takeRxOwedSTM :: Stream -> Int -> STM Int
takeRxOwedSTM Stream{..} consumed = stateTVar streamRxBounds take'
where
take' b@RxBounds{..}
| owed > 0 = (owed, b{rxCredited = rxCredited + owed})
| otherwise = (0, b)
where
owed = rxHighest - consumed - rxCredited
-- | What the application has read of the stream, as its window counts it.
getRxConsumed :: Stream -> IO Int
getRxConsumed Stream{..} = rxfConsumed <$> readIORef streamFlowRx