http3-0.1.7: Network/QPACK/Table/Dynamic.hs
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
module Network.QPACK.Table.Dynamic (
-- * Dynamic table
DynamicTable,
newDynamicTableForEncoding,
newDynamicTableForDecoding,
-- * Capacity
isTableReady,
getTableCapacity,
getMaxTableCapacity,
setTableCapacity,
setDecoderTableCapacity,
getMaxNumOfEntries,
getMaxEntries,
setMaxEntries,
-- * Entry
insertEntryToDecoder,
insertEntryToEncoder,
toDynamicEntry,
-- * Section
Section (..),
insertSection,
getAndDelSection,
getAndDelSections,
increaseReference,
decreaseReference,
-- * Streams
getMaxBlockedStreams,
setMaxBlockedStreams,
tryIncreaseStreams,
decreaseStreams,
-- * Blocked streams
insertBlockedStreamE,
deleteBlockedStreamE,
unblockStreamsE,
checkBlockedStreams,
-- * Required insert count
getRequiredInsertCount,
clearRequiredInsertCount,
checkRequiredInsertCount,
checkRequiredInsertCountNB,
updateRequiredInsertCount,
-- * Known received count
incrementKnownReceivedCount,
updateKnownReceivedCount,
wouldSectionBeBlocked,
wouldInstructionBeBlocked,
setInsersionPointToKnownReceivedCount,
-- * Points
getBasePoint,
setBasePointToInsersionPoint,
getInsertionPoint,
getInsertionPointSTM,
-- * Draining
isDraining,
adjustDrainingPoint,
checkTailDuplication,
tailDuplication,
duplicate,
tryDrop,
-- * Dropping
canInsertEntry,
-- * Accessing
getLruCache,
getRevIndex,
getHuffmanDecoder,
sendIns,
-- * Max header size
getMaxHeaderSize,
setMaxHeaderSize,
-- * Debug
qpackDebug,
getDebugQPACK,
setDebugQPACK,
printReferences,
checkHIndex,
checkAbsoluteIndex,
-- * QIF
getImmediateAck,
setImmediateAck,
) where
import Control.Concurrent
import Control.Concurrent.STM
import Data.Array.Base (unsafeRead, unsafeWrite)
import Data.Array.IO (IOArray, newArray)
import Data.IORef
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IntMap
import Data.Sequence (Seq, ViewL (..), viewl, (|>))
import qualified Data.Sequence as Seq
import Data.Set (Set)
import qualified Data.Set as Set -- Set.size is O(1), IntSet.size is O(n)
import Imports
import Network.Control
import Network.HPACK.Internal (
Entry,
HuffmanDecoder,
Index,
Size,
decodeH,
dummyEntry,
entryFieldValue,
entryHeaderName,
entrySize,
maxNumbers,
)
import System.IO.Unsafe (unsafePerformIO)
import Network.QPACK.Error
import Network.QPACK.Table.RevIndex
import Network.QPACK.Types
import Network.QUIC (StreamId)
----------------------------------------------------------------
type Table = TArray Index Entry
data Section = Section RequiredInsertCount [AbsoluteIndex]
data Reference = Reference Int Int deriving (Show)
{- FOURMOLU_DISABLE -}
data CodeInfo
= EncodeInfo
{ revIndex :: RevIndex -- Reverse index
, requiredInsertCount :: IORef RequiredInsertCount
, droppingPoint :: IORef AbsoluteIndex
, drainingPoint :: IORef AbsoluteIndex
, knownReceivedCount :: TVar Int
, referenceCounters :: IORef (IOArray Index Reference)
, sections :: IORef (IntMap (Seq Section)) -- oldest first
, lruCache :: LRUCacheRef (FieldName, FieldValue) ()
, immediateAck :: IORef Bool -- for QIF
, blockedStreamsE :: IORef (Set Int)
}
| DecodeInfo
{ huffmanDecoder :: HuffmanDecoder -- only for encoder instruction handler
, blockedStreamsD :: IORef Int
}
{- FOURMOLU_ENABLE -}
-- | Dynamic table for QPACK.
{- FOURMOLU_DISABLE -}
data DynamicTable = DynamicTable
{ codeInfo :: CodeInfo
, insertionPoint :: TVar InsertionPoint
, maxNumOfEntries :: TVar Int
, maxEntries :: IORef Int -- MaxEntries of RFC 9204 section 4.5.1.1
, circularTable :: TVar Table
, basePoint :: IORef BasePoint
, debugQPACK :: IORef Bool
, capaReady :: IORef Bool
, tableSize :: TVar Size
, maxTableSize :: IORef Size
, sendIns :: ByteString -> IO ()
, maxHeaderSize :: IORef Int
, maxBlockedStreams :: IORef Int
}
{- FOURMOLU_ENABLE -}
----------------------------------------------------------------
{- FOURMOLU_DISABLE -}
-- | Creating 'DynamicTable' for encoding.
newDynamicTableForEncoding
:: (ByteString -> IO ())
-> IO DynamicTable
newDynamicTableForEncoding sendEI = do
arr <- newArray (0, 0) $ Reference 0 0
info <- do
revIndex <- newRevIndex
requiredInsertCount <- newIORef 0
droppingPoint <- newIORef 0
drainingPoint <- newIORef 0
knownReceivedCount <- newTVarIO 0
referenceCounters <- newIORef arr
sections <- newIORef IntMap.empty
lruCache <- newLRUCacheRef 0
immediateAck <- newIORef False
blockedStreamsE <- newIORef Set.empty
return EncodeInfo{..}
newDynamicTable info sendEI
{- FOURMOLU_ENABLE -}
-- | Creating 'DynamicTable' for decoding.
newDynamicTableForDecoding
:: Size
-- ^ The size of temporary buffer for Huffman decoding
-> (ByteString -> IO ())
-> IO DynamicTable
newDynamicTableForDecoding huftmpsiz sendDI = do
gcbuf <- mallocPlainForeignPtrBytes huftmpsiz
let huffmanDecoder = decodeH gcbuf huftmpsiz
blockedStreamsD <- newIORef 0
newDynamicTable DecodeInfo{..} sendDI
newDynamicTable :: CodeInfo -> (ByteString -> IO ()) -> IO DynamicTable
newDynamicTable info send = do
tbl <- atomically $ newArray (0, 0) dummyEntry
let codeInfo = info
insertionPoint <- newTVarIO 0
maxNumOfEntries <- newTVarIO 0
maxEntries <- newIORef 0
circularTable <- newTVarIO tbl
basePoint <- newIORef 0
debugQPACK <- newIORef False
capaReady <- newIORef False
tableSize <- newTVarIO 0
maxTableSize <- newIORef 0
let sendIns = send
maxHeaderSize <- newIORef maxBound
maxBlockedStreams <- newIORef 0
return DynamicTable{..}
----------------------------------------------------------------
isTableReady :: DynamicTable -> IO Bool
isTableReady DynamicTable{..} = readIORef capaReady
getTableCapacity :: DynamicTable -> IO Int
getTableCapacity DynamicTable{..} = readTVarIO tableSize
-- | The capacity, as last set. 'getTableCapacity' is how much of it the
-- entries take, whatever its name says.
getMaxTableCapacity :: DynamicTable -> IO Int
getMaxTableCapacity DynamicTable{..} = readIORef maxTableSize
setTableCapacity :: DynamicTable -> Int -> IO ()
setTableCapacity dyntbl@DynamicTable{..} maxsiz = do
qpackDebug dyntbl $ putStrLn $ "setTableCapacity " ++ show maxsiz
when (maxN >= 1) $ do
writeIORef maxTableSize maxsiz
tbl <- atomically $ newArray (0, end) dummyEntry
atomically $ do
writeTVar maxNumOfEntries maxN
writeTVar circularTable tbl
case codeInfo of
EncodeInfo{..} -> do
arr <- newArray (0, end) $ Reference 0 0
writeIORef referenceCounters arr
setLRUCapacity lruCache (maxN * 4)
_ -> return ()
writeIORef capaReady True
where
maxN = maxNumbers maxsiz
end = maxN - 1
-- | Setting the capacity of the decoder's table, as the encoder instructs.
--
-- The encoder may change the capacity whenever it likes, within the maximum
-- the decoder announced (RFC 9204, section 3.2.3), and the entries in the
-- table stay where they are. So the ring is allocated once, for as many
-- entries as the maximum allows ('setMaxEntries'), and a later change only
-- moves the capacity. Reallocating it, as 'setTableCapacity' does, emptied
-- the table and changed what the absolute indices map onto: references to
-- entries inserted before the change came back as a dummy entry.
setDecoderTableCapacity :: DynamicTable -> Int -> IO ()
setDecoderTableCapacity dyntbl@DynamicTable{..} cap = do
qpackDebug dyntbl $ putStrLn $ "setDecoderTableCapacity " ++ show cap
maxN <- readIORef maxEntries
allocated <- (>= 1) <$> readTVarIO maxNumOfEntries
when (not allocated && maxN >= 1) $ do
tbl <- atomically $ newArray (0, maxN - 1) dummyEntry
atomically $ do
writeTVar maxNumOfEntries maxN
writeTVar circularTable tbl
writeIORef maxTableSize cap
-- No entry is smaller than 32, so below that nothing can be inserted.
writeIORef capaReady (maxN >= 1 && maxNumbers cap >= 1)
getMaxNumOfEntries :: DynamicTable -> IO Int
getMaxNumOfEntries DynamicTable{..} = readTVarIO maxNumOfEntries
-- | MaxEntries, which the Required Insert Count of a field section is
-- encoded and decoded against (RFC 9204, section 4.5.1.1).
--
-- It comes from the decoder's SETTINGS_QPACK_MAX_TABLE_CAPACITY, not from the
-- capacity the encoder actually set. The two ends agree on the former; the
-- latter is only the encoder's choice within it, and using it here made the
-- two ends disagree once more than 2*MaxEntries entries had been inserted.
getMaxEntries :: DynamicTable -> IO Int
getMaxEntries DynamicTable{..} = readIORef maxEntries
-- | Setting MaxEntries from the decoder's maximum table capacity.
setMaxEntries :: DynamicTable -> Int -> IO ()
setMaxEntries DynamicTable{..} maxCapacity =
writeIORef maxEntries $ maxNumbers maxCapacity
----------------------------------------------------------------
insertEntryToEncoder :: Entry -> DynamicTable -> IO AbsoluteIndex
insertEntryToEncoder ent dyntbl@DynamicTable{..} = do
InsertionPoint insp <- atomically $ do
x <- readTVar insertionPoint
writeTVar insertionPoint (x + 1)
return x
maxN <- readTVarIO maxNumOfEntries
let i = insp `mod` maxN
table <- readTVarIO circularTable
atomically $ unsafeWrite table i ent
let revtbl = getRevIndex dyntbl
let ai = AbsoluteIndex insp
insertRevIndex ent ai revtbl
atomically $ modifyTVar' tableSize (+ entrySize ent)
dropIfNecessary dyntbl
resetReference dyntbl ai
return ai
insertEntryToDecoder :: Entry -> DynamicTable -> STM AbsoluteIndex
insertEntryToDecoder ent DynamicTable{..} = do
x@(InsertionPoint insp) <- readTVar insertionPoint
writeTVar insertionPoint (x + 1)
maxN <- readTVar maxNumOfEntries
let i = insp `mod` maxN
table <- readTVar circularTable
unsafeWrite table i ent
modifyTVar' tableSize (+ entrySize ent)
return $ AbsoluteIndex insp
-- | Look up an entry of the dynamic table by absolute index.
--
-- The live window is the most recent 'maxNumOfEntries' insertions. An index
-- outside it names nothing, and the @mod@ below would fold it back into the
-- table and hand over whichever slot it landed on -- a header nobody sent.
-- RFC 9204 section 2.1.2 asks for an error instead, and this one reaches the
-- peer as QPACK_DECOMPRESSION_FAILED from a field section, or as
-- QPACK_ENCODER_STREAM_ERROR from an encoder instruction.
--
-- The window is empty until a capacity has been set, which is also what keeps
-- the @mod@ from dividing by zero: 'maxNumOfEntries' starts at 0 and a field
-- section can reach here before the peer has sent us a capacity at all.
toDynamicEntry :: DynamicTable -> AbsoluteIndex -> STM Entry
toDynamicEntry DynamicTable{..} (AbsoluteIndex idx) = do
maxN <- readTVar maxNumOfEntries
InsertionPoint ip <- readTVar insertionPoint
-- @max 0@ because early on the table holds fewer than maxN entries and
-- @ip - maxN@ is negative, which would let a negative index through; the
-- index conversions produce those readily, since a pre-base index larger
-- than the base gives one.
let lo = max 0 (ip - maxN)
when (maxN == 0 || idx < lo || idx >= ip) $
throwSTM $
IllegalDynamicIndex idx
table <- readTVar circularTable
unsafeRead table (idx `mod` maxN)
----------------------------------------------------------------
-- | Registering a field section which awaits acknowledgement.
--
-- A stream can have more than one outstanding -- headers and trailers, say --
-- and a Section Acknowledgement is for the oldest of them (RFC 9204, section
-- 4.4.1), so they are queued rather than keyed by the stream alone. Keyed
-- alone, the second replaced the first, whose references were then never
-- released, and the second acknowledgement found nothing and was taken for a
-- decoder stream error.
insertSection :: DynamicTable -> StreamId -> Section -> IO ()
insertSection DynamicTable{..} sid section = atomicModifyIORef' sections ins
where
ins m =
let m' = IntMap.insertWith (\_ old -> old |> section) sid (Seq.singleton section) m
in (m', ())
EncodeInfo{..} = codeInfo
-- | Taking out the oldest outstanding field section of a stream.
getAndDelSection :: DynamicTable -> StreamId -> IO (Maybe Section)
getAndDelSection DynamicTable{..} sid = atomicModifyIORef' sections getAndDel
where
getAndDel m = case IntMap.lookup sid m of
Nothing -> (m, Nothing)
Just q -> case viewl q of
EmptyL -> (IntMap.delete sid m, Nothing)
sec :< rest
| Seq.null rest -> (IntMap.delete sid m, Just sec)
| otherwise -> (IntMap.insert sid rest m, Just sec)
EncodeInfo{..} = codeInfo
-- | Taking out every outstanding field section of a stream.
getAndDelSections :: DynamicTable -> StreamId -> IO [Section]
getAndDelSections DynamicTable{..} sid = atomicModifyIORef' sections getAndDel
where
getAndDel m = case IntMap.lookup sid m of
Nothing -> (m, [])
Just q -> (IntMap.delete sid m, toList q)
EncodeInfo{..} = codeInfo
increaseReference :: DynamicTable -> AbsoluteIndex -> IO ()
increaseReference = modifyReference $ \(Reference c t) -> Reference (c + 1) (t + 1)
decreaseReference :: DynamicTable -> AbsoluteIndex -> IO ()
decreaseReference = modifyReference $ \(Reference c t) -> Reference (c - 1) t
modifyReference
:: (Reference -> Reference) -> DynamicTable -> AbsoluteIndex -> IO ()
modifyReference func DynamicTable{..} (AbsoluteIndex idx) = do
maxN <- readTVarIO maxNumOfEntries
let i = idx `mod` maxN
arr <- readIORef referenceCounters
-- modifyArray' is not provided by GHC 9.4 or earlier, sigh.
x <- unsafeRead arr i
let x' = func x
unsafeWrite arr i x'
where
EncodeInfo{..} = codeInfo
resetReference :: DynamicTable -> AbsoluteIndex -> IO ()
resetReference DynamicTable{..} (AbsoluteIndex idx) = do
maxN <- readTVarIO maxNumOfEntries
let i = idx `mod` maxN
arr <- readIORef referenceCounters
unsafeWrite arr i $ Reference 0 0
where
EncodeInfo{..} = codeInfo
----------------------------------------------------------------
getMaxBlockedStreams :: DynamicTable -> IO Int
getMaxBlockedStreams DynamicTable{..} = readIORef maxBlockedStreams
setMaxBlockedStreams :: DynamicTable -> Int -> IO ()
setMaxBlockedStreams DynamicTable{..} n = writeIORef maxBlockedStreams n
-- Decoder
tryIncreaseStreams :: DynamicTable -> IO Bool
tryIncreaseStreams DynamicTable{..} = do
lim <- readIORef maxBlockedStreams
-- Counted only if it is let in, since nothing takes a refused one off.
atomicModifyIORef' blockedStreamsD $ \n ->
if n < lim then (n + 1, True) else (n, False)
where
DecodeInfo{..} = codeInfo
decreaseStreams :: DynamicTable -> IO ()
decreaseStreams DynamicTable{..} = atomicModifyIORef' blockedStreamsD (\n -> (n - 1, ()))
where
DecodeInfo{..} = codeInfo
----------------------------------------------------------------
getBlockedStreamsE :: DynamicTable -> IO Int
getBlockedStreamsE DynamicTable{..} =
Set.size <$> readIORef blockedStreamsE
where
EncodeInfo{..} = codeInfo
insertBlockedStreamE :: DynamicTable -> StreamId -> IO ()
insertBlockedStreamE DynamicTable{..} sid =
atomicModifyIORef' blockedStreamsE (\set -> (Set.insert sid set, ()))
where
EncodeInfo{..} = codeInfo
deleteBlockedStreamE :: DynamicTable -> StreamId -> IO ()
deleteBlockedStreamE DynamicTable{..} sid =
atomicModifyIORef' blockedStreamsE (\set -> (Set.delete sid set, ()))
where
EncodeInfo{..} = codeInfo
-- | Forgetting the blocked streams that the known received count has caught
-- up with.
--
-- A stream stops being blocked once the decoder holds every entry its
-- section refers to (RFC 9204, section 2.1.2), which an Insert Count
-- Increment can say as well as a Section Acknowledgement. Waiting for the
-- acknowledgement alone would be safe but would keep a stream counted for as
-- long as it is never acknowledged -- forever, for one that was reset.
unblockStreamsE :: DynamicTable -> IO ()
unblockStreamsE DynamicTable{..} = do
krc <- readTVarIO knownReceivedCount
secs <- readIORef sections
let blocking (Section (RequiredInsertCount ric) _) = ric > krc
stillBlocked sid = maybe False (any blocking) $ IntMap.lookup sid secs
atomicModifyIORef' blockedStreamsE (\set -> (Set.filter stillBlocked set, ()))
where
EncodeInfo{..} = codeInfo
checkBlockedStreams :: DynamicTable -> IO Bool
checkBlockedStreams dyntbl = do
maxBlocked <- getMaxBlockedStreams dyntbl
blocked <- getBlockedStreamsE dyntbl
-- The next one would be blocked, so <, not <=
return $ blocked < maxBlocked
----------------------------------------------------------------
getRequiredInsertCount :: DynamicTable -> IO RequiredInsertCount
getRequiredInsertCount DynamicTable{..} = readIORef requiredInsertCount
where
EncodeInfo{..} = codeInfo
clearRequiredInsertCount :: DynamicTable -> IO ()
clearRequiredInsertCount DynamicTable{..} = writeIORef requiredInsertCount 0
where
EncodeInfo{..} = codeInfo
checkRequiredInsertCount :: DynamicTable -> RequiredInsertCount -> IO ()
checkRequiredInsertCount DynamicTable{..} (RequiredInsertCount reqip) = atomically $ do
InsertionPoint ip <- readTVar insertionPoint
-- RequiredInsertCount is index + 1
-- InsertionPoin is index + 1
-- So, equal is necessary.
check (reqip <= ip)
checkRequiredInsertCountNB :: DynamicTable -> RequiredInsertCount -> IO Bool
checkRequiredInsertCountNB DynamicTable{..} (RequiredInsertCount reqip) = atomically $ do
InsertionPoint ip <- readTVar insertionPoint
return (reqip <= ip)
absoluteIndexToRequiredInsertCount :: AbsoluteIndex -> RequiredInsertCount
absoluteIndexToRequiredInsertCount (AbsoluteIndex idx) =
RequiredInsertCount (idx + 1)
updateRequiredInsertCount :: DynamicTable -> AbsoluteIndex -> IO ()
updateRequiredInsertCount DynamicTable{..} aidx = do
let newric = absoluteIndexToRequiredInsertCount aidx
oldric <- readIORef requiredInsertCount
when (newric > oldric) $ writeIORef requiredInsertCount newric
where
EncodeInfo{..} = codeInfo
----------------------------------------------------------------
incrementKnownReceivedCount :: DynamicTable -> Int -> IO ()
incrementKnownReceivedCount DynamicTable{..} n =
atomically $ modifyTVar' knownReceivedCount (+ n)
where
EncodeInfo{..} = codeInfo
updateKnownReceivedCount :: DynamicTable -> RequiredInsertCount -> IO ()
updateKnownReceivedCount DynamicTable{..} (RequiredInsertCount reqInsCnt) =
atomically $ modifyTVar' knownReceivedCount $ \krc -> max reqInsCnt krc
where
EncodeInfo{..} = codeInfo
wouldSectionBeBlocked :: DynamicTable -> RequiredInsertCount -> IO Bool
wouldSectionBeBlocked DynamicTable{..} (RequiredInsertCount reqip) = atomically $ do
krc <- readTVar knownReceivedCount
return (reqip > krc)
where
EncodeInfo{..} = codeInfo
wouldInstructionBeBlocked :: DynamicTable -> AbsoluteIndex -> IO Bool
wouldInstructionBeBlocked DynamicTable{..} (AbsoluteIndex ai) = atomically $ do
krc <- readTVar knownReceivedCount
-- The decoder is known to hold entries 0 .. krc-1, so a reference to
-- krc itself is still one it may not have. This is the same test as
-- 'wouldSectionBeBlocked', whose Required Insert Count is ai + 1.
return (ai >= krc)
where
EncodeInfo{..} = codeInfo
setInsersionPointToKnownReceivedCount :: DynamicTable -> IO ()
setInsersionPointToKnownReceivedCount dyntbl@DynamicTable{..} = do
InsertionPoint ai <- getInsertionPoint dyntbl
atomically $ writeTVar knownReceivedCount ai
where
EncodeInfo{..} = codeInfo
----------------------------------------------------------------
getBasePoint :: DynamicTable -> IO BasePoint
getBasePoint DynamicTable{..} = readIORef basePoint
setBasePointToInsersionPoint :: DynamicTable -> IO ()
setBasePointToInsersionPoint DynamicTable{..} = do
InsertionPoint ip <- readTVarIO insertionPoint
writeIORef basePoint $ BasePoint ip
getInsertionPoint :: DynamicTable -> IO InsertionPoint
getInsertionPoint DynamicTable{..} = readTVarIO insertionPoint
getInsertionPointSTM :: DynamicTable -> STM InsertionPoint
getInsertionPointSTM DynamicTable{..} = readTVar insertionPoint
----------------------------------------------------------------
isDraining :: DynamicTable -> AbsoluteIndex -> IO Bool
isDraining DynamicTable{..} ai = do
di <- readIORef drainingPoint
return (ai <= di)
where
EncodeInfo{..} = codeInfo
adjustDrainingPoint :: DynamicTable -> IO ()
adjustDrainingPoint DynamicTable{..} = do
InsertionPoint beg <- readTVarIO insertionPoint
AbsoluteIndex end <- readIORef droppingPoint
let num = beg - end
space = max 1 (num !>>. 4)
end' = beg - num + space
writeIORef drainingPoint $ AbsoluteIndex end'
table <- readTVarIO circularTable
maxN <- readTVarIO maxNumOfEntries
loop end end' table maxN
where
EncodeInfo{..} = codeInfo
loop :: Int -> Int -> Table -> Int -> IO ()
loop ai lim table maxN
| ai == lim = return ()
| otherwise = do
let i = ai `mod` maxN
ent <- atomically $ unsafeRead table i
deleteRevIndex revIndex ent $ AbsoluteIndex ai
loop (ai + 1) lim table maxN
tailDuplicationThreshold :: Int
tailDuplicationThreshold = 10
checkTailDuplication :: DynamicTable -> IO (Maybe AbsoluteIndex)
checkTailDuplication DynamicTable{..} = do
dai@(AbsoluteIndex ai) <- readIORef droppingPoint
arr <- readIORef referenceCounters
maxN <- readTVarIO maxNumOfEntries
let i = ai `mod` maxN
-- modifyArray' is not provided by GHC 9.4 or earlier, sigh.
ref@(Reference _ total) <- unsafeRead arr i
krc <- readTVarIO knownReceivedCount
-- Duplicating it evicts it, so it has to be evictable.
if evictable krc ai ref && total >= tailDuplicationThreshold
then return $ Just dai
else return Nothing
where
EncodeInfo{..} = codeInfo
tailDuplication :: DynamicTable -> IO AbsoluteIndex
tailDuplication dyntbl@DynamicTable{..} = do
dai <- readIORef droppingPoint
ndai <- duplicate dyntbl dai
dropIfNecessary dyntbl
return ndai
where
EncodeInfo{..} = codeInfo
duplicate :: DynamicTable -> AbsoluteIndex -> IO AbsoluteIndex
duplicate dyntbl@DynamicTable{..} (AbsoluteIndex ai) = do
maxN <- readTVarIO maxNumOfEntries
let i = ai `mod` maxN
table <- readTVarIO circularTable
ent <- atomically $ unsafeRead table i
insertEntryToEncoder ent dyntbl
----------------------------------------------------------------
-- | Whether the entry at an absolute index may be evicted, given the Known
-- Received Count (RFC 9204, section 2.1.1): once its insertion has been
-- acknowledged and no unacknowledged field section refers to it.
--
-- Whether it was ever referred to has nothing to do with it. This used to
-- ask for that instead of the acknowledgement, which kept an entry nothing
-- referred to for good -- and, since entries go oldest first, every entry
-- after it too, so that the table filled up and took nothing new for the
-- rest of the connection. An encoder inserts such an entry whenever it may
-- not block another stream and so writes the field out literally.
evictable :: Int -> Int -> Reference -> Bool
evictable krc ai (Reference current _) = current == 0 && ai < krc
canInsertEntry :: DynamicTable -> Entry -> Maybe AbsoluteIndex -> IO Bool
canInsertEntry DynamicTable{..} ent mai = do
let siz = entrySize ent
tblsiz <- readTVarIO tableSize
maxtblsiz <- readIORef maxTableSize
if tblsiz + siz <= maxtblsiz
then
return True
else do
AbsoluteIndex ai <- readIORef droppingPoint
InsertionPoint lim <- readTVarIO insertionPoint
loop ai lim (tblsiz + siz - maxtblsiz)
where
myself = case mai of
Nothing -> -1 -- trick: this index does not exist
Just (AbsoluteIndex i) -> i
EncodeInfo{..} = codeInfo
loop ai lim requiredSize
| requiredSize <= 0 = return True
| otherwise = do
if ai < lim && ai /= myself -- don't drop the referred entry
then do
maxN <- readTVarIO maxNumOfEntries
let i = ai `mod` maxN
refs <- readIORef referenceCounters
ref <- unsafeRead refs i
krc <- readTVarIO knownReceivedCount
if evictable krc ai ref
then do
table <- readTVarIO circularTable
dent <- atomically $ unsafeRead table i
let siz = entrySize dent
loop (ai + 1) lim (requiredSize - siz)
else return False
else return False
dropIfNecessary :: DynamicTable -> IO ()
dropIfNecessary dyntbl@DynamicTable{..} = loop
where
loop = do
tblsize <- readTVarIO tableSize
maxtblsize <- readIORef maxTableSize
unless (tblsize <= maxtblsize) $ do
dropped <- tryDrop dyntbl
if dropped then loop else error "dropIfNecessary"
tryDrop :: DynamicTable -> IO Bool
tryDrop dyntbl@DynamicTable{..} = do
maxN <- readTVarIO maxNumOfEntries
dai@(AbsoluteIndex ai) <- readIORef droppingPoint
InsertionPoint lim <- readTVarIO insertionPoint
if ai < lim
then do
let i = ai `mod` maxN
refs <- readIORef referenceCounters
ref <- unsafeRead refs i
krc <- readTVarIO knownReceivedCount
if evictable krc ai ref
then do
table <- readTVarIO circularTable
ent <- atomically $ do
e <- unsafeRead table i
unsafeWrite table i dummyEntry
return e
let siz = entrySize ent
atomically $ modifyTVar' tableSize $ subtract siz
modifyIORef' droppingPoint (+ 1)
qpackDebug dyntbl $ do
putStrLn $
"DROPPED (AbsoluteIndex "
++ show ai
++ ") "
++ show (entryHeaderName ent)
++ " "
++ show (entryFieldValue ent)
tblsiz <- readTVarIO tableSize
putStrLn $ " tblsiz: " ++ show tblsiz
deleteRevIndex revIndex ent dai
return True
else return False
else return False
where
EncodeInfo{..} = codeInfo
----------------------------------------------------------------
getLruCache :: DynamicTable -> LRUCacheRef (FieldName, FieldValue) ()
getLruCache DynamicTable{..} = lruCache
where
EncodeInfo{..} = codeInfo
{-# INLINE getRevIndex #-}
getRevIndex :: DynamicTable -> RevIndex
getRevIndex DynamicTable{..} = revIndex
where
EncodeInfo{..} = codeInfo
-- only for encoder instruction handler
getHuffmanDecoder :: DynamicTable -> HuffmanDecoder
getHuffmanDecoder DynamicTable{..} = huffmanDecoder
where
DecodeInfo{..} = codeInfo
----------------------------------------------------------------
getMaxHeaderSize :: DynamicTable -> IO Int
getMaxHeaderSize DynamicTable{..} = readIORef maxHeaderSize
setMaxHeaderSize :: DynamicTable -> Int -> IO ()
setMaxHeaderSize DynamicTable{..} n = writeIORef maxHeaderSize n
----------------------------------------------------------------
qpackDebug :: DynamicTable -> IO () -> IO ()
qpackDebug DynamicTable{..} action = do
debug <- readIORef debugQPACK
when debug $ withMVar stdoutLock $ \_ -> action
getDebugQPACK :: DynamicTable -> IO Bool
getDebugQPACK DynamicTable{..} = readIORef debugQPACK
setDebugQPACK :: DynamicTable -> Bool -> IO ()
setDebugQPACK DynamicTable{..} b = writeIORef debugQPACK b
{-# NOINLINE stdoutLock #-}
stdoutLock :: MVar ()
stdoutLock = unsafePerformIO $ newMVar ()
printReferences :: DynamicTable -> IO ()
printReferences DynamicTable{..} = do
AbsoluteIndex start <- readIORef droppingPoint
InsertionPoint end <- readTVarIO insertionPoint
maxN <- readTVarIO maxNumOfEntries
arr <- readIORef referenceCounters
putStr "Refs: "
loop start end arr maxN
putStr "\n"
where
loop :: Int -> Int -> IOArray Index Reference -> Int -> IO ()
loop start end arr maxN
| start < end = do
r <- unsafeRead arr (start `mod` maxN)
putStr $ show start ++ ": " ++ showReference r ++ ", "
loop (start + 1) end arr maxN
| otherwise = return ()
EncodeInfo{..} = codeInfo
showReference (Reference c t) = show c ++ "(" ++ show t ++ ")"
-- For decoder
checkHIndex :: DynamicTable -> HIndex -> IO ()
checkHIndex _ (SIndex _) = return ()
checkHIndex DynamicTable{..} (DIndex (AbsoluteIndex ai)) = do
InsertionPoint ip <- readTVarIO insertionPoint
maxN <- readTVarIO maxNumOfEntries
if ip - maxN <= ai && ai < ip
then return ()
else error "checkHIndex"
-- For encoder
checkAbsoluteIndex :: DynamicTable -> AbsoluteIndex -> String -> IO ()
checkAbsoluteIndex DynamicTable{..} (AbsoluteIndex ai) tag = do
InsertionPoint beg <- readTVarIO insertionPoint
AbsoluteIndex end <- readIORef droppingPoint
maxN <- readTVarIO maxNumOfEntries
table <- readTVarIO circularTable
let calcSize i acc
| i == beg = return acc
| otherwise = do
siz <- entrySize <$> atomically (unsafeRead table (i `mod` maxN))
calcSize (i + 1) (acc + siz)
if end <= ai && ai < beg
then do
size <- calcSize end 0
size0 <- readTVarIO tableSize
when (size /= size0) $ error $ "checkAbsoluteIndex: size /= size0) " ++ tag
lim <- readIORef maxTableSize
when (size > lim) $ error $ "checkAbsoluteIndex: size > lim " ++ tag
putStrLn $ " check: tblsiz: " ++ show size ++ " " ++ show ai ++ " " ++ tag
else
error $
"checkAbsoluteIndex (3) "
++ tag
++ " <= "
++ show end
++ " "
++ show ai
++ " < "
++ show beg
where
EncodeInfo{..} = codeInfo
----------------------------------------------------------------
getImmediateAck :: DynamicTable -> IO Bool
getImmediateAck DynamicTable{..} = readIORef immediateAck
where
EncodeInfo{..} = codeInfo
setImmediateAck :: DynamicTable -> Bool -> IO ()
setImmediateAck DynamicTable{..} b = writeIORef immediateAck b
where
EncodeInfo{..} = codeInfo