packages feed

http3 0.0.24 → 0.1.0

raw patch · 39 files changed

+2320/−880 lines, 39 filesdep +cerealdep +network-controldep +psqueuesdep ~quicdep ~time-managernew-component:exe:qif-decnew-component:exe:qif-encPVP ok

version bump matches the API change (PVP)

Dependencies added: cereal, network-control, psqueues

Dependency ranges changed: quic, time-manager

API changes (from Hackage documentation)

- Network.HQ.Client: ClientConfig :: Scheme -> Authority -> ClientConfig
- Network.HQ.Client: [authority] :: ClientConfig -> Authority
- Network.HQ.Client: [scheme] :: ClientConfig -> Scheme
- Network.HQ.Client: type Authority = String
- Network.HTTP3.Client: ClientConfig :: Scheme -> Authority -> ClientConfig
- Network.HTTP3.Client: [authority] :: ClientConfig -> Authority
- Network.HTTP3.Client: [scheme] :: ClientConfig -> Scheme
- Network.QPACK: EncodeStrategy :: CompressionAlgo -> Bool -> EncodeStrategy
- Network.QPACK: Linear :: CompressionAlgo
- Network.QPACK: Naive :: CompressionAlgo
- Network.QPACK: Static :: CompressionAlgo
- Network.QPACK: [compressionAlgo] :: EncodeStrategy -> CompressionAlgo
- Network.QPACK: [dcDynamicTableSize] :: QDecoderConfig -> Size
- Network.QPACK: [ecDynamicTableSize] :: QEncoderConfig -> Size
- Network.QPACK: [ecPrefixBufferSize] :: QEncoderConfig -> Size
- Network.QPACK: [encStrategy] :: QEncoderConfig -> EncodeStrategy
- Network.QPACK: [useHuffman] :: EncodeStrategy -> Bool
- Network.QPACK: data CompressionAlgo
- Network.QPACK: data EncodeStrategy
- Network.QPACK.Internal: EncodeStrategy :: CompressionAlgo -> Bool -> EncodeStrategy
- Network.QPACK.Internal: HBRelativeIndex :: Int -> HBRelativeIndex
- Network.QPACK.Internal: Linear :: CompressionAlgo
- Network.QPACK.Internal: Naive :: CompressionAlgo
- Network.QPACK.Internal: Static :: CompressionAlgo
- Network.QPACK.Internal: [compressionAlgo] :: EncodeStrategy -> CompressionAlgo
- Network.QPACK.Internal: [useHuffman] :: EncodeStrategy -> Bool
- Network.QPACK.Internal: checkInsertionPoint :: DynamicTable -> InsertionPoint -> IO ()
- Network.QPACK.Internal: data CompressionAlgo
- Network.QPACK.Internal: data EncodeStrategy
- Network.QPACK.Internal: fromHBRelativeIndex :: HBRelativeIndex -> BasePoint -> AbsoluteIndex
- Network.QPACK.Internal: getLargestReference :: DynamicTable -> IO InsertionPoint
- Network.QPACK.Internal: getSendDI :: DynamicTable -> ByteString -> IO ()
- Network.QPACK.Internal: newtype HBRelativeIndex
- Network.QPACK.Internal: toHBRelativeIndex :: AbsoluteIndex -> BasePoint -> HBRelativeIndex
- Network.QPACK.Internal: updateLargestReference :: DynamicTable -> AbsoluteIndex -> IO ()
+ Network.HQ.Client: [confQDecoderConfig] :: Config -> QDecoderConfig
+ Network.HQ.Client: [confQEncoderConfig] :: Config -> QEncoderConfig
+ Network.HQ.Client: defaultClientConfig :: ClientConfig
+ Network.HQ.Client: scheme :: ClientConfig -> Scheme
+ Network.HQ.Server: [confQDecoderConfig] :: Config -> QDecoderConfig
+ Network.HQ.Server: [confQEncoderConfig] :: Config -> QEncoderConfig
+ Network.HTTP3.Client: [confQDecoderConfig] :: Config -> QDecoderConfig
+ Network.HTTP3.Client: [confQEncoderConfig] :: Config -> QEncoderConfig
+ Network.HTTP3.Client: authority :: ClientConfig -> Authority
+ Network.HTTP3.Client: dcHuffmanBufferSize :: QDecoderConfig -> Size
+ Network.HTTP3.Client: dcMaxTableCapacity :: QDecoderConfig -> Size
+ Network.HTTP3.Client: defaultClientConfig :: ClientConfig
+ Network.HTTP3.Client: defaultConfig :: Config
+ Network.HTTP3.Client: defaultQDecoderConfig :: QDecoderConfig
+ Network.HTTP3.Client: defaultQEncoderConfig :: QEncoderConfig
+ Network.HTTP3.Client: ecHeaderBlockBufferSize :: QEncoderConfig -> Size
+ Network.HTTP3.Client: ecInstructionBufferSize :: QEncoderConfig -> Size
+ Network.HTTP3.Client: ecMaxTableCapacity :: QEncoderConfig -> Size
+ Network.HTTP3.Client: scheme :: ClientConfig -> Scheme
+ Network.HTTP3.Server: [confQDecoderConfig] :: Config -> QDecoderConfig
+ Network.HTTP3.Server: [confQEncoderConfig] :: Config -> QEncoderConfig
+ Network.HTTP3.Server: dcHuffmanBufferSize :: QDecoderConfig -> Size
+ Network.HTTP3.Server: dcMaxTableCapacity :: QDecoderConfig -> Size
+ Network.HTTP3.Server: defaultConfig :: Config
+ Network.HTTP3.Server: defaultQDecoderConfig :: QDecoderConfig
+ Network.HTTP3.Server: defaultQEncoderConfig :: QEncoderConfig
+ Network.HTTP3.Server: ecHeaderBlockBufferSize :: QEncoderConfig -> Size
+ Network.HTTP3.Server: ecInstructionBufferSize :: QEncoderConfig -> Size
+ Network.HTTP3.Server: ecMaxTableCapacity :: QEncoderConfig -> Size
+ Network.QPACK: TableOperation :: (Int -> IO ()) -> (Int -> IO ()) -> (Int -> IO ()) -> TableOperation
+ Network.QPACK: [dcBlockedSterams] :: QDecoderConfig -> Int
+ Network.QPACK: [dcMaxFieldSectionSize] :: QDecoderConfig -> Int
+ Network.QPACK: [dcMaxTableCapacity] :: QDecoderConfig -> Size
+ Network.QPACK: [ecMaxTableCapacity] :: QEncoderConfig -> Size
+ Network.QPACK: [setBlockedStreams] :: TableOperation -> Int -> IO ()
+ Network.QPACK: [setCapacity] :: TableOperation -> Int -> IO ()
+ Network.QPACK: [setHeaderSize] :: TableOperation -> Int -> IO ()
+ Network.QPACK: data TableOperation
+ Network.QPACK: newQEncoderS :: QEncoderConfig -> (EncodedEncoderInstruction -> IO ()) -> Int -> Bool -> Bool -> IO QEncoderS
+ Network.QPACK: type QEncoderS = StreamId -> [Header] -> IO EncodedFieldSection
+ Network.QPACK.Internal: BlockedStreamsOverflow :: DecodeError
+ Network.QPACK.Internal: PreBaseIndex :: Int -> PreBaseIndex
+ Network.QPACK.Internal: RequiredInsertCount :: Int -> RequiredInsertCount
+ Network.QPACK.Internal: Section :: RequiredInsertCount -> [AbsoluteIndex] -> Section
+ Network.QPACK.Internal: adjustDrainingPoint :: DynamicTable -> IO ()
+ Network.QPACK.Internal: canInsertEntry :: DynamicTable -> Entry -> Maybe AbsoluteIndex -> IO Bool
+ Network.QPACK.Internal: checkAbsoluteIndex :: DynamicTable -> AbsoluteIndex -> String -> IO ()
+ Network.QPACK.Internal: checkBlockedStreams :: DynamicTable -> IO Bool
+ Network.QPACK.Internal: checkHIndex :: DynamicTable -> HIndex -> IO ()
+ Network.QPACK.Internal: checkRequiredInsertCount :: DynamicTable -> RequiredInsertCount -> IO ()
+ Network.QPACK.Internal: checkRequiredInsertCountNB :: DynamicTable -> RequiredInsertCount -> IO Bool
+ Network.QPACK.Internal: checkTailDuplication :: DynamicTable -> IO (Maybe AbsoluteIndex)
+ Network.QPACK.Internal: clearRequiredInsertCount :: DynamicTable -> IO ()
+ Network.QPACK.Internal: data Section
+ Network.QPACK.Internal: decreaseReference :: DynamicTable -> AbsoluteIndex -> IO ()
+ Network.QPACK.Internal: decreaseStreams :: DynamicTable -> IO ()
+ Network.QPACK.Internal: deleteBlockedStreamE :: DynamicTable -> StreamId -> IO ()
+ Network.QPACK.Internal: deleteRevIndex :: RevIndex -> Entry -> AbsoluteIndex -> IO ()
+ Network.QPACK.Internal: duplicate :: DynamicTable -> AbsoluteIndex -> IO AbsoluteIndex
+ Network.QPACK.Internal: fromPreBaseIndex :: PreBaseIndex -> BasePoint -> AbsoluteIndex
+ Network.QPACK.Internal: getAndDelSection :: DynamicTable -> StreamId -> IO (Maybe Section)
+ Network.QPACK.Internal: getImmediateAck :: DynamicTable -> IO Bool
+ Network.QPACK.Internal: getLruCache :: DynamicTable -> LRUCacheRef (FieldName, FieldValue) ()
+ Network.QPACK.Internal: getMaxBlockedStreams :: DynamicTable -> IO Int
+ Network.QPACK.Internal: getMaxHeaderSize :: DynamicTable -> IO Int
+ Network.QPACK.Internal: getRequiredInsertCount :: DynamicTable -> IO RequiredInsertCount
+ Network.QPACK.Internal: getTableCapacity :: DynamicTable -> IO Int
+ Network.QPACK.Internal: increaseReference :: DynamicTable -> AbsoluteIndex -> IO ()
+ Network.QPACK.Internal: incrementKnownReceivedCount :: DynamicTable -> Int -> IO ()
+ Network.QPACK.Internal: insertBlockedStreamE :: DynamicTable -> StreamId -> IO ()
+ Network.QPACK.Internal: insertRevIndex :: Entry -> AbsoluteIndex -> RevIndex -> IO ()
+ Network.QPACK.Internal: insertSection :: DynamicTable -> StreamId -> Section -> IO ()
+ Network.QPACK.Internal: isDraining :: DynamicTable -> AbsoluteIndex -> IO Bool
+ Network.QPACK.Internal: isKeyRegistered :: FieldName -> RevIndex -> IO (Maybe AbsoluteIndex)
+ Network.QPACK.Internal: isTableReady :: DynamicTable -> IO Bool
+ Network.QPACK.Internal: lookupRevIndex' :: Token -> FieldValue -> RevResult
+ Network.QPACK.Internal: lookupRevIndexS :: Token -> FieldValue -> RevResult
+ Network.QPACK.Internal: newRevIndex :: IO RevIndex
+ Network.QPACK.Internal: newtype PreBaseIndex
+ Network.QPACK.Internal: newtype RequiredInsertCount
+ Network.QPACK.Internal: printReferences :: DynamicTable -> IO ()
+ Network.QPACK.Internal: renewRevIndex :: RevIndex -> IO ()
+ Network.QPACK.Internal: set00000 :: Setter
+ Network.QPACK.Internal: setImmediateAck :: DynamicTable -> Bool -> IO ()
+ Network.QPACK.Internal: setInsersionPointToKnownReceivedCount :: DynamicTable -> IO ()
+ Network.QPACK.Internal: setMaxBlockedStreams :: DynamicTable -> Int -> IO ()
+ Network.QPACK.Internal: setMaxHeaderSize :: DynamicTable -> Int -> IO ()
+ Network.QPACK.Internal: setTableCapacity :: DynamicTable -> Int -> IO ()
+ Network.QPACK.Internal: tailDuplication :: DynamicTable -> IO AbsoluteIndex
+ Network.QPACK.Internal: toBaseIndex :: AbsoluteIndex -> BasePoint -> Either PreBaseIndex PostBaseIndex
+ Network.QPACK.Internal: toDynamicEntry :: DynamicTable -> AbsoluteIndex -> STM Entry
+ Network.QPACK.Internal: toPreBaseIndex :: AbsoluteIndex -> BasePoint -> PreBaseIndex
+ Network.QPACK.Internal: tokenToStaticIndex :: Token -> Maybe AbsoluteIndex
+ Network.QPACK.Internal: tryDrop :: DynamicTable -> IO Bool
+ Network.QPACK.Internal: tryIncreaseStreams :: DynamicTable -> IO Bool
+ Network.QPACK.Internal: updateKnownReceivedCount :: DynamicTable -> RequiredInsertCount -> IO ()
+ Network.QPACK.Internal: updateRequiredInsertCount :: DynamicTable -> AbsoluteIndex -> IO ()
+ Network.QPACK.Internal: wouldInstructionBeBlocked :: DynamicTable -> AbsoluteIndex -> IO Bool
+ Network.QPACK.Internal: wouldSectionBeBlocked :: DynamicTable -> RequiredInsertCount -> IO Bool
- Network.HQ.Client: Config :: Hooks -> PositionReadMaker -> Manager -> Config
+ Network.HQ.Client: Config :: Hooks -> QEncoderConfig -> QDecoderConfig -> PositionReadMaker -> Manager -> Config
- Network.HQ.Server: Config :: Hooks -> PositionReadMaker -> Manager -> Config
+ Network.HQ.Server: Config :: Hooks -> QEncoderConfig -> QDecoderConfig -> PositionReadMaker -> Manager -> Config
- Network.HTTP3.Client: Config :: Hooks -> PositionReadMaker -> Manager -> Config
+ Network.HTTP3.Client: Config :: Hooks -> QEncoderConfig -> QDecoderConfig -> PositionReadMaker -> Manager -> Config
- Network.HTTP3.Server: Config :: Hooks -> PositionReadMaker -> Manager -> Config
+ Network.HTTP3.Server: Config :: Hooks -> QEncoderConfig -> QDecoderConfig -> PositionReadMaker -> Manager -> Config
- Network.QPACK: QDecoderConfig :: Size -> Size -> QDecoderConfig
+ Network.QPACK: QDecoderConfig :: Size -> Size -> Int -> Int -> QDecoderConfig
- Network.QPACK: QEncoderConfig :: Size -> Size -> Size -> Size -> EncodeStrategy -> QEncoderConfig
+ Network.QPACK: QEncoderConfig :: Size -> Size -> Size -> QEncoderConfig
- Network.QPACK: newQEncoder :: QEncoderConfig -> IO (QEncoder, DecoderInstructionHandler)
+ Network.QPACK: newQEncoder :: QEncoderConfig -> (EncodedEncoderInstruction -> IO ()) -> IO (QEncoder, DecoderInstructionHandler, TableOperation)
- Network.QPACK: type EncoderInstructionHandlerS = EncodedEncoderInstruction -> IO ()
+ Network.QPACK: type EncoderInstructionHandlerS = EncodedEncoderInstruction -> IO EncodedEncoderInstruction
- Network.QPACK: type QDecoderS = StreamId -> EncodedFieldSection -> IO [Header]
+ Network.QPACK: type QDecoderS = StreamId -> EncodedFieldSection -> IO Maybe [Header]
- Network.QPACK: type QEncoder = TokenHeaderList -> IO (EncodedFieldSection, EncodedEncoderInstruction)
+ Network.QPACK: type QEncoder = StreamId -> TokenHeaderList -> IO EncodedFieldSection
- Network.QPACK.Internal: IllegalStaticIndex :: DecodeError
+ Network.QPACK.Internal: IllegalStaticIndex :: Int -> DecodeError
- Network.QPACK.Internal: decodeBase :: InsertionPoint -> Bool -> Int -> BasePoint
+ Network.QPACK.Internal: decodeBase :: RequiredInsertCount -> Bool -> Int -> BasePoint
- Network.QPACK.Internal: decodePrefix :: ReadBuffer -> DynamicTable -> IO (InsertionPoint, BasePoint, Bool)
+ Network.QPACK.Internal: decodePrefix :: ReadBuffer -> DynamicTable -> IO (RequiredInsertCount, BasePoint, Bool)
- Network.QPACK.Internal: decodeRequiredInsertCount :: Int -> InsertionPoint -> Int -> InsertionPoint
+ Network.QPACK.Internal: decodeRequiredInsertCount :: Int -> InsertionPoint -> Int -> RequiredInsertCount
- Network.QPACK.Internal: decodeTokenHeaderS :: DynamicTable -> ReadBuffer -> IO ([Header], Bool)
+ Network.QPACK.Internal: decodeTokenHeaderS :: DynamicTable -> ReadBuffer -> IO (Maybe ([Header], Bool))
- Network.QPACK.Internal: encodeBase :: InsertionPoint -> BasePoint -> (Bool, Int)
+ Network.QPACK.Internal: encodeBase :: RequiredInsertCount -> BasePoint -> (Bool, Int)
- Network.QPACK.Internal: encodeHeader :: EncodeStrategy -> DynamicTable -> [Header] -> IO (EncodedFieldSection, EncodedEncoderInstruction)
+ Network.QPACK.Internal: encodeHeader :: DynamicTable -> [Header] -> IO (EncodedFieldSection, EncodedEncoderInstruction)
- Network.QPACK.Internal: encodeRequiredInsertCount :: Int -> InsertionPoint -> Int
+ Network.QPACK.Internal: encodeRequiredInsertCount :: Int -> RequiredInsertCount -> Int
- Network.QPACK.Internal: encodeTokenHeader :: WriteBuffer -> WriteBuffer -> EncodeStrategy -> DynamicTable -> TokenHeaderList -> IO TokenHeaderList
+ Network.QPACK.Internal: encodeTokenHeader :: WriteBuffer -> WriteBuffer -> DynamicTable -> TokenHeaderList -> IO [AbsoluteIndex]
- Network.QPACK.Internal: insertEntryToDecoder :: Entry -> DynamicTable -> STM ()
+ Network.QPACK.Internal: insertEntryToDecoder :: Entry -> DynamicTable -> STM AbsoluteIndex
- Network.QPACK.Internal: newDynamicTableForDecoding :: Size -> Size -> (ByteString -> IO ()) -> IO DynamicTable
+ Network.QPACK.Internal: newDynamicTableForDecoding :: Size -> (ByteString -> IO ()) -> IO DynamicTable
- Network.QPACK.Internal: newDynamicTableForEncoding :: Size -> IO DynamicTable
+ Network.QPACK.Internal: newDynamicTableForEncoding :: (ByteString -> IO ()) -> IO DynamicTable
- Network.QPACK.Internal: setDebugQPACK :: DynamicTable -> IO ()
+ Network.QPACK.Internal: setDebugQPACK :: DynamicTable -> Bool -> IO ()

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Revision history for http3 +## 0.1.0++* QPACK encoder now supports the dynamic table.+* Breaking change: `Config` takes 'confQEncoderConfig' and+  `confQDecoderConfig`.+ ## 0.0.24  * Supporting SSLKEYLOGFILE in h3-server and h3-client.
Imports.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE CPP #-}+ module Imports (     ByteString (..),     module Control.Applicative,@@ -16,11 +18,14 @@     module Data.CaseInsensitive,     withForeignPtr,     mallocPlainForeignPtrBytes,+#if !MIN_VERSION_base(4,17,0)+    (!<<.), (!>>.),+#endif ) where  import Control.Applicative import Control.Monad-import Data.Bits hiding (Bits)+import Data.Bits import Data.ByteString.Internal (ByteString (..)) import Data.CaseInsensitive (foldedCase, mk, original) import Data.Foldable@@ -35,3 +40,13 @@ import Network.HTTP.Semantics import Network.HTTP.Types import Numeric++#if !MIN_VERSION_base(4,17,0)+infixl 8 !<<.+(!<<.) :: Bits a => a -> Int -> a+(!<<.) = unsafeShiftL++infixl 8 !>>.+(!>>.) :: Bits a => a -> Int -> a+(!>>.) = unsafeShiftR+#endif
Network/HQ/Client.hs view
@@ -6,13 +6,16 @@     -- * Runner     run, -    -- * Runner arguments-    H3.ClientConfig (..),+    -- * Client configration+    H3.ClientConfig,+    H3.defaultClientConfig,+    H3.scheme,+    Scheme,++    -- * Common configration     H3.Config (..),     H3.allocSimpleConfig,     H3.freeSimpleConfig,-    Scheme,-    Authority,      -- * HQ client     Client,
Network/HQ/Server.hs view
@@ -5,7 +5,7 @@     -- * Runner     run, -    -- * Runner arguments+    -- * Common configration     Config (..),     allocSimpleConfig,     freeSimpleConfig,
Network/HTTP3/Client.hs view
@@ -7,13 +7,28 @@     -- * Runner     run, -    -- * Runner arguments-    ClientConfig (..),+    -- * Client configration+    ClientConfig,+    defaultClientConfig,+    scheme,+    authority,++    -- * Common configuration     Config (..),+    defaultConfig,     allocSimpleConfig,     freeSimpleConfig,+    defaultQEncoderConfig,+    ecMaxTableCapacity,+    ecHeaderBlockBufferSize,+    ecInstructionBufferSize,+    defaultQDecoderConfig,+    dcMaxTableCapacity,+    dcHuffmanBufferSize,     Hooks (..),     defaultHooks,++    -- * HTTP semantics     module Network.HTTP.Semantics.Client, ) where @@ -36,6 +51,7 @@ import Network.HTTP3.Frame import Network.HTTP3.Recv import Network.HTTP3.Send+import Network.QPACK  -- | Configuration for HTTP\/3 or HQ client. For HQ, 'authority' is --   not used and an server's IP address is used in 'Request'.@@ -44,6 +60,13 @@     , authority :: Authority     } +defaultClientConfig :: ClientConfig+defaultClientConfig =+    ClientConfig+        { scheme = "https"+        , authority = "localhost"+        }+ -- | Running an HTTP\/3 client. run :: Connection -> ClientConfig -> Config -> Client a -> IO a run conn ClientConfig{..} conf client = withContext conn conf $ \ctx -> do@@ -100,4 +123,7 @@     auth'         | isIPv6 = "[" <> UTF8.fromString auth <> "]"         | otherwise = UTF8.fromString auth-    hdr' = (":scheme", scm) : (":authority", auth') : hdr+    hdr' = addIfNotExist (":scheme", scm) $ addIfNotExist (":authority", auth') hdr+    addIfNotExist keyVal@(key, _) hs = case find (\(k, _) -> k == key) hs of+        Nothing -> keyVal : hs+        Just _ -> hs
Network/HTTP3/Config.hs view
@@ -2,6 +2,7 @@  import Network.HTTP.Semantics.Client import Network.HTTP3.Frame+import Network.QPACK import Network.QUIC (Stream) import qualified System.TimeManager as T @@ -28,14 +29,32 @@ -- | Configuration for HTTP\/3 or HQ. data Config = Config     { confHooks :: Hooks+    , confQEncoderConfig :: QEncoderConfig+    , confQDecoderConfig :: QDecoderConfig     , confPositionReadMaker :: PositionReadMaker     , confTimeoutManager :: T.Manager     } +defaultConfig :: Config+defaultConfig =+    Config+        { confHooks = defaultHooks+        , confQEncoderConfig = defaultQEncoderConfig+        , confQDecoderConfig = defaultQDecoderConfig+        , confPositionReadMaker = defaultPositionReadMaker+        , confTimeoutManager = T.defaultManager+        }+ -- | Allocating a simple configuration with a handle-based position --   reader and a locally allocated timeout manager. allocSimpleConfig :: IO Config-allocSimpleConfig = Config defaultHooks defaultPositionReadMaker <$> T.initialize (30 * 1000000)+allocSimpleConfig =+    Config+        defaultHooks+        defaultQEncoderConfig+        defaultQDecoderConfig+        defaultPositionReadMaker+        <$> T.initialize (30 * 1000000)  -- | Freeing a simple configration. freeSimpleConfig :: Config -> IO ()
Network/HTTP3/Context.hs view
@@ -60,31 +60,22 @@  newContext :: Connection -> Config -> IO Context newContext conn conf = do-    sendDI <- setupUnidirectional conn conf-    ctl <- controlStream conn <$> newIORef IInit-    (enc, handleDI) <- newQEncoder defaultQEncoderConfig-    (dec, handleEI) <- newQDecoder defaultQDecoderConfig sendDI+    (sendEI, sendDI) <- setupUnidirectional conn conf+    (ctxQEncoder, handleDI, dyntblE) <- newQEncoder (confQEncoderConfig conf) sendEI+    -- newQDecoder passes dyntbl for decoder to handleEI internally+    (ctxQDecoder, handleEI) <- newQDecoder (confQDecoderConfig conf) sendDI+    ctl <- controlStream conn dyntblE <$> newIORef IInit     info <- getConnectionInfo conn     let handleDI' recv = handleDI recv `E.catch` abortWith QpackDecoderStreamError         handleEI' recv = handleEI recv `E.catch` abortWith QpackEncoderStreamError-        sw = switch conn ctl handleEI' handleDI'-        preadM = confPositionReadMaker conf-        hooks = confHooks conf-        mysa = localSockAddr info-        peersa = remoteSockAddr info-    mgr <- T.newThreadManager $ confTimeoutManager conf-    return $-        Context-            { ctxConnection = conn-            , ctxQEncoder = enc-            , ctxQDecoder = dec-            , ctxUniSwitch = sw-            , ctxPReadMaker = preadM-            , ctxThreadManager = mgr-            , ctxHooks = hooks-            , ctxMySockAddr = mysa-            , ctxPeerSockAddr = peersa-            }+        ctxUniSwitch = switch conn ctl handleEI' handleDI'+        ctxPReadMaker = confPositionReadMaker conf+        ctxHooks = confHooks conf+        ctxMySockAddr = localSockAddr info+        ctxPeerSockAddr = remoteSockAddr info+    ctxThreadManager <- T.newThreadManager $ confTimeoutManager conf+    let ctxConnection = conn+    return Context{..}   where     abortWith :: ApplicationProtocolError -> E.SomeException -> IO ()     abortWith aerr se
Network/HTTP3/Control.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}  module Network.HTTP3.Control (     setupUnidirectional,@@ -21,14 +22,17 @@ mkType = BS.singleton . fromIntegral . fromH3StreamType  setupUnidirectional-    :: Connection -> H3.Config -> IO (EncodedDecoderInstruction -> IO ())-setupUnidirectional conn conf = do+    :: Connection+    -> H3.Config+    -> IO (EncodedEncoderInstruction -> IO (), EncodedDecoderInstruction -> IO ())+setupUnidirectional conn conf@H3.Config{..} = do+    let QDecoderConfig{..} = confQDecoderConfig     settings <-         encodeH3Settings-            [ (SettingsQpackBlockedStreams, 100)-            , (SettingsQpackMaxTableCapacity, 4096)-            , (SettingsMaxFieldSectionSize, 32768)-            ] -- fixme+            [ (SettingsQpackBlockedStreams, dcBlockedSterams)+            , (SettingsQpackMaxTableCapacity, dcMaxTableCapacity)+            , (SettingsMaxFieldSectionSize, dcMaxFieldSectionSize)+            ]     let framesC = H3.onControlFrameCreated hooks [H3Frame H3FrameSettings settings]     let bssC = encodeH3Frames framesC     sC <- unidirectionalStream conn@@ -41,15 +45,17 @@     H3.onControlStreamCreated hooks sC     H3.onEncoderStreamCreated hooks sE     H3.onDecoderStreamCreated hooks sD-    return $ sendStream sD+    return (sendStream sE, sendStream sD)   where     stC = mkType H3ControlStreams     stE = mkType QPACKEncoderStream     stD = mkType QPACKDecoderStream     hooks = H3.confHooks conf -controlStream :: Connection -> IORef IFrame -> InstructionHandler-controlStream conn ref recv = loop0+-- DynamicTable for Encoder+controlStream+    :: Connection -> TableOperation -> IORef IFrame -> InstructionHandler+controlStream conn tblop ref recv = loop0   where     loop0 = do         bs <- recv 1024@@ -70,7 +76,7 @@         case parseH3Frame st0 bs of             IDone typ payload leftover -> do                 case typ of-                    H3FrameSettings -> checkSettings conn payload+                    H3FrameSettings -> checkSettings conn tblop payload                     _ -> abortConnection conn H3MissingSettings ""                 st1 <- parse leftover IInit                 return (True, st1)@@ -90,20 +96,31 @@                 parse leftover IInit             st1 -> return st1 -checkSettings :: Connection -> ByteString -> IO ()-checkSettings conn payload = do+checkSettings :: Connection -> TableOperation -> ByteString -> IO ()+checkSettings conn tblop payload = do     h3settings <- decodeH3Settings payload     loop (0 :: Int) h3settings   where     loop _ [] = return ()-    loop flags ((k@(H3SettingsKey i), _v) : ss)+    loop flags ((k@(H3SettingsKey i), v) : ss)         | flags `testBit` i = abortConnection conn H3SettingsError ""         | otherwise = do             let flags' = flags `setBit` i             case k of-                SettingsQpackMaxTableCapacity -> loop flags' ss-                SettingsMaxFieldSectionSize -> loop flags' ss-                SettingsQpackBlockedStreams -> loop flags' ss+                SettingsQpackMaxTableCapacity -> do+                    setCapacity tblop v+                    loop flags' ss+                -- This value is not used yet.+                -- RFC 9114: "A server that receives a larger field+                -- section than it is willing to handle can send an+                -- HTTP 431 (Request Header Fields Too Large) status+                -- code ([RFC6585])."+                SettingsMaxFieldSectionSize -> do+                    setHeaderSize tblop v+                    loop flags' ss+                SettingsQpackBlockedStreams -> do+                    setBlockedStreams tblop v+                    loop flags' ss                 _                     -- HTTP/2 settings                     | i <= 0x6 -> abortConnection conn H3SettingsError ""
Network/HTTP3/Frame.hs view
@@ -39,23 +39,23 @@  {- FOURMOLU_DISABLE -} fromH3FrameType :: H3FrameType -> Int64-fromH3FrameType H3FrameData        = 0x0-fromH3FrameType H3FrameHeaders     = 0x1-fromH3FrameType H3FrameCancelPush  = 0x3-fromH3FrameType H3FrameSettings    = 0x4-fromH3FrameType H3FramePushPromise = 0x5-fromH3FrameType H3FrameGoaway      = 0x7-fromH3FrameType H3FrameMaxPushId   = 0xD+fromH3FrameType H3FrameData        = 0x00+fromH3FrameType H3FrameHeaders     = 0x01+fromH3FrameType H3FrameCancelPush  = 0x03+fromH3FrameType H3FrameSettings    = 0x04+fromH3FrameType H3FramePushPromise = 0x05+fromH3FrameType H3FrameGoaway      = 0x07+fromH3FrameType H3FrameMaxPushId   = 0x0d fromH3FrameType (H3FrameUnknown i) = i  toH3FrameType :: Int64 -> H3FrameType-toH3FrameType 0x0 = H3FrameData-toH3FrameType 0x1 = H3FrameHeaders-toH3FrameType 0x3 = H3FrameCancelPush-toH3FrameType 0x4 = H3FrameSettings-toH3FrameType 0x5 = H3FramePushPromise-toH3FrameType 0x7 = H3FrameGoaway-toH3FrameType 0xD = H3FrameMaxPushId+toH3FrameType 0x00 = H3FrameData+toH3FrameType 0x01 = H3FrameHeaders+toH3FrameType 0x03 = H3FrameCancelPush+toH3FrameType 0x04 = H3FrameSettings+toH3FrameType 0x05 = H3FramePushPromise+toH3FrameType 0x07 = H3FrameGoaway+toH3FrameType 0x0d = H3FrameMaxPushId toH3FrameType i   = H3FrameUnknown i  permittedInControlStream :: H3FrameType -> Bool@@ -67,7 +67,7 @@ permittedInControlStream H3FrameGoaway      = True permittedInControlStream H3FrameMaxPushId   = True permittedInControlStream (H3FrameUnknown i)-    | i <= 0x9 = False+    | i <= 0x09 = False     | otherwise = True  permittedInRequestStream :: H3FrameType -> Bool@@ -79,7 +79,7 @@ permittedInRequestStream H3FrameGoaway      = False permittedInRequestStream H3FrameMaxPushId   = False permittedInRequestStream (H3FrameUnknown i)-    | i <= 0x9  = False+    | i <= 0x09 = False     | otherwise = True  permittedInPushStream :: H3FrameType -> Bool@@ -91,7 +91,7 @@ permittedInPushStream H3FrameGoaway      = False permittedInPushStream H3FrameMaxPushId   = False permittedInPushStream (H3FrameUnknown i)-    | i <= 0x9  = False+    | i <= 0x09 = False     | otherwise = True {- FOURMOLU_ENABLE -} 
Network/HTTP3/Send.hs view
@@ -26,7 +26,7 @@ sendHeader ctx strm th hdrs = do     -- fixme: fixHeaders     (ths, _) <- toTokenHeaderTable hdrs-    (hdr, "") <- qpackEncode ctx ths -- FIXME: send 2nd ret value as EI+    hdr <- qpackEncode ctx (streamId strm) ths     let frames = [H3Frame H3FrameHeaders hdr]         frames' = onHeadersFrameCreated (getHooks ctx) frames         bss = encodeH3Frames frames'
Network/HTTP3/Server.hs view
@@ -6,12 +6,22 @@     -- * Runner     run, -    -- * Runner arguments+    -- * Common configration     Config (..),+    defaultConfig,     allocSimpleConfig,     freeSimpleConfig,+    defaultQEncoderConfig,+    ecMaxTableCapacity,+    ecHeaderBlockBufferSize,+    ecInstructionBufferSize,+    defaultQDecoderConfig,+    dcMaxTableCapacity,+    dcHuffmanBufferSize,     Hooks (..),     defaultHooks,++    -- * HTTP semantics     module Network.HTTP.Semantics.Server,      -- * Internal@@ -40,6 +50,7 @@ import Network.HTTP3.Frame import Network.HTTP3.Recv import Network.HTTP3.Send+import Network.QPACK import Network.QPACK.Internal  -- | Running an HTTP\/3 server.
Network/HTTP3/Settings.hs view
@@ -11,13 +11,13 @@  {- FOURMOLU_DISABLE -} pattern SettingsQpackMaxTableCapacity :: H3SettingsKey-pattern SettingsQpackMaxTableCapacity  = H3SettingsKey 0x1+pattern SettingsQpackMaxTableCapacity  = H3SettingsKey 0x01  pattern SettingsMaxFieldSectionSize   :: H3SettingsKey-pattern SettingsMaxFieldSectionSize    = H3SettingsKey 0x6+pattern SettingsMaxFieldSectionSize    = H3SettingsKey 0x06  pattern SettingsQpackBlockedStreams   :: H3SettingsKey-pattern SettingsQpackBlockedStreams    = H3SettingsKey 0x7+pattern SettingsQpackBlockedStreams    = H3SettingsKey 0x07 {- FOURMOLU_ENABLE -}  {- FOURMOLU_DISABLE -}
Network/QPACK.hs view
@@ -8,7 +8,12 @@     defaultQEncoderConfig,     QEncoder,     newQEncoder,+    TableOperation (..), +    -- ** Encoder for debugging+    QEncoderS,+    newQEncoderS,+     -- * Decoder     QDecoderConfig (..),     defaultQDecoderConfig,@@ -28,10 +33,6 @@     InstructionHandler,     Size, -    -- * Strategy-    EncodeStrategy (..),-    CompressionAlgo (..),-     -- * Re-exports     TokenHeaderTable,     TokenHeaderList,@@ -47,8 +48,9 @@ import Control.Concurrent import Control.Concurrent.STM import qualified Control.Exception as E-import qualified Data.ByteString as B-import Data.CaseInsensitive+import qualified Data.ByteString as BS+import Data.CaseInsensitive hiding (map)+import qualified Data.CaseInsensitive as CI import Network.ByteOrder import Network.HPACK.Internal (     GCBuffer,@@ -58,7 +60,7 @@     toTokenHeaderTable,  ) import Network.HTTP.Types-import Network.QUIC.Internal (StreamId, stdoutLogger)+import Network.QUIC.Internal (StreamId)  import Imports import Network.QPACK.Error@@ -70,20 +72,24 @@ ----------------------------------------------------------------  -- | QPACK encoder.-type QEncoder =-    TokenHeaderList -> IO (EncodedFieldSection, EncodedEncoderInstruction)+type QEncoder = StreamId -> TokenHeaderList -> IO EncodedFieldSection +-- | QPACK simple encoder.+type QEncoderS = StreamId -> [Header] -> IO EncodedFieldSection+ -- | QPACK decoder. type QDecoder = StreamId -> EncodedFieldSection -> IO TokenHeaderTable  -- | QPACK simple decoder.-type QDecoderS = StreamId -> EncodedFieldSection -> IO [Header]+type QDecoderS = StreamId -> EncodedFieldSection -> IO (Maybe [Header])  -- | Encoder instruction handler. type EncoderInstructionHandler = (Int -> IO EncodedEncoderInstruction) -> IO ()  -- | Simple encoder instruction handler.-type EncoderInstructionHandlerS = EncodedEncoderInstruction -> IO ()+--   Leftover is returned.+type EncoderInstructionHandlerS =+    EncodedEncoderInstruction -> IO EncodedEncoderInstruction  -- | Encoded decoder instruction. type EncodedDecoderInstruction = ByteString@@ -94,122 +100,275 @@ -- | A type to integrating handlers. type InstructionHandler = (Int -> IO ByteString) -> IO () +data TableOperation = TableOperation+    { setCapacity :: Int -> IO ()+    , setBlockedStreams :: Int -> IO ()+    , setHeaderSize :: Int -> IO ()+    }+ ----------------------------------------------------------------  -- | Configuration for QPACK encoder. data QEncoderConfig = QEncoderConfig-    { ecDynamicTableSize :: Size+    { ecMaxTableCapacity :: Size     , ecHeaderBlockBufferSize :: Size-    , ecPrefixBufferSize :: Size     , ecInstructionBufferSize :: Size-    , encStrategy :: EncodeStrategy     }     deriving (Show)  -- | Default configuration for QPACK encoder. -- -- >>> defaultQEncoderConfig--- QEncoderConfig {ecDynamicTableSize = 4096, ecHeaderBlockBufferSize = 4096, ecPrefixBufferSize = 128, ecInstructionBufferSize = 4096, encStrategy = EncodeStrategy {compressionAlgo = Static, useHuffman = True}}+-- QEncoderConfig {ecMaxTableCapacity = 4096, ecHeaderBlockBufferSize = 4096, ecInstructionBufferSize = 4096} defaultQEncoderConfig :: QEncoderConfig defaultQEncoderConfig =     QEncoderConfig-        { ecDynamicTableSize = 4096+        { ecMaxTableCapacity = 4096         , ecHeaderBlockBufferSize = 4096-        , ecPrefixBufferSize = 128         , ecInstructionBufferSize = 4096-        , encStrategy = EncodeStrategy Static True         }  -- | Creating a new QPACK encoder.-newQEncoder :: QEncoderConfig -> IO (QEncoder, DecoderInstructionHandler)-newQEncoder QEncoderConfig{..} = do+newQEncoder+    :: QEncoderConfig+    -> (EncodedEncoderInstruction -> IO ())+    -> IO (QEncoder, DecoderInstructionHandler, TableOperation)+newQEncoder QEncoderConfig{..} sendEI = do     let bufsiz1 = ecHeaderBlockBufferSize-        bufsiz2 = ecPrefixBufferSize-        bufsiz3 = ecInstructionBufferSize+        bufsiz2 = ecInstructionBufferSize     gcbuf1 <- mallocPlainForeignPtrBytes bufsiz1     gcbuf2 <- mallocPlainForeignPtrBytes bufsiz2-    gcbuf3 <- mallocPlainForeignPtrBytes bufsiz3-    dyntbl <- newDynamicTableForEncoding ecDynamicTableSize+    dyntbl <- newDynamicTableForEncoding sendEI     lock <- newMVar ()     let enc =             qpackEncoder-                encStrategy                 gcbuf1                 bufsiz1                 gcbuf2                 bufsiz2-                gcbuf3-                bufsiz3                 dyntbl                 lock         handler = decoderInstructionHandler dyntbl-    return (enc, handler)+        ctl =+            TableOperation+                { setCapacity = \n -> do+                    -- "n" is decoder-proposed size via settings.+                    let tableSize = min ecMaxTableCapacity n+                    setTableCapacity dyntbl tableSize+                    ins <- encodeEncoderInstructions [SetDynamicTableCapacity tableSize] False+                    sendIns dyntbl ins+                , setBlockedStreams = setMaxBlockedStreams dyntbl+                , setHeaderSize = setMaxHeaderSize dyntbl+                }+    return (enc, handler, ctl) +tokenHeaderSize :: TokenHeader -> Int+tokenHeaderSize (t, v) = BS.length (CI.original (tokenKey t)) + BS.length v + 8 -- adhoc overhead++split :: Int -> TokenHeaderList -> (TokenHeaderList, TokenHeaderList)+split lim ts = split' 0 ts+  where+    split' _ [] = ([], [])+    split' s xxs@(x : xs)+        | siz > lim = E.throw BufferOverrun+        | s' < lim =+            let (ys, zs) = split' s' xs+             in (x : ys, zs)+        | otherwise = ([], xxs)+      where+        siz = tokenHeaderSize x+        s' = s + siz++splitThrough :: Int -> TokenHeaderList -> [TokenHeaderList]+splitThrough lim ts0 = loop ts0 id+  where+    loop [] builder = builder []+    loop ts builder = loop ts2 (builder . (ts1 :))+      where+        (ts1, ts2) = split lim ts+ qpackEncoder-    :: EncodeStrategy-    -> GCBuffer+    :: GCBuffer     -> Int     -> GCBuffer     -> Int+    -> DynamicTable+    -> MVar ()+    -> QEncoder+qpackEncoder gcbuf1 bufsiz1 gcbuf2 bufsiz2 dyntbl lock sid ts =+    withMVar lock $ \_ ->+        withForeignPtr gcbuf1 $ \buf1 ->+            withForeignPtr gcbuf2 $ \buf2 -> do+                siz <- getTableCapacity dyntbl+                qpackDebug dyntbl $+                    putStrLn $+                        "---- Stream " ++ show sid ++ " " ++ "tblsiz: " ++ show siz+                setBasePointToInsersionPoint dyntbl+                clearRequiredInsertCount dyntbl+                let tss = splitThrough bufsiz1 ts+                his <- mapM (qpackEncodeHeader buf1 bufsiz1 buf2 bufsiz2 dyntbl) tss+                let (hbs, daiss) = unzip his+                prefix <- qpackEncodePrefix buf1 bufsiz1 dyntbl+                let section = BS.concat (prefix : hbs)+                reqInsCnt <- getRequiredInsertCount dyntbl+                -- To count only blocked sections,+                -- dont' register this section if reqInsCnt == 0.+                when (reqInsCnt /= 0) $+                    insertSection dyntbl sid $+                        Section reqInsCnt $+                            concat daiss+                return section++qpackEncoderS+    :: GCBuffer+    -> Int     -> GCBuffer     -> Int     -> DynamicTable     -> MVar ()-    -> TokenHeaderList-    -> IO (EncodedFieldSection, EncodedEncoderInstruction)-qpackEncoder stgy gcbuf1 bufsiz1 gcbuf2 bufsiz2 gcbuf3 bufsiz3 dyntbl lock ts =+    -> QEncoderS+qpackEncoderS gcbuf1 bufsiz1 gcbuf2 bufsiz2 dyntbl lock sid hs =     withMVar lock $ \_ ->         withForeignPtr gcbuf1 $ \buf1 ->-            withForeignPtr gcbuf2 $ \buf2 ->-                withForeignPtr gcbuf3 $ \buf3 -> do-                    wbuf1 <- newWriteBuffer buf1 bufsiz1-                    wbuf2 <- newWriteBuffer buf2 bufsiz2-                    wbuf3 <- newWriteBuffer buf3 bufsiz3-                    thl <- encodeTokenHeader wbuf1 wbuf3 stgy dyntbl ts -- fixme: leftover-                    when (thl /= []) $ stdoutLogger "qpackEncoder: leftover"-                    hb0 <- toByteString wbuf1-                    ins <- toByteString wbuf3-                    encodePrefix wbuf2 dyntbl-                    prefix <- toByteString wbuf2-                    let hb = prefix `B.append` hb0-                    return (hb, ins)+            withForeignPtr gcbuf2 $ \buf2 -> do+                siz <- getTableCapacity dyntbl+                qpackDebug dyntbl $+                    putStrLn $+                        "---- Stream " ++ show sid ++ " " ++ "tblsiz: " ++ show siz+                setBasePointToInsersionPoint dyntbl+                clearRequiredInsertCount dyntbl+                let tss = splitThrough bufsiz1 ts+                his <- mapM (qpackEncodeHeader buf1 bufsiz1 buf2 bufsiz2 dyntbl) tss+                let (hbs, daiss) = unzip his+                prefix <- qpackEncodePrefix buf1 bufsiz1 dyntbl+                let section = BS.concat (prefix : hbs)+                reqInsCnt <- getRequiredInsertCount dyntbl+                -- To count only blocked sections,+                -- dont' register this section if reqInsCnt == 0.+                immAck <- getImmediateAck dyntbl+                when (reqInsCnt /= 0) $ do+                    blocked <- wouldSectionBeBlocked dyntbl reqInsCnt+                    when blocked $ insertBlockedStreamE dyntbl sid+                    let dais = concat daiss+                    insertSection dyntbl sid $ Section reqInsCnt dais+                    when immAck $ do+                        -- The same logic of SectionAcknowledgement.+                        updateKnownReceivedCount dyntbl reqInsCnt+                        mapM_ (decreaseReference dyntbl) dais+                        deleteBlockedStreamE dyntbl sid+                -- Need to emulate InsertCountIncrement since+                -- SectionAcknowledgement is not returned if+                -- RequiredInsertCount is 0.+                when immAck $ setInsersionPointToKnownReceivedCount dyntbl+                return section+  where+    mk' (k, v) = (t, v)+      where+        t = toToken $ foldedCase k+    ts = map mk' hs +qpackEncodeHeader+    :: Buffer+    -> BufferSize+    -> Buffer+    -> BufferSize+    -> DynamicTable+    -> TokenHeaderList+    -> IO (ByteString, [AbsoluteIndex])+qpackEncodeHeader buf1 bufsiz1 buf2 bufsiz2 dyntbl ts = do+    wbuf1 <- newWriteBuffer buf1 bufsiz1+    wbuf2 <- newWriteBuffer buf2 bufsiz2+    dais <- encodeTokenHeader wbuf1 wbuf2 dyntbl ts+    hb <- toByteString wbuf1+    ins <- toByteString wbuf2+    when (ins /= "") $ sendIns dyntbl ins+    return (hb, dais)++qpackEncodePrefix :: Buffer -> BufferSize -> DynamicTable -> IO ByteString+qpackEncodePrefix buf1 bufsiz1 dyntbl = do+    wbuf1 <- newWriteBuffer buf1 bufsiz1+    encodePrefix wbuf1 dyntbl+    toByteString wbuf1++-- Note: dyntbl for encoder decoderInstructionHandler :: DynamicTable -> DecoderInstructionHandler-decoderInstructionHandler dyntbl recv = loop+decoderInstructionHandler dyntbl recv = loop ""   where-    loop = do-        _ <- getInsertionPoint dyntbl -- fixme-        bs <- recv 1024+    loop bs0 = do+        bs1 <- recv 1024+        let bs+                | bs0 == "" = bs1+                | otherwise = bs0 <> bs1         when (bs /= "") $ do-            (ins, leftover) <- decodeDecoderInstructions bs -- fixme: saving leftover-            when (leftover /= "") $ stdoutLogger "decoderInstructionHandler: leftover"+            (ins, leftover) <- decodeDecoderInstructions bs             qpackDebug dyntbl $ mapM_ print ins             mapM_ handle ins-            loop-    handle (SectionAcknowledgement _n) = return ()-    handle (StreamCancellation _n) = return ()+            loop leftover+    handle (SectionAcknowledgement sid) = do+        msec <- getAndDelSection dyntbl sid+        case msec of+            Nothing -> E.throwIO DecoderInstructionError+            Just (Section reqInsCnt ais) -> do+                updateKnownReceivedCount dyntbl reqInsCnt+                mapM_ (decreaseReference dyntbl) ais+                deleteBlockedStreamE dyntbl sid+    handle (StreamCancellation _n) = return () -- fixme     handle (InsertCountIncrement n)         | n == 0 = E.throwIO DecoderInstructionError-        | otherwise = return () -- FIXME: Known Received Count+        | otherwise = incrementKnownReceivedCount dyntbl n  ---------------------------------------------------------------- +newQEncoderS+    :: QEncoderConfig -- capacity+    -> (EncodedEncoderInstruction -> IO ())+    -> Int -- blocked stream+    -> Bool -- immediate Acks+    -> Bool -- debug+    -> IO QEncoderS+newQEncoderS QEncoderConfig{..} saveEI blocked immediateAck debug = do+    let bufsiz1 = ecHeaderBlockBufferSize+        bufsiz2 = ecInstructionBufferSize+    gcbuf1 <- mallocPlainForeignPtrBytes bufsiz1+    gcbuf2 <- mallocPlainForeignPtrBytes bufsiz2+    dyntbl <- newDynamicTableForEncoding saveEI+    setTableCapacity dyntbl ecMaxTableCapacity+    setMaxBlockedStreams dyntbl blocked+    setImmediateAck dyntbl immediateAck+    setDebugQPACK dyntbl debug+    lock <- newMVar ()+    let enc =+            qpackEncoderS+                gcbuf1+                bufsiz1+                gcbuf2+                bufsiz2+                dyntbl+                lock+    return enc++----------------------------------------------------------------+ -- | Configuration for QPACK decoder. data QDecoderConfig = QDecoderConfig-    { dcDynamicTableSize :: Size-    , dcHuffmanBufferSize :: Size+    { dcMaxTableCapacity :: Size+    , dcHuffmanBufferSize :: Size -- for encoder insteruction handler+    , dcBlockedSterams :: Int+    , dcMaxFieldSectionSize :: Int     }     deriving (Show)  -- | Default configuration for QPACK decoder. -- -- >>> defaultQDecoderConfig--- QDecoderConfig {dcDynamicTableSize = 4096, dcHuffmanBufferSize = 4096}+-- QDecoderConfig {dcMaxTableCapacity = 4096, dcHuffmanBufferSize = 2048, dcBlockedSterams = 100, dcMaxFieldSectionSize = 32768} defaultQDecoderConfig :: QDecoderConfig defaultQDecoderConfig =     QDecoderConfig-        { dcDynamicTableSize = 4096-        , dcHuffmanBufferSize = 4096+        { dcMaxTableCapacity = 4096+        , dcHuffmanBufferSize = 2048 -- no global locking+        , dcBlockedSterams = 100+        , dcMaxFieldSectionSize = 32768         }  -- | Creating a new QPACK decoder.@@ -219,9 +378,10 @@     -> IO (QDecoder, EncoderInstructionHandler) newQDecoder QDecoderConfig{..} sendDI = do     dyntbl <--        newDynamicTableForDecoding dcDynamicTableSize dcHuffmanBufferSize sendDI+        newDynamicTableForDecoding dcHuffmanBufferSize sendDI+    setMaxBlockedStreams dyntbl dcBlockedSterams     let dec = qpackDecoder dyntbl-        handler = encoderInstructionHandler dyntbl+        handler = encoderInstructionHandler dcMaxTableCapacity dyntbl     return (dec, handler)  -- | Creating a new simple QPACK decoder.@@ -232,10 +392,11 @@     -> IO (QDecoderS, EncoderInstructionHandlerS) newQDecoderS QDecoderConfig{..} sendDI debug = do     dyntbl <--        newDynamicTableForDecoding dcDynamicTableSize dcHuffmanBufferSize sendDI-    when debug $ setDebugQPACK dyntbl+        newDynamicTableForDecoding dcHuffmanBufferSize sendDI+    setMaxBlockedStreams dyntbl dcBlockedSterams+    setDebugQPACK dyntbl debug     let dec = qpackDecoderS dyntbl-        handler = encoderInstructionHandlerS dyntbl+        handler = encoderInstructionHandlerS dcMaxTableCapacity dyntbl     return (dec, handler)  qpackDecoder@@ -243,40 +404,55 @@ qpackDecoder dyntbl sid bs = do     (tbl, needAck) <- withReadBuffer bs $ \rbuf -> decodeTokenHeader dyntbl rbuf     when needAck $-        encodeDecoderInstructions [SectionAcknowledgement sid] >>= getSendDI dyntbl+        encodeDecoderInstructions [SectionAcknowledgement sid] >>= sendIns dyntbl     return tbl -qpackDecoderS :: DynamicTable -> StreamId -> EncodedFieldSection -> IO [Header]+qpackDecoderS+    :: DynamicTable -> StreamId -> EncodedFieldSection -> IO (Maybe [Header]) qpackDecoderS dyntbl sid bs = do-    (hs, needAck) <- withReadBuffer bs $ \rbuf -> decodeTokenHeaderS dyntbl rbuf-    when needAck $-        encodeDecoderInstructions [SectionAcknowledgement sid] >>= getSendDI dyntbl-    return hs+    qpackDebug dyntbl $ putStrLn $ "---- Stream " ++ show sid+    mhs <- withReadBuffer bs $ \rbuf -> decodeTokenHeaderS dyntbl rbuf+    case mhs of+        Nothing -> return Nothing+        Just (hs, needAck) -> do+            when needAck $+                encodeDecoderInstructions [SectionAcknowledgement sid] >>= sendIns dyntbl+            return $ Just hs -encoderInstructionHandler :: DynamicTable -> EncoderInstructionHandler-encoderInstructionHandler dyntbl recv = loop+-- Note: dyntbl for decoder+encoderInstructionHandler :: Int -> DynamicTable -> EncoderInstructionHandler+encoderInstructionHandler decCapLim dyntbl recv = loop ""   where-    loop = do-        bs <- recv 1024+    loop bs0 = do+        bs1 <- recv 1024+        let bs+                | bs0 == "" = bs1+                | otherwise = bs0 <> bs1         when (bs /= "") $ do-            encoderInstructionHandlerS dyntbl bs-            loop--encoderInstructionHandlerS :: DynamicTable -> EncoderInstructionHandlerS-encoderInstructionHandlerS _dyntbl "" = return ()-encoderInstructionHandlerS dyntbl bs = do-    (ins, leftover) <- decodeEncoderInstructions hufdec bs -- fixme: saving leftover-    when (leftover /= "") $ stdoutLogger "encoderInstructionHandler: leftover"+            leftover <- encoderInstructionHandlerS decCapLim dyntbl bs+            loop leftover -    qpackDebug dyntbl $ mapM_ print ins-    mapM_ handle ins+-- Note: dyntbl for decoder+encoderInstructionHandlerS :: Int -> DynamicTable -> EncoderInstructionHandlerS+encoderInstructionHandlerS _ _dyntbl "" = return ""+encoderInstructionHandlerS decCapLim dyntbl bs = do+    (ins, leftover) <- decodeEncoderInstructions hufdec bs+    cnt <- sum <$> mapM handle ins+    when (cnt /= 0) $+        encodeDecoderInstructions [InsertCountIncrement cnt] >>= sendIns dyntbl+    return leftover   where-    hufdec = getHuffmanDecoder dyntbl-    handle (SetDynamicTableCapacity n)-        | n > 4096 = E.throwIO EncoderInstructionError-        | otherwise = return () -- FIXME: set cap-    handle (InsertWithNameReference ii val) = do-        atomically $ do+    hufdec = getHuffmanDecoder dyntbl -- only for encoder instruction handler+    handle ins@(SetDynamicTableCapacity n)+        | n > decCapLim = E.throwIO EncoderInstructionError+        | otherwise = do+            setTableCapacity dyntbl n+            qpackDebug dyntbl $ print ins+            return 0+    handle ins@(InsertWithNameReference ii val) = do+        ready <- isTableReady dyntbl+        unless ready $ E.throwIO EncoderInstructionError+        dai <- atomically $ do             idx <- case ii of                 Left ai -> return $ SIndex ai                 Right ri -> do@@ -284,17 +460,29 @@                     return $ DIndex $ fromInsRelativeIndex ri ip             ent0 <- toIndexedEntry dyntbl idx             let ent = toEntryToken (entryToken ent0) val-            insertEntryToDecoder ent dyntbl-        encodeDecoderInstructions [InsertCountIncrement 1] >>= getSendDI dyntbl-    handle (InsertWithLiteralName t val) = do-        atomically $ do+            _ <- insertEntryToDecoder ent dyntbl+            return idx+        qpackDebug dyntbl $ putStrLn $ show ins ++ ": " ++ show dai+        return 1+    handle ins@(InsertWithLiteralName t val) = do+        ready <- isTableReady dyntbl+        unless ready $ E.throwIO EncoderInstructionError+        dai <- atomically $ do             let ent = toEntryToken t val             insertEntryToDecoder ent dyntbl-        encodeDecoderInstructions [InsertCountIncrement 1] >>= getSendDI dyntbl-    handle (Duplicate ri) = do-        atomically $ do+        qpackDebug dyntbl $ putStrLn $ show ins ++ ": " ++ show dai+        return 1+    handle ins@(Duplicate ri) = do+        ready <- isTableReady dyntbl+        unless ready $ E.throwIO EncoderInstructionError+        (dai, dai') <- atomically $ do             ip <- getInsertionPointSTM dyntbl-            let idx = DIndex $ fromInsRelativeIndex ri ip+            let ai = fromInsRelativeIndex ri ip+                idx = DIndex ai             ent <- toIndexedEntry dyntbl idx-            insertEntryToDecoder ent dyntbl-        encodeDecoderInstructions [InsertCountIncrement 1] >>= getSendDI dyntbl+            ai' <- insertEntryToDecoder ent dyntbl+            return (ai, ai')+        qpackDebug dyntbl $+            putStrLn $+                show ins ++ ": " ++ show dai ++ " -> " ++ show dai'+        return 1
Network/QPACK/Error.hs view
@@ -28,8 +28,9 @@ {- FOURMOLU_ENABLE -}  data DecodeError-    = IllegalStaticIndex+    = IllegalStaticIndex Int     | IllegalInsertCount+    | BlockedStreamsOverflow     deriving (Eq, Show)  data EncoderInstructionError = EncoderInstructionError
Network/QPACK/HeaderBlock.hs view
@@ -4,8 +4,6 @@     encodeTokenHeader,     EncodedFieldSection,     EncodedEncoderInstruction,-    EncodeStrategy (..),-    CompressionAlgo (..),     encodePrefix,      -- * Decoder
Network/QPACK/HeaderBlock/Decode.hs view
@@ -3,10 +3,13 @@ module Network.QPACK.HeaderBlock.Decode where  import Control.Concurrent.STM+import qualified Control.Exception as E import qualified Data.ByteString.Char8 as BS8 import Data.CaseInsensitive import Network.ByteOrder import Network.HPACK.Internal (+    HuffmanDecoder,+    decodeH,     decodeI,     decodeS,     decodeSimple,@@ -17,6 +20,7 @@ import Network.HTTP.Types  import Imports+import Network.QPACK.Error import Network.QPACK.HeaderBlock.Prefix import Network.QPACK.Table import Network.QPACK.Types@@ -26,31 +30,56 @@     -> ReadBuffer     -> IO (TokenHeaderTable, Bool) decodeTokenHeader dyntbl rbuf = do-    (reqip, bp, needAck) <- decodePrefix rbuf dyntbl-    checkInsertionPoint dyntbl reqip-    tbl <- decodeSophisticated (toTokenHeader dyntbl bp) rbuf+    (reqInsertCount, bp, needAck) <- decodePrefix rbuf dyntbl+    ready <- checkRequiredInsertCountNB dyntbl reqInsertCount+    unless ready $ do+        ok <- tryIncreaseStreams dyntbl+        unless ok $ E.throwIO BlockedStreamsOverflow+        checkRequiredInsertCount dyntbl reqInsertCount+        decreaseStreams dyntbl+    checkRequiredInsertCount dyntbl reqInsertCount+    let bufsiz = 2048+    gcbuf <- mallocPlainForeignPtrBytes 2048+    let hufdec = decodeH gcbuf bufsiz+    tbl <- decodeSophisticated (toTokenHeader dyntbl bp hufdec) rbuf     return (tbl, needAck)  decodeTokenHeaderS     :: DynamicTable     -> ReadBuffer-    -> IO ([Header], Bool)+    -> IO (Maybe ([Header], Bool)) decodeTokenHeaderS dyntbl rbuf = do-    (reqip, bp, needAck) <- decodePrefix rbuf dyntbl-    debug <- getDebugQPACK dyntbl-    unless debug $ checkInsertionPoint dyntbl reqip-    hs <- decodeSimple (toTokenHeader dyntbl bp) rbuf-    return (hs, needAck)+    (reqInsertCount, bp, needAck) <- decodePrefix rbuf dyntbl+    ok <- checkRequiredInsertCountNB dyntbl reqInsertCount+    if ok+        then do+            let bufsiz = 2048+            gcbuf <- mallocPlainForeignPtrBytes 2048+            let hufdec = decodeH gcbuf bufsiz+            hs <- decodeSimple (toTokenHeader dyntbl bp hufdec) rbuf+            return $ Just (hs, needAck)+        else return Nothing +{- FOURMOLU_DISABLE -} toTokenHeader-    :: DynamicTable -> BasePoint -> Word8 -> ReadBuffer -> IO TokenHeader-toTokenHeader dyntbl bp w8 rbuf-    | w8 `testBit` 7 = decodeIndexedFieldLine rbuf dyntbl bp w8-    | w8 `testBit` 6 = decodeLiteralFieldLineWithNameReference rbuf dyntbl bp w8-    | w8 `testBit` 5 = decodeLiteralFieldLineWithoutNameReference rbuf dyntbl bp w8-    | w8 `testBit` 4 = decodeIndexedFieldLineWithPostBaseIndex rbuf dyntbl bp w8+    :: DynamicTable+    -> BasePoint+    -> HuffmanDecoder+    -> Word8+    -> ReadBuffer+    -> IO TokenHeader+toTokenHeader dyntbl bp hufdec w8 rbuf+    | w8 `testBit` 7 =+        decodeIndexedFieldLine                  rbuf dyntbl        bp w8+    | w8 `testBit` 6 =+        decodeLiteralFieldLineWithNameReference rbuf dyntbl hufdec bp w8+    | w8 `testBit` 5 =+        decodeLiteralFieldLineWithLiteralName   rbuf dyntbl hufdec bp w8+    | w8 `testBit` 4 =+        decodeIndexedFieldLineWithPostBaseIndex rbuf dyntbl        bp w8     | otherwise =-        decodeLiteralFieldLineWithPostBaseNameReference rbuf dyntbl bp w8+        decodeLiteralFieldLineWithPostBaseNameReference rbuf dyntbl hufdec bp w8+{- FOURMOLU_ENABLE -}  -- 4.5.2.  Indexed Field Line decodeIndexedFieldLine@@ -60,48 +89,14 @@     let static = w8 `testBit` 6         hidx             | static = SIndex $ AbsoluteIndex i-            | otherwise = DIndex $ fromHBRelativeIndex (HBRelativeIndex i) bp+            | otherwise = DIndex $ fromPreBaseIndex (PreBaseIndex i) bp     ret <- atomically (entryTokenHeader <$> toIndexedEntry dyntbl hidx)-    qpackDebug dyntbl $+    qpackDebug dyntbl $ do+        checkHIndex dyntbl hidx         putStrLn $             "IndexedFieldLine (" ++ show hidx ++ ") " ++ showTokenHeader ret     return ret --- 4.5.4.  Literal Field Line With Name Reference-decodeLiteralFieldLineWithNameReference-    :: ReadBuffer -> DynamicTable -> BasePoint -> Word8 -> IO TokenHeader-decodeLiteralFieldLineWithNameReference rbuf dyntbl bp w8 = do-    i <- decodeI 4 (w8 .&. 0b00001111) rbuf-    let static = w8 `testBit` 4-        hidx-            | static = SIndex $ AbsoluteIndex i-            | otherwise = DIndex $ fromHBRelativeIndex (HBRelativeIndex i) bp-    key <- atomically (entryToken <$> toIndexedEntry dyntbl hidx)-    let hufdec = getHuffmanDecoder dyntbl-    val <- decodeS (`clearBit` 7) (`testBit` 7) 7 hufdec rbuf-    let ret = (key, val)-    qpackDebug dyntbl $-        putStrLn $-            "LiteralFieldLineWithNameReference ("-                ++ show hidx-                ++ ") "-                ++ showTokenHeader ret-    return ret---- 4.5.6.  Literal Field Line Without Name Reference-decodeLiteralFieldLineWithoutNameReference-    :: ReadBuffer -> DynamicTable -> BasePoint -> Word8 -> IO TokenHeader-decodeLiteralFieldLineWithoutNameReference rbuf dyntbl _bp _w8 = do-    ff rbuf (-1)-    let hufdec = getHuffmanDecoder dyntbl-    key <- toToken <$> decodeS (.&. 0b00000111) (`testBit` 3) 3 hufdec rbuf-    val <- decodeS (`clearBit` 7) (`testBit` 7) 7 hufdec rbuf-    let ret = (key, val)-    qpackDebug dyntbl $-        putStrLn $-            "LiteralFieldLineWithoutNameReference " ++ showTokenHeader ret-    return ret- -- 4.5.3.  Indexed Field Line With Post-Base Index decodeIndexedFieldLineWithPostBaseIndex     :: ReadBuffer -> DynamicTable -> BasePoint -> Word8 -> IO TokenHeader@@ -109,34 +104,84 @@     i <- decodeI 4 (w8 .&. 0b00001111) rbuf     let hidx = DIndex $ fromPostBaseIndex (PostBaseIndex i) bp     ret <- atomically (entryTokenHeader <$> toIndexedEntry dyntbl hidx)-    qpackDebug dyntbl $+    qpackDebug dyntbl $ do+        checkHIndex dyntbl hidx         putStrLn $             "IndexedFieldLineWithPostBaseIndex ("                 ++ show hidx                 ++ " "-                ++ show i-                ++ "/"                 ++ show bp+                ++ " after "+                ++ show i                 ++ ") "                 ++ showTokenHeader ret     return ret +-- 4.5.4.  Literal Field Line With Name Reference+decodeLiteralFieldLineWithNameReference+    :: ReadBuffer+    -> DynamicTable+    -> HuffmanDecoder+    -> BasePoint+    -> Word8+    -> IO TokenHeader+decodeLiteralFieldLineWithNameReference rbuf dyntbl hufdec bp w8 = do+    i <- decodeI 4 (w8 .&. 0b00001111) rbuf+    let static = w8 `testBit` 4+        hidx+            | static = SIndex $ AbsoluteIndex i+            | otherwise = DIndex $ fromPreBaseIndex (PreBaseIndex i) bp+    key <- atomically (entryToken <$> toIndexedEntry dyntbl hidx)+    val <- decodeS (`clearBit` 7) (`testBit` 7) 7 hufdec rbuf+    let ret = (key, val)+    qpackDebug dyntbl $ do+        checkHIndex dyntbl hidx+        putStrLn $+            "LiteralFieldLineWithNameReference ("+                ++ show hidx+                ++ ") "+                ++ showTokenHeader ret+    return ret+ -- 4.5.5.  Literal Field Line With Post-Base Name Reference decodeLiteralFieldLineWithPostBaseNameReference-    :: ReadBuffer -> DynamicTable -> BasePoint -> Word8 -> IO TokenHeader-decodeLiteralFieldLineWithPostBaseNameReference rbuf dyntbl bp w8 = do+    :: ReadBuffer+    -> DynamicTable+    -> HuffmanDecoder+    -> BasePoint+    -> Word8+    -> IO TokenHeader+decodeLiteralFieldLineWithPostBaseNameReference rbuf dyntbl hufdec bp w8 = do     i <- decodeI 3 (w8 .&. 0b00000111) rbuf     let hidx = DIndex $ fromPostBaseIndex (PostBaseIndex i) bp     key <- atomically (entryToken <$> toIndexedEntry dyntbl hidx)-    let hufdec = getHuffmanDecoder dyntbl     val <- decodeS (`clearBit` 7) (`testBit` 7) 7 hufdec rbuf     let ret = (key, val)-    qpackDebug dyntbl $+    qpackDebug dyntbl $ do+        checkHIndex dyntbl hidx         putStrLn $             "LiteralFieldLineWithPostBaseNameReference ("                 ++ show hidx                 ++ ") "                 ++ showTokenHeader ret+    return ret++-- 4.5.6.  Literal Field Line With Literal Name+decodeLiteralFieldLineWithLiteralName+    :: ReadBuffer+    -> DynamicTable+    -> HuffmanDecoder+    -> BasePoint+    -> Word8+    -> IO TokenHeader+decodeLiteralFieldLineWithLiteralName rbuf dyntbl hufdec _bp _w8 = do+    ff rbuf (-1)+    key <- toToken <$> decodeS (.&. 0b00000111) (`testBit` 3) 3 hufdec rbuf+    val <- decodeS (`clearBit` 7) (`testBit` 7) 7 hufdec rbuf+    let ret = (key, val)+    qpackDebug dyntbl $+        putStrLn $+            "LiteralFieldLineWithLiteralName " ++ showTokenHeader ret     return ret  showTokenHeader :: TokenHeader -> String
Network/QPACK/HeaderBlock/Encode.hs view
@@ -1,22 +1,19 @@ {-# LANGUAGE BinaryLiterals #-}-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}  module Network.QPACK.HeaderBlock.Encode (     encodeHeader,     encodeTokenHeader,     EncodedFieldSection,     EncodedEncoderInstruction,-    EncodeStrategy (..),-    CompressionAlgo (..), ) where  import qualified Control.Exception as E import qualified Data.ByteString as B-import Data.IORef+import qualified Data.ByteString.Char8 as C8 import Network.ByteOrder+import Network.Control import Network.HPACK.Internal (-    CompressionAlgo (..),-    EncodeStrategy (..),     encodeI,     encodeS,     toEntryToken,@@ -40,15 +37,17 @@ --   Header block with prefix and instructions are returned. --   2048, 32, and 2048 bytes-buffers are --   temporally allocated for header block, prefix and encoder instructions.+--   If headers are too large, 'BufferOverrun' is thrown. encodeHeader-    :: EncodeStrategy-    -> DynamicTable+    :: DynamicTable     -> [Header]     -> IO (EncodedFieldSection, EncodedEncoderInstruction)-encodeHeader stgy dyntbl hs = do+encodeHeader dyntbl hs = do+    setBasePointToInsersionPoint dyntbl+    clearRequiredInsertCount dyntbl     (hb0, insb) <- withWriteBuffer' 2048 $ \wbuf1 ->         withWriteBuffer 2048 $ \wbuf2 -> do-            hs1 <- encodeTokenHeader wbuf1 wbuf2 stgy dyntbl ts+            hs1 <- encodeTokenHeader wbuf1 wbuf2 dyntbl ts             unless (null hs1) $ E.throwIO BufferOverrun     prefix <- withWriteBuffer 32 $ \wbuf -> encodePrefix wbuf dyntbl     let hb = prefix `B.append` hb0@@ -57,32 +56,24 @@     ts = map (\(k, v) -> let t = toToken (foldedCase k) in (t, v)) hs  -- | Converting 'TokenHeaderList' to the QPACK format.+--  'TokenHeaderList' must be smaller than or equal to workspaces.+--  Otherwise, 'BufferOverrun' is thrown. encodeTokenHeader     :: WriteBuffer     -- ^ Workspace for the body of header block     -> WriteBuffer     -- ^ Workspace for encoder instructions-    -> EncodeStrategy     -> DynamicTable     -> TokenHeaderList-    -> IO TokenHeaderList-    -- ^ Leftover-encodeTokenHeader wbuf1 wbuf2 EncodeStrategy{..} dyntbl ts0 = do+    -> IO [AbsoluteIndex]+encodeTokenHeader wbuf1 wbuf2 dyntbl ts0 = do     clearWriteBuffer wbuf1     clearWriteBuffer wbuf2-    setBasePointToInsersionPoint dyntbl     let revidx = getRevIndex dyntbl-    ref <- newIORef ts0-    case compressionAlgo of-        Static ->-            encodeStatic wbuf1 wbuf2 dyntbl revidx useHuffman ref ts0 `E.catch` \BufferOverrun -> return ()-        _ ->-            encodeLinear wbuf1 wbuf2 dyntbl revidx useHuffman ref ts0 `E.catch` \BufferOverrun -> return ()-    ts <- readIORef ref-    unless (null ts) $ do-        goBack wbuf1-        goBack wbuf2-    return ts+    ready <- isTableReady dyntbl+    if ready+        then encodeLinear wbuf1 wbuf2 dyntbl revidx True ts0+        else encodeStatic wbuf1 wbuf2 dyntbl revidx True ts0  encodeStatic     :: WriteBuffer@@ -90,87 +81,259 @@     -> DynamicTable     -> RevIndex     -> Bool-    -> IORef TokenHeaderList     -> TokenHeaderList-    -> IO ()-encodeStatic wbuf1 _wbuf2 dyntbl revidx huff ref ts0 = loop ts0-  where-    loop [] = return ()-    loop ((t, val) : ts) = do-        rr <- lookupRevIndex t val revidx-        case rr of-            KV hi -> do-                -- 4.5.2.  Indexed Field Line-                encodeIndexedFieldLine wbuf1 dyntbl hi-            K hi -> do-                -- 4.5.4.  Literal Field Line With Name Reference-                encodeLiteralFieldLineWithNameReference wbuf1 dyntbl hi val huff-            N -> do-                -- 4.5.6.  Literal Field Line Without Name Reference-                encodeLiteralFieldLineWithoutNameReference wbuf1 t val huff-        save wbuf1-        writeIORef ref ts-        loop ts+    -> IO [AbsoluteIndex]+encodeStatic wbuf1 _wbuf2 dyntbl revidx huff ts0 = do+    mapM_ (encStatic wbuf1 dyntbl revidx huff) ts0+    return [] +encStatic+    :: WriteBuffer -> DynamicTable -> RevIndex -> Bool -> (Token, FieldValue) -> IO ()+encStatic wbuf1 dyntbl revidx huff (t, val) = do+    rr <- lookupRevIndex t val revidx+    case rr of+        KV hi -> do+            -- 4.5.2.  Indexed Field Line+            encodeIndexedFieldLine wbuf1 dyntbl hi+        K i -> do+            -- 4.5.4.  Literal Field Line With Name Reference+            encodeLiteralFieldLineWithNameReference wbuf1 dyntbl i val huff+        N -> do+            -- 4.5.6.  Literal Field Line with Literal Name+            encodeLiteralFieldLineWithLiteralName wbuf1 dyntbl t val huff+ encodeLinear     :: WriteBuffer     -> WriteBuffer     -> DynamicTable     -> RevIndex     -> Bool-    -> IORef TokenHeaderList     -> TokenHeaderList-    -> IO ()-encodeLinear wbuf1 wbuf2 dyntbl revidx huff ref ts0 = loop ts0+    -> IO [AbsoluteIndex]+encodeLinear wbuf1 wbuf2 dyntbl revidx huff ts0 =+    catMaybes <$> mapM (encLinear wbuf1 wbuf2 dyntbl revidx huff) ts0++encLinear+    :: WriteBuffer+    -> WriteBuffer+    -> DynamicTable+    -> RevIndex+    -> Bool+    -> (Token, FieldValue)+    -> IO (Maybe AbsoluteIndex)+encLinear wbuf1 wbuf2 dyntbl revidx huff (t, val) = do+    rr <- lookupRevIndex t val revidx+    qpackDebug dyntbl $ do+        tblsiz <- getTableCapacity dyntbl+        base <- getBasePoint dyntbl+        insPnt <- getInsertionPoint dyntbl+        putStrLn $+            "    Table size: "+                ++ show tblsiz+                ++ " "+                ++ show base+                ++ " "+                ++ show insPnt+        putStr "    "+        printReferences dyntbl+        putStrLn $ show rr ++ ": " ++ show (tokenKey t) ++ " " ++ show val ++ ""+    case rr of+        KV hi@(SIndex _) -> do+            -- 4.5.2/4.5.3+            encodeIndexed wbuf1 dyntbl hi+            return Nothing+        KV hi@(DIndex ai) -> do+            qpackDebug dyntbl $ checkAbsoluteIndex dyntbl ai "KV (1)"+            withDIndex ai $ do+                -- 4.5.2/4.5.3+                encodeIndexed wbuf1 dyntbl hi+                increaseReference dyntbl ai+                return $ Just ai+        K hi@(SIndex i) -> tryInsertVal hi $ do+            insertWithNameReference val ent Nothing $ Left i+        K hi@(DIndex ai) -> do+            qpackDebug dyntbl $ checkAbsoluteIndex dyntbl ai "K (1)"+            withDIndex ai $ tryInsertVal hi $ do+                ridx <- toInsRelativeIndex ai <$> getInsertionPoint dyntbl+                insertWithNameReference val ent (Just ai) $ Right ridx+        N -> tryInsertKeyVal $ insertWithLiteralName val ent   where-    loop [] = return ()-    loop ((t, val) : ts) = do-        rr <- lookupRevIndex t val revidx-        case rr of-            KV hi -> do-                -- 4.5.2.  Indexed Field Line-                encodeIndexedFieldLine wbuf1 dyntbl hi-            K hi-                | shouldBeIndexed t -> do-                    insidx <- case hi of-                        SIndex i -> return $ Left i-                        DIndex i -> do-                            ip <- getInsertionPoint dyntbl-                            return $ Right $ toInsRelativeIndex i ip-                    let ins = InsertWithNameReference insidx val-                    encodeEI wbuf2 True ins-                    dai <- insertEntryToEncoder (toEntryToken t val) dyntbl-                    -- 4.5.3.  Indexed Field Line With Post-Base Index-                    encodeIndexedFieldLineWithPostBaseIndex wbuf1 dyntbl dai-                | otherwise -> do-                    -- 4.5.4.  Literal Field Line With Name Reference-                    encodeLiteralFieldLineWithNameReference wbuf1 dyntbl hi val huff-            N-                | shouldBeIndexed t -> do-                    let ins = InsertWithLiteralName t val-                    encodeEI wbuf2 True ins-                    dai <- insertEntryToEncoder (toEntryToken t val) dyntbl-                    encodeIndexedFieldLineWithPostBaseIndex wbuf1 dyntbl dai-                | otherwise -> do-                    -- 4.5.6.  Literal Field Line Without Name Reference-                    encodeLiteralFieldLineWithoutNameReference wbuf1 t val huff-        save wbuf1-        save wbuf2-        writeIORef ref ts-        loop ts+    ent = toEntryToken t val+    key = tokenFoldedKey t+    lru = getLruCache dyntbl +    withDIndex ai action = do+        blocked <- wouldInstructionBeBlocked dyntbl ai+        canUseDynamicTable <- checkBlockedStreams dyntbl+        if canUseDynamicTable || not blocked+            then action+            else encodeLiteralFieldLineStatic++    insertWithNameReference v e mai insidx =+        insertWith mai e $ InsertWithNameReference insidx v++    insertWithLiteralName v e =+        insertWith Nothing e $ InsertWithLiteralName t v++    insertWith maiForKey e ins = do+        encodeEI wbuf2 True ins+        ai <- insertEntryToEncoder e dyntbl+        qpackDebug dyntbl $ putStrLn $ show ins ++ ": " ++ show ai+        canUseDynamicTable <- checkBlockedStreams dyntbl+        if canUseDynamicTable+            then do+                -- 4.5.4/4.5.5+                encodeWithNameReference wbuf1 dyntbl (DIndex ai) val huff+                increaseReference dyntbl ai+                return $ Just ai+            else encodeLiteralValue maiForKey++    encodeLiteralValue Nothing = encodeLiteralFieldLineStatic+    encodeLiteralValue (Just ai) = do+        blocked <- wouldInstructionBeBlocked dyntbl ai+        canUseDynamicTable <- checkBlockedStreams dyntbl+        if canUseDynamicTable || not blocked+            then do+                -- 4.5.4/4.5.5+                encodeWithNameReference wbuf1 dyntbl (DIndex ai) val huff+                increaseReference dyntbl ai+                return $ Just ai+            else encodeLiteralFieldLineStatic++    tryInsertVal hi action = do+        -- Field representation MUST not refer to a dropped entry+        -- on insertion.+        let possiblelyDropMySelf = case hi of+                SIndex _ -> Nothing+                DIndex ai -> Just ai+        ok <- checkExistenceAndSpace ent key val possiblelyDropMySelf "Val"+        if ok+            then action+            else do+                -- 4.5.4/4.5.5+                encodeWithNameReference wbuf1 dyntbl hi val huff+                case hi of+                    SIndex _ -> return Nothing+                    DIndex dai -> do+                        increaseReference dyntbl dai+                        return $ Just dai++    tryInsertKeyVal action = do+        ok <- checkExistenceAndSpace ent key val Nothing "KeyVal"+        case ok of+            True -> action+            False -> tryInsertKey++    tryInsertKey+        | isJust (tokenToStaticIndex t) = encodeLiteralFieldLineStatic+        | otherwise = do+            mdai <- isKeyRegistered key revidx+            case mdai of+                Nothing -> do+                    let val' = ""+                        ent' = toEntryToken t val'+                    okK <- checkExistenceAndSpace ent' key val' Nothing "Key"+                    if okK+                        then insertWithLiteralName val' ent'+                        else encodeLiteralFieldLineStatic+                Just dai -> encodeLiteralFieldLineDynamic dai++    encodeLiteralFieldLineDynamic dai = do+        canUseDynamicTable <- checkBlockedStreams dyntbl+        if canUseDynamicTable+            then do+                -- 4.5.4/4.5.5+                encodeWithNameReference wbuf1 dyntbl (DIndex dai) val huff++                increaseReference dyntbl dai+                return $ Just dai+            else do+                -- 4.5.6.  Literal Field Line with Literal Name+                encodeLiteralFieldLineWithLiteralName wbuf1 dyntbl t val huff+                tryTailDuplication+                return Nothing++    encodeLiteralFieldLineStatic = do+        case tokenToStaticIndex t of+            Just i -> do+                -- 4.5.4/4.5.5+                encodeWithNameReference wbuf1 dyntbl (SIndex i) val huff+            Nothing -> do+                -- 4.5.6.  Literal Field Line with Literal Name+                encodeLiteralFieldLineWithLiteralName wbuf1 dyntbl t val huff+                tryTailDuplication+        return Nothing++    checkExistence k v tag = do+        (_, exist) <- cached lru (k, v) (return ())+        qpackDebug dyntbl $+            putStrLn $+                (if exist then "    HIT for " ++ tag else "    not HIT for " ++ tag)+                    ++ " "+                    ++ show k+                    ++ " "+                    ++ show v+        return exist++    checkSpace e possiblelyDropMySelf tag = do+        spaceOK <- canInsertEntry dyntbl e possiblelyDropMySelf+        unless spaceOK $ do+            adjustDrainingPoint dyntbl+            qpackDebug dyntbl $ putStrLn $ "    NO SPACE for " ++ tag+        return spaceOK++    checkExistenceAndSpace e k v possiblelyDropMySelf tag = do+        exist <- checkExistence k v tag+        if exist+            then checkSpace e possiblelyDropMySelf tag+            else return False++    tryTailDuplication = do+        mx <- checkTailDuplication dyntbl+        case mx of+            Nothing -> return ()+            Just ai -> do+                ridx <- toInsRelativeIndex ai <$> getInsertionPoint dyntbl+                let ins = Duplicate ridx+                encodeEI wbuf2 True ins+                qpackDebug dyntbl $ putStrLn $ (show ins) ++ " = " ++ show ai+                nai <- tailDuplication dyntbl+                qpackDebug dyntbl $ putStrLn $ "Duplicate: " ++ show ai ++ " -> " ++ show nai++-- 4.5.2/4.5.3+encodeIndexed :: WriteBuffer -> DynamicTable -> HIndex -> IO ()+encodeIndexed wbuf dyntbl hi@(SIndex (AbsoluteIndex idx)) = do+    encodeI wbuf set11 6 idx+    qpackDebug dyntbl $ putStrLn $ "IndexedFieldLine (" ++ show hi ++ ")"+encodeIndexed wbuf dyntbl hi@(DIndex ai) = do+    qpackDebug dyntbl $ checkAbsoluteIndex dyntbl ai "encodeIndexed"+    updateRequiredInsertCount dyntbl ai+    bp <- getBasePoint dyntbl+    case toBaseIndex ai bp of+        Left (PreBaseIndex idx) -> do+            encodeI wbuf set10 6 idx+            qpackDebug dyntbl $ putStrLn $ "IndexedFieldLine (" ++ show hi ++ ")"+        Right (PostBaseIndex idx) -> do+            encodeI wbuf set0001 4 idx+            qpackDebug dyntbl $+                putStrLn $+                    "IndexedFieldLineWithPostBaseIndex (" ++ show hi ++ ")"+ -- 4.5.2.  Indexed Field Line encodeIndexedFieldLine :: WriteBuffer -> DynamicTable -> HIndex -> IO () encodeIndexedFieldLine wbuf dyntbl hi = do     (idx, set) <- case hi of         SIndex (AbsoluteIndex i) -> return (i, set11)         DIndex ai -> do-            updateLargestReference dyntbl ai+            qpackDebug dyntbl $ checkAbsoluteIndex dyntbl ai "encodeIndexedFieldLine"+            updateRequiredInsertCount dyntbl ai             bp <- getBasePoint dyntbl-            let HBRelativeIndex i = toHBRelativeIndex ai bp+            let PreBaseIndex i = toPreBaseIndex ai bp             return (i, set10)     encodeI wbuf set 6 idx+    qpackDebug dyntbl $ putStrLn $ "IndexedFieldLine (" ++ show hi ++ ")" +{- -- 4.5.3.  Indexed Field Line With Post-Base Index encodeIndexedFieldLineWithPostBaseIndex     :: WriteBuffer@@ -178,31 +341,93 @@     -> AbsoluteIndex -- in Dynamic table     -> IO () encodeIndexedFieldLineWithPostBaseIndex wbuf dyntbl ai = do+    updateRequiredInsertCount dyntbl ai     bp <- getBasePoint dyntbl-    let HBRelativeIndex idx = toHBRelativeIndex ai bp+    let PostBaseIndex idx = toPostBaseIndex ai bp     encodeI wbuf set0001 4 idx+    qpackDebug dyntbl $ putStrLn "IndexedFieldLineWithPostBaseIndex "+-} +---------------------------------------------------------------++-- 4.5.4/4.5.5+encodeWithNameReference+    :: WriteBuffer -> DynamicTable -> HIndex -> ByteString -> Bool -> IO ()+encodeWithNameReference wbuf dyntbl hidx@(SIndex (AbsoluteIndex idx)) val huff = do+    encodeI wbuf set0101 4 idx+    encodeS wbuf huff id set1 7 val+    qpackDebug dyntbl $+        putStrLn $+            "LiteralFieldLineWithNameReference (" ++ show hidx ++ ")"+encodeWithNameReference wbuf dyntbl hidx@(DIndex ai) val huff = do+    qpackDebug dyntbl $ checkAbsoluteIndex dyntbl ai "encodeWithNameReference"+    updateRequiredInsertCount dyntbl ai+    bp <- getBasePoint dyntbl+    case toBaseIndex ai bp of+        Left (PreBaseIndex idx) -> do+            encodeI wbuf set0100 4 idx+            encodeS wbuf huff id set1 7 val+            qpackDebug dyntbl $+                putStrLn $+                    "LiteralFieldLineWithNameReference (" ++ show hidx ++ ")"+        Right (PostBaseIndex idx) -> do+            encodeI wbuf set00000 3 idx+            encodeS wbuf huff id set1 7 val+            qpackDebug dyntbl $+                putStrLn $+                    "LiteralFieldLineWithPostBaseNameReference (DIndex " ++ show ai ++ ")"+ -- 4.5.4.  Literal Field Line With Name Reference encodeLiteralFieldLineWithNameReference     :: WriteBuffer -> DynamicTable -> HIndex -> ByteString -> Bool -> IO ()-encodeLiteralFieldLineWithNameReference wbuf dyntbl hi val huff = do-    (idx, set) <- case hi of+encodeLiteralFieldLineWithNameReference wbuf dyntbl hidx val huff = do+    (idx, set) <- case hidx of         SIndex (AbsoluteIndex i) -> return (i, set0101)         DIndex ai -> do-            updateLargestReference dyntbl ai+            updateRequiredInsertCount dyntbl ai             bp <- getBasePoint dyntbl-            let HBRelativeIndex i = toHBRelativeIndex ai bp+            let PreBaseIndex i = toPreBaseIndex ai bp             return (i, set0100)     encodeI wbuf set 4 idx     encodeS wbuf huff id set1 7 val+    qpackDebug dyntbl $+        putStrLn $+            "LiteralFieldLineWithNameReference (" ++ show hidx ++ ")" +{- -- 4.5.5.  Literal Field Line With Post-Base Name Reference--- not implemented+encodeLiteralFieldLineWithPostBaseNameReference+    :: WriteBuffer+    -> DynamicTable+    -> AbsoluteIndex -- in Dynamic table+    -> ByteString+    -> Bool+    -> IO ()+encodeLiteralFieldLineWithPostBaseNameReference wbuf dyntbl ai val huff = do+    updateRequiredInsertCount dyntbl ai+    bp <- getBasePoint dyntbl+    let PostBaseIndex idx = toPostBaseIndex ai bp+    encodeI wbuf set00000 3 idx+    encodeS wbuf huff id set1 7 val+    qpackDebug dyntbl $+        putStrLn $+            "LiteralFieldLineWithPostBaseNameReference (DIndex " ++ show ai ++ ")"+-} --- 4.5.6.  Literal Field Line Without Name Reference-encodeLiteralFieldLineWithoutNameReference-    :: WriteBuffer -> Token -> ByteString -> Bool -> IO ()-encodeLiteralFieldLineWithoutNameReference wbuf token val huff = do+---------------------------------------------------------------++-- 4.5.6.  Literal Field Line with Literal Name+encodeLiteralFieldLineWithLiteralName+    :: WriteBuffer -> DynamicTable -> Token -> ByteString -> Bool -> IO ()+encodeLiteralFieldLineWithLiteralName wbuf dyntbl token val huff = do     let key = tokenFoldedKey token     encodeS wbuf huff set0010 set00001 3 key     encodeS wbuf huff id set1 7 val+    qpackDebug dyntbl $+        putStrLn $+            "LiteralFieldLineWithLiteralName " ++ showHeader key val++---------------------------------------------------------------++showHeader :: ByteString -> ByteString -> String+showHeader key val = "\"" ++ C8.unpack key ++ "\" \"" ++ C8.unpack val ++ "\""
Network/QPACK/HeaderBlock/Prefix.hs view
@@ -26,25 +26,27 @@ -- 4 -- >>> encodeRequiredInsertCount 128 1000 -- 233-encodeRequiredInsertCount :: Int -> InsertionPoint -> Int+encodeRequiredInsertCount :: Int -> RequiredInsertCount -> Int encodeRequiredInsertCount _ 0 = 0-encodeRequiredInsertCount maxEntries (InsertionPoint reqInsertCount) =+encodeRequiredInsertCount maxEntries (RequiredInsertCount reqInsertCount) =     (reqInsertCount `mod` (2 * maxEntries)) + 1  -- | for decoder -- -- >>> decodeRequiredInsertCount 3 10 4--- InsertionPoint 9+-- RequiredInsertCount 9 -- >>> decodeRequiredInsertCount 128 990 233--- InsertionPoint 1000-decodeRequiredInsertCount :: Int -> InsertionPoint -> Int -> InsertionPoint+-- RequiredInsertCount 1000+decodeRequiredInsertCount+    :: Int -> InsertionPoint -> Int -> RequiredInsertCount decodeRequiredInsertCount _ _ 0 = 0+decodeRequiredInsertCount 0 _ n = RequiredInsertCount (n - 1) decodeRequiredInsertCount maxEntries (InsertionPoint totalNumberOfInserts) encodedInsertCount     | encodedInsertCount > fullRange = E.throw IllegalInsertCount     | reqInsertCount > maxValue && reqInsertCount <= fullRange =         E.throw IllegalInsertCount-    | reqInsertCount > maxValue = InsertionPoint (reqInsertCount - fullRange)-    | otherwise = InsertionPoint reqInsertCount+    | reqInsertCount > maxValue = RequiredInsertCount (reqInsertCount - fullRange)+    | otherwise = RequiredInsertCount reqInsertCount   where     fullRange = 2 * maxEntries     maxValue = totalNumberOfInserts + maxEntries@@ -58,8 +60,8 @@ -- (False,3) -- >>> encodeBase 9 6 -- (True,2)-encodeBase :: InsertionPoint -> BasePoint -> (Bool, Int)-encodeBase (InsertionPoint reqInsCnt) (BasePoint base)+encodeBase :: RequiredInsertCount -> BasePoint -> (Bool, Int)+encodeBase (RequiredInsertCount reqInsCnt) (BasePoint base)     | diff >= 0 = (False, diff) -- base - reqInsCnt     | otherwise = (True, negate diff - 1) -- reqInsCnt - base - 1   where@@ -70,9 +72,9 @@ -- BasePoint 9 -- >>> decodeBase 9 True 2 -- BasePoint 6-decodeBase :: InsertionPoint -> Bool -> Int -> BasePoint-decodeBase (InsertionPoint reqInsCnt) False deltaBase = BasePoint (reqInsCnt + deltaBase)-decodeBase (InsertionPoint reqInsCnt) True deltaBase = BasePoint (reqInsCnt - deltaBase - 1)+decodeBase :: RequiredInsertCount -> Bool -> Int -> BasePoint+decodeBase (RequiredInsertCount reqInsCnt) False deltaBase = BasePoint (reqInsCnt + deltaBase)+decodeBase (RequiredInsertCount reqInsCnt) True deltaBase = BasePoint (reqInsCnt - deltaBase - 1)  ---------------------------------------------------------------- @@ -83,7 +85,8 @@     clearWriteBuffer wbuf     maxEntries <- getMaxNumOfEntries dyntbl     baseIndex <- getBasePoint dyntbl-    reqInsCnt <- getLargestReference dyntbl+    reqInsCnt <- getRequiredInsertCount dyntbl+    qpackDebug dyntbl $ print reqInsCnt     -- Required Insert Count     let ric = encodeRequiredInsertCount maxEntries reqInsCnt     encodeI wbuf set0 8 ric@@ -96,7 +99,7 @@  -- | Decoding the prefix part of header block. decodePrefix-    :: ReadBuffer -> DynamicTable -> IO (InsertionPoint, BasePoint, Bool)+    :: ReadBuffer -> DynamicTable -> IO (RequiredInsertCount, BasePoint, Bool) decodePrefix rbuf dyntbl = do     maxEntries <- getMaxNumOfEntries dyntbl     totalNumberOfInserts <- getInsertionPoint dyntbl@@ -110,6 +113,12 @@     let baseIndex = decodeBase reqInsCnt s delta     qpackDebug dyntbl $         putStrLn $-            "Required" ++ show reqInsCnt ++ ", " ++ show baseIndex+            show reqInsCnt+                ++ ", "+                ++ show baseIndex+                ++ ", "+                ++ show totalNumberOfInserts+                ++ ", maxN "+                ++ show maxEntries     let needAck = reqInsCnt /= 0     return (reqInsCnt, baseIndex, needAck)
Network/QPACK/Instruction.hs view
@@ -59,14 +59,18 @@             ++ BS8.unpack v             ++ "\""     show (InsertWithNameReference (Right (InsRelativeIndex idx)) v) =-        "InsertWithNameReference (DynRel " ++ show idx ++ ") \"" ++ BS8.unpack v ++ "\""+        "InsertWithNameReference (InsRelative "+            ++ show idx+            ++ ") \""+            ++ BS8.unpack v+            ++ "\""     show (InsertWithLiteralName t v) =         "InsertWithLiteralName \""             ++ BS8.unpack (foldedCase (tokenKey t))             ++ "\" \""             ++ BS8.unpack v             ++ "\""-    show (Duplicate (InsRelativeIndex idx)) = "Duplicate (DynRel " ++ show idx ++ ")"+    show (Duplicate (InsRelativeIndex idx)) = "Duplicate (InsRelative " ++ show idx ++ ")"  ---------------------------------------------------------------- @@ -92,10 +96,12 @@ decodeEncoderInstructions'     :: ByteString -> IO ([EncoderInstruction], ByteString) decodeEncoderInstructions' bs = do-    let bufsiz = 4096-    gcbuf <- mallocPlainForeignPtrBytes 4096+    let bufsiz = 2048+    gcbuf <- mallocPlainForeignPtrBytes 2048     decodeEncoderInstructions (decodeH gcbuf bufsiz) bs +-- HuffmanDecoder is occupied.+-- No other threads MUST NOT use it. decodeEncoderInstructions     :: HuffmanDecoder -> ByteString -> IO ([EncoderInstruction], ByteString) decodeEncoderInstructions hufdec bs = withReadBuffer bs $ loop id
Network/QPACK/Table.hs view
@@ -1,36 +1,7 @@ module Network.QPACK.Table (-    -- * Dynamic table-    DynamicTable,-    newDynamicTableForEncoding,-    newDynamicTableForDecoding,--    -- * Getter and setter-    getMaxNumOfEntries,-    setBasePointToInsersionPoint,-    getBasePoint,-    getInsertionPoint,-    getInsertionPointSTM,-    checkInsertionPoint,-    getLargestReference,-    updateLargestReference,--    -- * Entry-    insertEntryToEncoder,-    insertEntryToDecoder,+    module Network.QPACK.Table.Dynamic,+    module Network.QPACK.Table.RevIndex,     toIndexedEntry,--    -- * Reverse index-    RevIndex,-    RevResult (..),-    getRevIndex,-    lookupRevIndex,--    -- * Misc-    getHuffmanDecoder,-    getSendDI,-    setDebugQPACK,-    getDebugQPACK,-    qpackDebug, ) where  import Control.Concurrent.STM
Network/QPACK/Table/Dynamic.hs view
@@ -1,190 +1,254 @@ {-# LANGUAGE RecordWildCards #-} {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} -module Network.QPACK.Table.Dynamic where+module Network.QPACK.Table.Dynamic (+    -- * Dynamic table+    DynamicTable,+    newDynamicTableForEncoding,+    newDynamicTableForDecoding, +    -- * Capacity+    isTableReady,+    getTableCapacity,+    setTableCapacity,+    getMaxNumOfEntries,++    -- * Entry+    insertEntryToDecoder,+    insertEntryToEncoder,+    toDynamicEntry,++    -- * Section+    Section (..),+    insertSection,+    getAndDelSection,+    increaseReference,+    decreaseReference,++    -- * Streams+    getMaxBlockedStreams,+    setMaxBlockedStreams,+    tryIncreaseStreams,+    decreaseStreams,++    -- * Blocked streams+    insertBlockedStreamE,+    deleteBlockedStreamE,+    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 qualified Control.Exception as E import Data.Array.Base (unsafeRead, unsafeWrite)-import Data.Array.MArray (newArray)+import Data.Array.IO (IOArray, newArray) import Data.IORef-import Network.ByteOrder+import Data.IntMap.Strict (IntMap)+import qualified Data.IntMap.Strict as IntMap+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,-    GCBuffer,     HuffmanDecoder,     Index,     Size,-    decH,+    decodeH,     dummyEntry,+    entryFieldValue,+    entryHeaderName,+    entrySize,     maxNumbers,  )+import System.IO.Unsafe (unsafePerformIO) -import Imports 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 -- Reverse index-        (IORef InsertionPoint)-    | DecodeInfo HuffmanDecoder (ByteString -> IO ())+        { revIndex            :: RevIndex -- Reverse index+        , requiredInsertCount :: IORef RequiredInsertCount+        , droppingPoint       :: IORef AbsoluteIndex+        , drainingPoint       :: IORef AbsoluteIndex+        , knownReceivedCount  :: TVar Int+        , referenceCounters   :: IORef (IOArray Index Reference)+        , sections            :: IORef (IntMap Section)+        , 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-    , droppingPoint :: IORef AbsoluteIndex-    , drainingPoint :: IORef AbsoluteIndex-    , insertionPoint :: TVar InsertionPoint-    , basePoint :: IORef BasePoint-    , maxNumOfEntries :: TVar Int-    , circularTable :: TVar Table-    , debugQPACK :: IORef Bool+    { codeInfo          :: CodeInfo+    , insertionPoint    :: TVar InsertionPoint+    , maxNumOfEntries   :: TVar Int+    , 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     }---- knownReceivedCount :: TVar Int--type Table = TArray Index Entry--------------------------------------------------------------------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--checkInsertionPoint :: DynamicTable -> InsertionPoint -> IO ()-checkInsertionPoint DynamicTable{..} reqip = atomically $ do-    ip <- readTVar insertionPoint-    check (reqip <= ip)+{- FOURMOLU_ENABLE -}  ---------------------------------------------------------------- +{- FOURMOLU_DISABLE -} -- | Creating 'DynamicTable' for encoding. newDynamicTableForEncoding-    :: Size-    -- ^ The dynamic table size+    :: (ByteString -> IO ())     -> IO DynamicTable-newDynamicTableForEncoding maxsiz = do-    rev <- newRevIndex-    ref <- newIORef 0-    let info = EncodeInfo rev ref-    newDynamicTable maxsiz info+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 dynamic table size-    -> Size     -- ^ The size of temporary buffer for Huffman decoding     -> (ByteString -> IO ())     -> IO DynamicTable-newDynamicTableForDecoding maxsiz huftmpsiz sendDI = do+newDynamicTableForDecoding huftmpsiz sendDI = do     gcbuf <- mallocPlainForeignPtrBytes huftmpsiz-    tvar <- newTVarIO $ Just (gcbuf, huftmpsiz)-    let decoder = decodeHLock tvar-        info = DecodeInfo decoder sendDI-    newDynamicTable maxsiz info--decodeHLock-    :: TVar (Maybe (GCBuffer, Int)) -> ReadBuffer -> Int -> IO ByteString-decodeHLock tvar rbuf len = E.bracket lock unlock $ \(gcbuf, bufsiz) ->-    withForeignPtr gcbuf $ \buf -> do-        wbuf <- newWriteBuffer buf bufsiz-        decH wbuf rbuf len-        toByteString wbuf-  where-    lock = atomically $ do-        mx <- readTVar tvar-        case mx of-            Nothing -> retry-            Just x -> do-                writeTVar tvar Nothing-                return x-    unlock x = atomically $ writeTVar tvar $ Just x+    let huffmanDecoder = decodeH gcbuf huftmpsiz+    blockedStreamsD <- newIORef 0+    newDynamicTable DecodeInfo{..} sendDI -newDynamicTable :: Size -> CodeInfo -> IO DynamicTable-newDynamicTable maxsiz info = do-    tbl <- atomically $ newArray (0, end) dummyEntry-    DynamicTable info-        <$> newIORef 0 -- droppingPoint-        <*> newIORef 0 -- drainingPoint-        <*> newTVarIO 0 -- insertionPoint-        <*> newIORef 0 -- basePoint-        <*> newTVarIO maxN -- maxNumOfEntries-        <*> newTVarIO tbl -- maxDynamicTableSize-        <*> newIORef False -- debugQPACK-  where-    maxN = maxNumbers maxsiz-    end = maxN - 1+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+    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{..}  ---------------------------------------------------------------- -setDebugQPACK :: DynamicTable -> IO ()-setDebugQPACK DynamicTable{..} = writeIORef debugQPACK True--getDebugQPACK :: DynamicTable -> IO Bool-getDebugQPACK DynamicTable{..} = readIORef debugQPACK+isTableReady :: DynamicTable -> IO Bool+isTableReady DynamicTable{..} = readIORef capaReady -qpackDebug :: DynamicTable -> IO () -> IO ()-qpackDebug DynamicTable{..} action = do-    debug <- readIORef debugQPACK-    when debug action+getTableCapacity :: DynamicTable -> IO Int+getTableCapacity DynamicTable{..} = readTVarIO tableSize -----------------------------------------------------------------+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  getMaxNumOfEntries :: DynamicTable -> IO Int getMaxNumOfEntries DynamicTable{..} = readTVarIO maxNumOfEntries  ---------------------------------------------------------------- -{-# INLINE getRevIndex #-}-getRevIndex :: DynamicTable -> RevIndex-getRevIndex DynamicTable{..} = rev-  where-    EncodeInfo rev _ = codeInfo--getHuffmanDecoder :: DynamicTable -> HuffmanDecoder-getHuffmanDecoder DynamicTable{..} = huf-  where-    DecodeInfo huf _ = codeInfo--getSendDI :: DynamicTable -> (ByteString -> IO ())-getSendDI DynamicTable{..} = sendDI-  where-    DecodeInfo _ sendDI = codeInfo--------------------------------------------------------------------clearLargestReference :: DynamicTable -> IO ()-clearLargestReference DynamicTable{..} = writeIORef ref 0-  where-    EncodeInfo _ ref = codeInfo--getLargestReference :: DynamicTable -> IO InsertionPoint-getLargestReference DynamicTable{..} = readIORef ref-  where-    EncodeInfo _ ref = codeInfo--updateLargestReference :: DynamicTable -> AbsoluteIndex -> IO ()-updateLargestReference DynamicTable{..} (AbsoluteIndex idx) = do-    let nidx = InsertionPoint idx-    oidx <- readIORef ref-    when (nidx > oidx) $ writeIORef ref nidx-  where-    EncodeInfo _ ref = codeInfo------------------------------------------------------------------- insertEntryToEncoder :: Entry -> DynamicTable -> IO AbsoluteIndex insertEntryToEncoder ent dyntbl@DynamicTable{..} = do     InsertionPoint insp <- atomically $ do@@ -197,10 +261,13 @@     atomically $ unsafeWrite table i ent     let revtbl = getRevIndex dyntbl     let ai = AbsoluteIndex insp-    insertRevIndex ent (DIndex ai) revtbl+    insertRevIndex ent ai revtbl+    atomically $ modifyTVar' tableSize (+ entrySize ent)+    dropIfNecessary dyntbl+    resetReference dyntbl ai     return ai -insertEntryToDecoder :: Entry -> DynamicTable -> STM ()+insertEntryToDecoder :: Entry -> DynamicTable -> STM AbsoluteIndex insertEntryToDecoder ent DynamicTable{..} = do     x@(InsertionPoint insp) <- readTVar insertionPoint     writeTVar insertionPoint (x + 1)@@ -208,6 +275,8 @@     let i = insp `mod` maxN     table <- readTVar circularTable     unsafeWrite table i ent+    modifyTVar' tableSize (+ entrySize ent)+    return $ AbsoluteIndex insp  toDynamicEntry :: DynamicTable -> AbsoluteIndex -> STM Entry toDynamicEntry DynamicTable{..} (AbsoluteIndex idx) = do@@ -215,3 +284,457 @@     let i = idx `mod` maxN     table <- readTVar circularTable     unsafeRead table i++----------------------------------------------------------------++insertSection :: DynamicTable -> StreamId -> Section -> IO ()+insertSection DynamicTable{..} sid section = atomicModifyIORef' sections ins+  where+    ins m =+        let m' = IntMap.insert sid section m+         in (m', ())+    EncodeInfo{..} = codeInfo++getAndDelSection :: DynamicTable -> StreamId -> IO (Maybe Section)+getAndDelSection DynamicTable{..} sid = atomicModifyIORef' sections getAndDel+  where+    getAndDel m =+        let (msec, m') = IntMap.updateLookupWithKey f sid m+         in (m', msec)+    f _ _ = Nothing -- delete the entry if found+    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+    curr <- atomicModifyIORef' blockedStreamsD (\n -> (n + 1, n + 1))+    return (curr <= lim)+  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 =+    modifyIORef' blockedStreamsE (Set.insert sid)+  where+    EncodeInfo{..} = codeInfo++deleteBlockedStreamE :: DynamicTable -> StreamId -> IO ()+deleteBlockedStreamE DynamicTable{..} sid =+    modifyIORef' blockedStreamsE (Set.delete sid)+  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+    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.+    Reference current total <- unsafeRead arr i+    if current == 0 && 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++----------------------------------------------------------------++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+                    Reference current total <- unsafeRead refs i+                    if current == 0 && total >= 1+                        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+            Reference current total <- unsafeRead refs i+            if current == 0 && total >= 1+                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
Network/QPACK/Table/RevIndex.hs view
@@ -9,7 +9,10 @@     lookupRevIndex,     lookupRevIndex',     insertRevIndex,-    deleteRevIndexList,+    deleteRevIndex,+    tokenToStaticIndex,+    isKeyRegistered,+    lookupRevIndexS, ) where  import Data.Array (Array)@@ -17,8 +20,12 @@ import Data.Array.Base (unsafeAt) import Data.Function (on) import Data.IORef+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NE import Data.Map.Strict (Map) import qualified Data.Map.Strict as M+import Data.OrdPSQ (OrdPSQ)+import qualified Data.OrdPSQ as PSQ import Network.HPACK.Internal (Entry (..)) import Network.HTTP.Semantics @@ -29,37 +36,36 @@  ---------------------------------------------------------------- -data RevResult = N | K HIndex | KV HIndex deriving (Eq, Show)+data RevResult+    = N+    | K HIndex+    | KV HIndex+    deriving (Eq, Show)  ---------------------------------------------------------------- -data RevIndex = RevIndex DynamicRevIndex OtherRevIdex+data RevIndex = RevIndex DynamicRevIndex OtherRevIndex -type DynamicRevIndex = Array Int (IORef ValueMap)+---------------------------------------------------------------- -data KeyValue = KeyValue FieldName FieldValue deriving (Eq, Ord)+type DynamicRevIndex = Array Int (IORef DynamicValueMap) --- We always create an index for a pair of an unknown header and its value--- in Linear{H}.-type OtherRevIdex = IORef (Map KeyValue HIndex)+type DynamicValueMap = Map FieldValue AbsoluteIndex -{-# SPECIALIZE INLINE M.lookup ::-    KeyValue -> M.Map KeyValue HIndex -> Maybe HIndex-    #-}-{-# SPECIALIZE INLINE M.delete ::-    KeyValue -> M.Map KeyValue HIndex -> M.Map KeyValue HIndex-    #-}-{-# SPECIALIZE INLINE M.insert ::-    KeyValue -> HIndex -> M.Map KeyValue HIndex -> M.Map KeyValue HIndex-    #-}+---------------------------------------------------------------- +type OtherRevIndex = IORef (Map FieldName OtherValueMap) -- dynamic table only++type OtherValueMap = OrdPSQ FieldValue Int AbsoluteIndex+ ----------------------------------------------------------------  type StaticRevIndex = Array Int StaticEntry -data StaticEntry = StaticEntry HIndex (Maybe ValueMap) deriving (Show)+data StaticEntry = StaticEntry AbsoluteIndex (Maybe StaticValueMap)+    deriving (Show) -type ValueMap = Map FieldValue HIndex+type StaticValueMap = Map FieldValue AbsoluteIndex  ---------------------------------------------------------------- @@ -69,26 +75,33 @@     toEnt (k, xs) = (quicIx $ tokenIx $ toToken $ foldedCase k, m)       where         m = case xs of-            [] -> error "staticRevIndex"-            [("", i)] -> StaticEntry i Nothing-            (_, i) : _ -> StaticEntry i $ Just $ M.fromList xs-    zs = map extract $ groupBy ((==) `on` fst) $ sort lst+            ("", i) :| [] -> StaticEntry i Nothing+            (_, i) :| _ -> StaticEntry i $ Just $ M.fromList $ NE.toList xs+    zs = map extract $ NE.groupBy ((==) `on` fst) $ sort lst       where         lst =             zipWith (\(k, v) i -> (k, (v, i))) staticTableList $-                map (SIndex . AbsoluteIndex) [0 ..]-        extract xs = (headFst xs, map snd xs)-        headFst [] = error "headFst"-        headFst (x : _) = fst x+                map AbsoluteIndex [0 ..]+        extract xs = (fst (NE.head xs), NE.map snd xs)  {-# INLINE lookupStaticRevIndex #-} lookupStaticRevIndex :: Int -> FieldValue -> RevResult lookupStaticRevIndex ix v = case staticRevIndex `unsafeAt` ix of-    StaticEntry i Nothing -> K i+    StaticEntry i Nothing -> K $ SIndex i     StaticEntry i (Just m) -> case M.lookup v m of-        Nothing -> K i-        Just j -> KV j+        Nothing -> K $ SIndex i+        Just j -> KV $ SIndex j +lookupRevIndexS+    :: Token+    -> FieldValue+    -> RevResult+lookupRevIndexS Token{..} v+    | ix < 0 = N+    | otherwise = lookupStaticRevIndex ix v+  where+    ix = quicIx tokenIx+ ----------------------------------------------------------------  newDynamicRevIndex :: IO DynamicRevIndex@@ -109,49 +122,85 @@     let ref = drev `unsafeAt` ix     m <- readIORef ref     case M.lookup v m of-        Just i -> return $ KV i+        Just i -> return $ KV $ DIndex i         Nothing -> return $ lookupStaticRevIndex ix v  {-# INLINE insertDynamicRevIndex #-} insertDynamicRevIndex-    :: Token -> FieldValue -> HIndex -> DynamicRevIndex -> IO ()+    :: Token -> FieldValue -> AbsoluteIndex -> DynamicRevIndex -> IO () insertDynamicRevIndex t v i drev = modifyIORef ref $ M.insert v i   where-    ref = drev `unsafeAt` tokenIx t+    ref = drev `unsafeAt` quicIx (tokenIx t)  {-# INLINE deleteDynamicRevIndex #-}-deleteDynamicRevIndex :: Token -> FieldValue -> DynamicRevIndex -> IO ()-deleteDynamicRevIndex t v drev = modifyIORef ref $ M.delete v+deleteDynamicRevIndex+    :: Token -> FieldValue -> AbsoluteIndex -> DynamicRevIndex -> IO ()+deleteDynamicRevIndex t v ai drev = modifyIORef ref $ M.alter adjust v   where-    ref = drev `unsafeAt` tokenIx t+    ref = drev `unsafeAt` quicIx (tokenIx t)+    adjust Nothing = Nothing+    adjust x@(Just ai')+        | ai == ai' = Nothing+        -- This previous entry is already deleted by "duplicate"+        | otherwise = x  ---------------------------------------------------------------- -newOtherRevIndex :: IO OtherRevIdex+newOtherRevIndex :: IO OtherRevIndex newOtherRevIndex = newIORef M.empty -renewOtherRevIndex :: OtherRevIdex -> IO ()+renewOtherRevIndex :: OtherRevIndex -> IO () renewOtherRevIndex ref = writeIORef ref M.empty  {-# INLINE lookupOtherRevIndex #-}-lookupOtherRevIndex :: (FieldName, FieldValue) -> OtherRevIdex -> IO RevResult+lookupOtherRevIndex :: (FieldName, FieldValue) -> OtherRevIndex -> IO RevResult lookupOtherRevIndex (k, v) ref = do     oth <- readIORef ref-    case M.lookup (KeyValue k v) oth of+    case M.lookup k oth of         Nothing -> return N-        Just i -> return $ KV i+        Just psq -> case PSQ.lookup v psq of+            Nothing -> case PSQ.findMin psq of+                Nothing -> return N+                Just (_, _, ai) -> return $ K $ DIndex ai+            Just (_, i) -> return $ KV $ DIndex i +isKeyRegistered :: FieldName -> RevIndex -> IO (Maybe AbsoluteIndex)+isKeyRegistered k (RevIndex _ ref) = do+    oth <- readIORef ref+    return $ case M.lookup k oth of+        Nothing -> Nothing+        Just psq -> case PSQ.findMin psq of+            Nothing -> Nothing+            Just (_, _, ai) -> Just ai+ {-# INLINE insertOtherRevIndex #-}-insertOtherRevIndex :: Token -> FieldValue -> HIndex -> OtherRevIdex -> IO ()-insertOtherRevIndex t v i ref = modifyIORef' ref $ M.insert (KeyValue k v) i+insertOtherRevIndex+    :: Token -> FieldValue -> AbsoluteIndex -> OtherRevIndex -> IO ()+insertOtherRevIndex t v ai@(AbsoluteIndex i) ref = modifyIORef' ref $ M.alter adjust k   where+    adjust Nothing = Just $ PSQ.singleton v i ai+    adjust (Just psq) = Just $ PSQ.insert v i ai psq     k = tokenFoldedKey t  {-# INLINE deleteOtherRevIndex #-}-deleteOtherRevIndex :: Token -> FieldValue -> OtherRevIdex -> IO ()-deleteOtherRevIndex t v ref = modifyIORef' ref $ M.delete (KeyValue k v)+deleteOtherRevIndex+    :: Token -> FieldValue -> AbsoluteIndex -> OtherRevIndex -> IO ()+deleteOtherRevIndex t v ai ref = modifyIORef' ref $ M.alter adjust k   where     k = tokenFoldedKey t+    -- This previous entry is already deleted by "adjustDrainingPoint"+    adjust Nothing = Nothing+    adjust (Just psq)+        | PSQ.null psq' = Nothing+        | otherwise = Just psq'+      where+        psq' = snd $ PSQ.alter adj v psq+        adj x@(Just (_, ai'))+            | ai == ai' = ((), Nothing)+            -- This previous entry is already deleted by "duplicate"+            | otherwise = ((), x)+        -- This previous entry is already deleted by "adjustDrainingPoint"+        adj Nothing = ((), Nothing)  ---------------------------------------------------------------- @@ -178,8 +227,6 @@     ix = quicIx tokenIx     k = tokenFoldedKey t ---    ent = toEntryToken t v -- fixme- {-# INLINE lookupRevIndex' #-} lookupRevIndex'     :: Token@@ -187,28 +234,28 @@     -> RevResult lookupRevIndex' Token{..} v     | ix >= 0 = lookupStaticRevIndex ix v-    | otherwise = N -- fixme+    | otherwise = N   where     ix = quicIx tokenIx ---    k = tokenFoldedKey t -- fixme+tokenToStaticIndex :: Token -> Maybe AbsoluteIndex+tokenToStaticIndex Token{..}+    | ix >= 0 = case staticRevIndex `unsafeAt` ix of+        StaticEntry i _ -> Just i+    | otherwise = Nothing+  where+    ix = quicIx tokenIx  ----------------------------------------------------------------  {-# INLINE insertRevIndex #-}-insertRevIndex :: Entry -> HIndex -> RevIndex -> IO ()+insertRevIndex :: Entry -> AbsoluteIndex -> RevIndex -> IO () insertRevIndex (Entry _ t v) i (RevIndex dyn oth)     | quicIx (tokenIx t) >= 0 = insertDynamicRevIndex t v i dyn     | otherwise = insertOtherRevIndex t v i oth  {-# INLINE deleteRevIndex #-}-deleteRevIndex :: RevIndex -> Entry -> IO ()-deleteRevIndex (RevIndex dyn oth) (Entry _ t v)-    | quicIx (tokenIx t) >= 0 = deleteDynamicRevIndex t v dyn-    | otherwise = deleteOtherRevIndex t v oth--{-# INLINE deleteRevIndexList #-}-deleteRevIndexList :: [Entry] -> RevIndex -> IO ()-deleteRevIndexList es rev = mapM_ (deleteRevIndex rev) es---- isStaticToken+deleteRevIndex :: RevIndex -> Entry -> AbsoluteIndex -> IO ()+deleteRevIndex (RevIndex dyn oth) (Entry _ t v) ai+    | quicIx (tokenIx t) >= 0 = deleteDynamicRevIndex t v ai dyn+    | otherwise = deleteOtherRevIndex t v ai oth
Network/QPACK/Table/Static.hs view
@@ -34,7 +34,7 @@ toStaticEntry :: AbsoluteIndex -> Entry toStaticEntry (AbsoluteIndex sidx)     | sidx < staticTableSize = staticTable `unsafeAt` sidx-    | otherwise = E.throw IllegalStaticIndex+    | otherwise = E.throw $ IllegalStaticIndex sidx  -- | Pre-defined static table. staticTable :: Array Index Entry
Network/QPACK/Types.hs view
@@ -7,11 +7,17 @@  newtype AbsoluteIndex = AbsoluteIndex Int deriving (Eq, Ord, Show, Num) newtype InsRelativeIndex = InsRelativeIndex Int deriving (Eq, Ord, Show, Num)-newtype HBRelativeIndex = HBRelativeIndex Int deriving (Eq, Ord, Show, Num)+newtype PreBaseIndex = PreBaseIndex Int deriving (Eq, Ord, Show, Num) newtype PostBaseIndex = PostBaseIndex Int deriving (Eq, Ord, Show, Num)-newtype InsertionPoint = InsertionPoint Int deriving (Eq, Ord, Show, Num) newtype BasePoint = BasePoint Int deriving (Eq, Ord, Show, Num) +-- Point to insert an entry next+newtype InsertionPoint = InsertionPoint Int deriving (Eq, Ord, Show, Num)++-- Counter of how many entries must be inserted+newtype RequiredInsertCount = RequiredInsertCount Int+    deriving (Eq, Ord, Show, Num)+ data HIndex     = SIndex AbsoluteIndex     | DIndex AbsoluteIndex@@ -27,8 +33,8 @@       | Entries  |             Entries             | Space  |       +----------+---------------------------------+--------+ |  d  |                            |n-4|n-3|n-2|n-1|          Absolute-|n-d-1|                            | 3 | 2 | 1 | 0 |          Relative ins-|n-d-3|                            | 1 | 0 |                  Relative HB+|n-d-1|                            | 3 | 2 | 1 | 0 |          Ins Relative+|n-d-3|                            | 1 | 0 |                  Pre-Base                                            | 0 | 1 |          Post-Base                                            ^                                            |@@ -36,8 +42,8 @@                                                     ip = 100 |  d  |                            | 96| 97| 98| 99|          Absolute-|n-d-1|                            | 3 | 2 | 1 | 0 |          Relative ins-|n-d-3|                            | 1 | 0 |                  Relative HB+|n-d-1|                            | 3 | 2 | 1 | 0 |          Ins Relative+|n-d-3|                            | 1 | 0 |                  Pre-Base                                            | 0 | 1 |          Post-Base                                            bp = 98 -}@@ -71,24 +77,29 @@ fromInsRelativeIndex (InsRelativeIndex ri) (InsertionPoint ip) =     AbsoluteIndex (ip - ri - 1) +toBaseIndex :: AbsoluteIndex -> BasePoint -> Either PreBaseIndex PostBaseIndex+toBaseIndex (AbsoluteIndex idx) (BasePoint bp)+    | idx < bp = Left $ PreBaseIndex (bp - idx - 1)+    | otherwise = Right $ PostBaseIndex (idx - bp)+ -- | ----- >>> toHBRelativeIndex 96 98--- HBRelativeIndex 1--- >>> toHBRelativeIndex 97 98--- HBRelativeIndex 0-toHBRelativeIndex :: AbsoluteIndex -> BasePoint -> HBRelativeIndex-toHBRelativeIndex (AbsoluteIndex idx) (BasePoint bp) =-    HBRelativeIndex (bp - idx - 1)+-- >>> toPreBaseIndex 96 98+-- PreBaseIndex 1+-- >>> toPreBaseIndex 97 98+-- PreBaseIndex 0+toPreBaseIndex :: AbsoluteIndex -> BasePoint -> PreBaseIndex+toPreBaseIndex (AbsoluteIndex idx) (BasePoint bp) =+    PreBaseIndex (bp - idx - 1)  -- | ----- >>> fromHBRelativeIndex 1 98+-- >>> fromPreBaseIndex 1 98 -- AbsoluteIndex 96--- >>> fromHBRelativeIndex 0 98+-- >>> fromPreBaseIndex 0 98 -- AbsoluteIndex 97-fromHBRelativeIndex :: HBRelativeIndex -> BasePoint -> AbsoluteIndex-fromHBRelativeIndex (HBRelativeIndex ri) (BasePoint bp) =+fromPreBaseIndex :: PreBaseIndex -> BasePoint -> AbsoluteIndex+fromPreBaseIndex (PreBaseIndex ri) (BasePoint bp) =     AbsoluteIndex (bp - ri - 1)  -- |@@ -122,6 +133,7 @@     , set0100     , set0101     , set0010+    , set00000     , set00001         :: Setter @@ -139,8 +151,9 @@  set0, set00, set000, set0000 :: Setter -set0    = id-set00   = id-set000  = id-set0000 = id+set0     = id+set00    = id+set000   = id+set0000  = id+set00000 = id {- FOURMOLU_ENABLE -}
http3.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               http3-version:            0.0.24+version:            0.1.0 license:            BSD-3-Clause license-file:       LICENSE maintainer:         Kazu Yamamoto <kazu@iij.ad.jp>@@ -16,7 +16,6 @@     test/serverkey.pem     test/inputFile     qifs/qifs/*.qif-    qifs/encoded/qpack-05/examples/*.1     qifs/encoded/qpack-05/f5/*.0     qifs/encoded/qpack-05/f5/*.1     qifs/encoded/qpack-05/ls-qpack/*.0@@ -88,10 +87,12 @@         iproute >= 1.7 && < 1.8,         network,         network-byte-order,+        network-control >= 0.1.7 && <0.2,+        psqueues,         quic >= 0.2.11 && < 0.3,         sockaddr,         stm,-        time-manager >= 0.2 && < 0.3,+        time-manager >= 0.2.3 && <0.3,         utf8-string >=1.0 && <1.1  executable h3-server@@ -149,9 +150,10 @@     else         buildable: False -executable qif-    main-is:            qif.hs+executable qif-enc+    main-is:            qif-enc.hs     hs-source-dirs:     util+    other-modules:      QIF     default-language:   Haskell2010     default-extensions: Strict StrictData     ghc-options:        -Wall -threaded -rtsopts@@ -159,8 +161,11 @@         base >=4.9 && <5,         attoparsec,         bytestring,+        cereal,         conduit,         conduit-extra,+        containers,+        filepath,         http3,         quic,         stm@@ -170,11 +175,34 @@     else         buildable: False +executable qif-dec+    main-is:            qif-dec.hs+    hs-source-dirs:     util+    other-modules:      QIF+    default-language:   Haskell2010+    default-extensions: Strict StrictData+    ghc-options:        -Wall -threaded -rtsopts+    build-depends:+        base >=4.9 && <5,+        attoparsec,+        bytestring,+        conduit,+        conduit-extra,+        containers,+        http3,+        quic,+        stm++    if flag(devel)++    else+        buildable: False+ test-suite spec     type:               exitcode-stdio-1.0     main-is:            Spec.hs     build-tool-depends: hspec-discover:hspec-discover-    hs-source-dirs:     test+    hs-source-dirs:     test util     other-modules:         HTTP3.Config         HTTP3.Error@@ -183,7 +211,9 @@         HTTP3.ServerSpec         QPACK.InstructionSpec         QPACK.QIFSpec+        QPACK.QIF2Spec         QPACK.TableSpec+        QIF      default-language:   Haskell2010     default-extensions: Strict StrictData@@ -195,8 +225,10 @@         async,         base16-bytestring,         bytestring,+        case-insensitive,         conduit,         conduit-extra,+        containers,         crypton,         hspec >=1.3,         http-semantics,
− qifs/encoded/qpack-05/examples/examples.out.220.100.1

binary file changed (182 → absent bytes)

− qifs/qifs/draft-examples.qif
@@ -1,9 +0,0 @@-# stream 4-:path	/index.html--# stream 8-:authority	www.ietf.org--# stream 12-:authority	www.ietf.org-
test/HTTP3/Config.hs view
@@ -52,4 +52,4 @@     mhqidx = "hq" `L.elemIndex` protos  testH3ClientConfig :: H3.ClientConfig-testH3ClientConfig = H3.ClientConfig "https" "127.0.0.1"+testH3ClientConfig = H3.defaultClientConfig{H3.authority = "127.0.0.1"}
test/HTTP3/ErrorSpec.hs view
@@ -9,6 +9,6 @@  spec :: Spec spec =-    beforeAll setup $+    beforeAll (setup server 4096) $         afterAll teardown $             h3ErrorSpec testClientConfig testH3ClientConfig 2000 -- 2 seconds
test/HTTP3/Server.hs view
@@ -3,6 +3,7 @@  module HTTP3.Server (     setup,+    server,     teardown,     trailersMaker,     firstTrailerValue,@@ -31,14 +32,21 @@  import HTTP3.Config -setup :: IO ThreadId-setup = do+setup :: Server -> Int -> IO ThreadId+setup svr siz = do     sc <- makeTestServerConfig     tid <- forkIO $ QUIC.run sc loop     threadDelay 500000 -- give enough time to the server     return tid   where-    loop conn = E.bracket allocSimpleConfig freeSimpleConfig $ \conf -> run conn conf server+    loop conn = E.bracket allocSimpleConfig freeSimpleConfig $ \conf0 -> do+        let conf =+                conf0+                    { confQEncoderConfig = defaultQEncoderConfig{ecMaxTableCapacity = siz}+                    , confQDecoderConfig = defaultQDecoderConfig{dcMaxTableCapacity = siz}+                    }++        run conn conf svr  teardown :: ThreadId -> IO () teardown tid = killThread tid
test/HTTP3/ServerSpec.hs view
@@ -17,7 +17,7 @@ import HTTP3.Server  spec :: Spec-spec = beforeAll setup $ afterAll teardown h3spec+spec = beforeAll (setup server 4096) $ afterAll teardown h3spec  h3spec :: SpecWith a h3spec = do
+ test/QPACK/QIF2Spec.hs view
@@ -0,0 +1,135 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}++module QPACK.QIF2Spec where++import Control.Concurrent+import qualified Control.Exception as E+import Control.Monad+import qualified Data.ByteString as B+import Data.ByteString.Builder (Builder, byteString)+import qualified Data.ByteString.Char8 as C8+import Data.CaseInsensitive hiding (map)+import Data.List+import Data.Maybe+import Network.HTTP.Semantics+import Network.HTTP.Semantics.Client.Internal+import Network.HTTP.Semantics.Server+import Network.HTTP.Types (RequestHeaders, notFound404, ok200)+import qualified Network.HTTP3.Client as C+import Network.HTTP3.Server+import qualified Network.HTTP3.Server as S+import qualified Network.QUIC.Client as QUIC+import System.IO+import Test.Hspec++import HTTP3.Config+import HTTP3.Server hiding (server)+import QIF++qiffile :: FilePath+qiffile = "qifs/qifs/fb-req-hq.qif"++spec :: Spec+spec = do+    describe "QIF server" $ do+        it "handles dynamic table of 0-bytes" $ do+            let size = 0+            E.bracket (setup server size) (teardown) $+                \_ -> runClient size+    describe "QIF server" $ do+        it "handles dynamic table of 256-bytes" $ do+            let size = 256+            E.bracket (setup server size) (teardown) $+                \_ -> runClient size+    describe "QIF server" $ do+        it "handles dynamic table of 512-bytes" $ do+            let size = 512+            E.bracket (setup server size) (teardown) $+                \_ -> runClient size+    describe "QIF server" $ do+        it "handles dynamic table of 4096-bytes" $ do+            let size = 4096+            E.bracket (setup server size) (teardown) $+                \_ -> runClient size++runClient :: Int -> IO ()+runClient siz = QUIC.run testClientConfig $ \conn ->+    E.bracket allocSimpleConfig freeSimpleConfig $ \conf0 -> do+        let conf =+                conf0+                    { S.confQEncoderConfig = S.defaultQEncoderConfig{S.ecMaxTableCapacity = siz}+                    , S.confQDecoderConfig = S.defaultQDecoderConfig{S.dcMaxTableCapacity = siz}+                    }++        withFile qiffile ReadMode $ \hdl ->+            C.run conn testH3ClientConfig conf $ client hdl++client :: Handle -> (C.Request -> (C.Response -> IO ()) -> IO a) -> p -> IO ()+client hdl sendRequest _aux = do+    -- delay for set table capacity+    threadDelay 10000+    ms <- forkClient 0 id+    mapM_ takeMVar ms+  where+    forkClient n build = do+        hs <- headerlist hdl+        if null hs+            then return $ build []+            else do+                var <- newEmptyMVar+                _ <- forkIO $ do+                    let req = requestBuilder2 hs mempty+                    _ <- sendRequest req $ checkHeader hs+                    putMVar var ()+                when ((n :: Int) `mod` 50 == 0) $ threadDelay 100000+                forkClient (n + 1) (build . (var :))+    checkHeader hs rsp = do+        bs <- getBody+        let hs0 = map keyValue $ split bs+            hs0' = sort $ filter (\(k, _) -> k == "cookie:") hs0+            hs' = sort $ filter (\(k, _) -> k == "cookie:") hs+        hs0' `shouldBe` hs'+      where+        getBody = B.concat <$> loop id+        loop build = do+            bs <- C.getResponseBodyChunk rsp+            if bs == ""+                then return $ build []+                else loop (build . (bs :))+        split ls0 = go ls0 id+          where+            go "" build = build []+            go ls build = go ls'' (build . (l :))+              where+                (l, ls') = C8.break (== '\n') ls+                ls'' = C8.drop 1 ls'+        keyValue kv = (mk k, v)+          where+            (k, v') = C8.break (== ' ') kv+            v = C8.drop 1 v'++server :: Server+server req _aux sendResponse = case requestMethod req of+    Just "GET" -> sendResponse (responseSection req) []+    _ -> sendResponse (responseNoBody notFound404 []) []++responseSection :: S.Request -> S.Response+responseSection req = responseBuilder ok200 header body+  where+    header =+        [ ("Content-Type", "text/plain")+        , ("Server", "HaskellQuic/0.0.0")+        ]+    (thl, vt) = requestHeaders req+    pseudos = [tokenAuthority, tokenMethod, tokenPath, tokenScheme]+    pthl = map (\t -> (t, fromJust (getFieldValue t vt))) pseudos+    body =+        foldr1 (<>) $+            map (\(k, v) -> byteString (k <> " " <> v <> "\n")) $+                map (\(k, v) -> (tokenFoldedKey k, v)) (pthl ++ thl)++-- | Creating request with builder only from headers+--   where :path and :method are included.+requestBuilder2 :: RequestHeaders -> Builder -> C.Request+requestBuilder2 hdr builder = Request $ OutObj hdr (OutBodyBuilder builder) defaultTrailersMaker
test/QPACK/QIFSpec.hs view
@@ -3,118 +3,89 @@  module QPACK.QIFSpec where -import Conduit hiding (yield)-import Control.Concurrent-import Control.Concurrent.STM-import qualified Control.Exception as E import Control.Monad-import Data.Attoparsec.ByteString (Parser)-import qualified Data.Attoparsec.ByteString as P import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Char8 as BS8 import Data.Conduit.Attoparsec+import Data.IORef+import Data.Sequence (Seq, ViewR (..), viewr, (<|))+import qualified Data.Sequence as Seq import Network.QUIC (StreamId) import System.IO import Test.Hspec  import Network.QPACK+import Network.QPACK.Internal +import QIF+ spec :: Spec spec = do     describe "simple decoder" $ do-        it "decodes well" $ do+        it "decodes with dynamic table" $ do             forM_ ["fb-req-hq", "fb-resp-hq", "netbsd-hq"] $ \svc ->-                forM_ ["f5", "ls-qpack", "nghttp3", "proxygen", "qthingey", "quinn"] $ \impl -> do-                    putStrLn $ impl ++ " with " ++ svc-                    let inp = "qifs/encoded/qpack-05/" ++ impl ++ "/" ++ svc ++ ".out.4096.0.0"-                        out = "qifs/qifs/" ++ svc ++ ".qif"-                    test inp out+                forM_ ["f5", "ls-qpack", "nghttp3", "proxygen", "qthingey", "quinn"] $ \impl ->+                    forM_ [".0.0", ".0.1", ".100.0", ".100.1"] $ \suffix ->+                        forM_ [256, 512, 4096 :: Int] $ \size -> do+                            let inp =+                                    "qifs/encoded/qpack-05/" ++ impl ++ "/" ++ svc ++ ".out." ++ show size ++ suffix+                                out = "qifs/qifs/" ++ svc ++ ".qif"+                            putStrLn inp+                            test size inp out -data Block = Block Int ByteString deriving (Show)+        it "decodes only with static table" $ do+            forM_ ["fb-req-hq", "fb-resp-hq", "netbsd-hq"] $ \svc ->+                forM_ ["ls-qpack", "nghttp3", "qthingey", "quinn"] $ \impl ->+                    forM_ [".0.0", ".0.1", ".100.0", ".100.1"] $ \suffix -> do+                        let size = 0+                        let inp =+                                "qifs/encoded/qpack-05/" ++ impl ++ "/" ++ svc ++ ".out." ++ show size ++ suffix+                            out = "qifs/qifs/" ++ svc ++ ".qif"+                        putStrLn inp+                        test size inp out  ---------------------------------------------------------------- -test :: FilePath -> FilePath -> IO ()-test efile qfile = do-    (dec, insthdr) <- newQDecoderS defaultQDecoderConfig (\_ -> return ()) False-    q <- newTQueueIO-    let recv = atomically $ readTQueue q-        send x = atomically $ writeTQueue q x-    mvar <- newEmptyMVar+test :: Int -> FilePath -> FilePath -> IO ()+test size efile qfile = do+    (dec, insthdr) <-+        newQDecoderS+            defaultQDecoderConfig{dcMaxTableCapacity = size}+            (\_ -> return ())+            False+    _ <- encodeEncoderInstructions [SetDynamicTableCapacity size] False >>= insthdr+    ref <- newIORef Seq.empty     withFile qfile ReadMode $ \h -> do-        tid <- forkIO $ decode dec h recv mvar-        runConduitRes-            (sourceFile efile .| conduitParser block .| mapM_C (liftIO . switch send insthdr))-        takeMVar mvar-        killThread tid+        processBlock efile $ testSwitch dec insthdr ref h -switch-    :: (Block -> IO ())+testSwitch+    :: (StreamId -> ByteString -> IO (Maybe [Header]))     -> EncoderInstructionHandlerS+    -> IORef (Seq Block)+    -> Handle     -> (PositionRange, Block)     -> IO ()-switch send insthdr (_, blk@(Block n bs))+testSwitch dec insthdr ref h (_, blk@(Block n bs))     | n == 0 = do-        insthdr bs-        yield-    | otherwise = send blk--decode-    :: (StreamId -> ByteString -> IO [Header])-    -> Handle-    -> IO Block-    -> MVar ()-    -> IO ()-decode dec h recv mvar = loop+        _ <- insthdr bs+        fifo <- readIORef ref+        loop fifo+    -- to avoid blocking by "dec", ask decoding to the other thread+    | otherwise = do+        mhdr <- dec n bs+        case mhdr of+            Nothing -> modifyIORef' ref (blk <|)+            Just hdr -> compareHeaders hdr   where-    loop = do+    loop fifo = do+        case viewr fifo of+            EmptyR -> writeIORef ref Seq.empty+            fifo' :> Block n1 bs1 -> do+                mhdr <- dec n1 bs1+                case mhdr of+                    Nothing -> writeIORef ref fifo+                    Just hdr -> do+                        compareHeaders hdr+                        loop fifo'+    compareHeaders hdr = do         hdr' <- fromCaseSensitive <$> headerlist h-        if null hdr'-            then-                putMVar mvar ()-            else do-                Block n bs <- recv-                hdr <- dec n bs-                hdr `shouldBe` hdr'-                loop--fromCaseSensitive :: [Header] -> [Header]-fromCaseSensitive = map (\(k, v) -> (foldedCase $ mk k, v))--block :: Parser Block-block = do-    num <- toInt <$> P.take 8-    len <- toInt <$> P.take 4-    dat <- P.take len-    return $ Block num dat--toInt :: ByteString -> Int-toInt bs = BS.foldl' f 0 bs-  where-    f n w8 = n * 256 + fromIntegral w8--------------------------------------------------------------------headerlist :: Handle -> IO [Header]-headerlist h = loop id-  where-    loop b = do-        ml <- line h-        case ml of-            Nothing -> return $ b []-            Just l-                | l == "" -> return $ b []-                | otherwise -> do-                    let (k, v0) = BS8.break (== '\t') l-                        v = BS8.drop 1 v0-                    loop (b . ((mk k, v) :))--line :: Handle -> IO (Maybe ByteString)-line h = do-    el <- E.try $ BS8.hGetLine h-    case el of-        Left (_ :: E.IOException) -> return Nothing-        Right l-            | BS8.take 1 l == "#" -> line h-            | otherwise -> return $ Just l+        fromCaseSensitive hdr `shouldBe` hdr'
test/QPACK/TableSpec.hs view
@@ -9,13 +9,13 @@ spec = do     describe "encodeRequiredInsertCount and decodeRequiredInsertCount" $ do         prop "duality" $ \(Triple m ei di) -> do-            let ereq = encodeRequiredInsertCount m (InsertionPoint ei)-                InsertionPoint ei' = decodeRequiredInsertCount m (InsertionPoint di) ereq+            let ereq = encodeRequiredInsertCount m (RequiredInsertCount ei)+                RequiredInsertCount ei' = decodeRequiredInsertCount m (InsertionPoint di) ereq             ei' `shouldBe` ei     describe "encodeBase and decodeBase" $ do         prop "duality" $ \(Doubl base reqInsCnt) -> do-            let (s, delta) = encodeBase (InsertionPoint reqInsCnt) (BasePoint base)-                BasePoint base' = decodeBase (InsertionPoint reqInsCnt) s delta+            let (s, delta) = encodeBase (RequiredInsertCount reqInsCnt) (BasePoint base)+                BasePoint base' = decodeBase (RequiredInsertCount reqInsCnt) s delta             base' `shouldBe` base  data Doubl = Doubl Int Int deriving (Eq, Show)
util/ClientX.hs view
@@ -51,9 +51,8 @@     run conn cliconf conf $ client aux n0 paths   where     cliconf =-        ClientConfig-            { scheme = "https"-            , authority = auxAuthority+        H3.defaultClientConfig+            { authority = auxAuthority             }  client :: Aux -> Int -> [Path] -> Client ()
+ util/QIF.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module QIF (+    processBlock,+    Block (..),+    headerlist,+    fromCaseSensitive,+    headerSize,+) where++import qualified Control.Exception as E+import Data.Attoparsec.ByteString (Parser)+import qualified Data.Attoparsec.ByteString as P+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as BS8+import System.IO++import Network.QPACK++import Conduit+import Data.Conduit.Attoparsec++----------------------------------------------------------------++data Block = Block Int ByteString deriving (Show)++----------------------------------------------------------------++processBlock :: FilePath -> ((PositionRange, Block) -> IO ()) -> IO ()+processBlock efile func =+    runConduitRes+        ( sourceFile efile+            .| conduitParser block+            .| mapM_C (liftIO . func)+        )++----------------------------------------------------------------++block :: Parser Block+block = do+    num <- toInt <$> P.take 8+    len <- toInt <$> P.take 4+    dat <- P.take len+    return $ Block num dat++toInt :: ByteString -> Int+toInt = BS.foldl' f 0+  where+    f n w8 = n * 256 + fromIntegral w8++----------------------------------------------------------------++headerlist :: Handle -> IO [Header]+headerlist h = loop id+  where+    loop b = do+        ml <- line h+        case ml of+            Nothing -> return $ b []+            Just l+                | l == "" -> return $ b []+                | otherwise -> do+                    let (k, v0) = BS8.break (== '\t') l+                        v = BS8.drop 1 v0+                    loop (b . ((mk k, v) :))++line :: Handle -> IO (Maybe ByteString)+line h = do+    el <- E.try $ BS8.hGetLine h+    case el of+        Left (_ :: E.IOException) -> return Nothing+        Right l+            | BS8.take 1 l == "#" -> line h+            | otherwise -> return $ Just l++----------------------------------------------------------------++fromCaseSensitive :: [Header] -> [Header]+fromCaseSensitive = map (\(k, v) -> (foldedCase $ mk k, v))++headerSize :: Header -> Int+headerSize (k, v) = BS.length (foldedCase k) + BS.length v
+ util/qif-dec.hs view
@@ -0,0 +1,182 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.Conduit.Attoparsec+import Data.IORef+import Data.Sequence (Seq, ViewR (..), viewr, (<|))+import qualified Data.Sequence as Seq+import Network.QUIC (StreamId)+import System.Console.GetOpt+import System.Environment+import System.Exit+import System.IO++import Network.QPACK+import Network.QPACK.Internal++import QIF++data Options = Options+    { optDebug :: Bool+    }++defaultOptions :: Options+defaultOptions =+    Options+        { optDebug = False+        }++options :: [OptDescr (Options -> Options)]+options =+    [ Option+        ['d']+        ["debug"]+        (NoArg (\o -> o{optDebug = True}))+        "dump encode instructions while checking"+    ]++usage :: String+usage = "Usage: qif-dec <size> <encode-file> [<qif-file>]"++showUsageAndExit :: String -> IO a+showUsageAndExit msg = do+    putStrLn msg+    putStrLn $ usageInfo usage options+    exitFailure++decoderOpts :: [String] -> IO (Options, [String])+decoderOpts argv =+    case getOpt Permute options argv of+        (o, n, []) -> return (foldl (flip id) defaultOptions o, n)+        (_, _, errs) -> showUsageAndExit $ concat errs++main :: IO ()+main = do+    args <- getArgs+    (Options{..}, spec) <- decoderOpts args+    case spec of+        [size, efile] -> dump (read size) efile+        [size, efile, qfile] -> test (read size) efile qfile optDebug+        _ -> showUsageAndExit "Illegal arguments"++----------------------------------------------------------------++dump :: Int -> FilePath -> IO ()+dump size efile = do+    (dec, insthdr) <-+        newQDecoderS+            defaultQDecoderConfig{dcMaxTableCapacity = size}+            (\_ -> return ())+            True+    _ <- encodeEncoderInstructions [SetDynamicTableCapacity size] False >>= insthdr+    ref <- newIORef Seq.empty+    processBlock efile $ dumpSwitch dec insthdr ref++dumpSwitch+    :: (StreamId -> ByteString -> IO (Maybe [Header]))+    -> EncoderInstructionHandlerS+    -> IORef (Seq Block)+    -> (PositionRange, Block)+    -> IO ()+dumpSwitch dec insthdr ref (_, blk@(Block n bs))+    | n == 0 = do+        putStrLn "---- Encoder Stream"+        _ <- insthdr bs+        fifo <- readIORef ref+        loop fifo+    | otherwise = do+        mhdr <- dec n bs+        case mhdr of+            Nothing -> modifyIORef' ref (blk <|)+            Just _ -> return ()+  where+    loop fifo = do+        case viewr fifo of+            EmptyR -> writeIORef ref Seq.empty+            fifo' :> Block n1 bs1 -> do+                mhdr <- dec n1 bs1+                case mhdr of+                    Nothing -> writeIORef ref fifo+                    Just _ -> loop fifo'++----------------------------------------------------------------++data Ratio = Ratio+    { ratioInst :: Int+    , ratioHeader :: Int+    }++test :: Int -> FilePath -> FilePath -> Bool -> IO ()+test size efile qfile debug = do+    (dec, insthdr) <-+        newQDecoderS+            defaultQDecoderConfig{dcMaxTableCapacity = size}+            (\_ -> return ())+            debug+    _ <- encodeEncoderInstructions [SetDynamicTableCapacity size] False >>= insthdr+    ref <- newIORef Seq.empty+    ratio <- newIORef $ Ratio{ratioInst = 0, ratioHeader = 0}+    withFile qfile ReadMode $ \h -> do+        processBlock efile $ testSwitch dec insthdr ref h ratio+        r <- readIORef ratio+        let (x, y) = ((ratioInst r * 1000) `div` ratioHeader r) `divMod` 10+        putStrLn $ show x ++ "." ++ show y++testSwitch+    :: (StreamId -> ByteString -> IO (Maybe [Header]))+    -> EncoderInstructionHandlerS+    -> IORef (Seq Block)+    -> Handle+    -> IORef Ratio+    -> (PositionRange, Block)+    -> IO ()+testSwitch dec insthdr ref h ratio (_, blk@(Block n bs))+    | n == 0 = do+        _ <- insthdr bs+        modifyIORef' ratio $ \r -> r{ratioInst = ratioInst r + BS.length bs}+        fifo <- readIORef ref+        loop fifo+    -- to avoid blocking by "dec", ask decoding to the other thread+    | otherwise = do+        mhdr <- dec n bs+        case mhdr of+            Nothing -> modifyIORef' ref (blk <|)+            Just hdr -> compareHeaders hdr+  where+    loop fifo = do+        case viewr fifo of+            EmptyR -> writeIORef ref Seq.empty+            fifo' :> Block n1 bs1 -> do+                mhdr <- dec n1 bs1+                case mhdr of+                    Nothing -> writeIORef ref fifo+                    Just hdr -> do+                        compareHeaders hdr+                        loop fifo'+    compareHeaders hdr = do+        hdr' <- fromCaseSensitive <$> headerlist h+        if fromCaseSensitive hdr == hdr'+            then modifyIORef' ratio $ \r ->+                r+                    { ratioInst = ratioInst r + BS.length bs+                    , ratioHeader = ratioHeader r + sum (map headerSize hdr)+                    }+            else do+                putStrLn $ "---- Stream " ++ show n+                let hdrt = zip hdr hdr'+                mapM_ printDiff hdrt+                exitFailure++----------------------------------------------------------------++printDiff :: (Header, Header) -> IO ()+printDiff (kv0, kv1)+    | kv0 == kv1 = print kv1+    | otherwise = do+        putStrLn $ "EXPECT: " ++ show kv1+        putStrLn $ "ACTUAL: " ++ show kv0
+ util/qif-enc.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE RecordWildCards #-}++module Main where++import qualified Data.ByteString as BS+import Data.Serialize.Put+import System.Console.GetOpt+import System.Environment+import System.Exit+import System.FilePath+import System.IO++import Network.QPACK++import QIF++data Options = Options+    { optDebug :: Bool+    }++defaultOptions :: Options+defaultOptions =+    Options+        { optDebug = False+        }++options :: [OptDescr (Options -> Options)]+options =+    [ Option+        ['d']+        ["debug"]+        (NoArg (\o -> o{optDebug = True}))+        "dump encode instructions"+    ]++usage :: String+usage = "Usage: qif-enc <qif-file> <capacity> <blocked> <ack>"++showUsageAndExit :: String -> IO a+showUsageAndExit msg = do+    putStrLn msg+    putStrLn $ usageInfo usage options+    exitFailure++encoderOpts :: [String] -> IO (Options, [String])+encoderOpts argv =+    case getOpt Permute options argv of+        (o, n, []) -> return (foldl (flip id) defaultOptions o, n)+        (_, _, errs) -> showUsageAndExit $ concat errs++main :: IO ()+main = do+    args <- getArgs+    (Options{..}, spec) <- encoderOpts args+    case spec of+        [qfile, cap, blk, ack] -> do+            let efile = takeBaseName qfile ++ ".out." ++ cap ++ "." ++ blk ++ "." ++ ack+                capacity = read cap+                blocked = read blk+                immack = ack == "1"+            encode qfile efile capacity blocked immack optDebug+        _ -> showUsageAndExit "Illegal arguments"++encode :: FilePath -> FilePath -> Int -> Int -> Bool -> Bool -> IO ()+encode qfile efile capacity blocked immack debug = do+    let encConf = defaultQEncoderConfig{ecMaxTableCapacity = capacity}+        save sid bs = do+            let header = runPut $ do+                    putWord64be $ fromIntegral (sid :: Int)+                    putWord32be $ fromIntegral $ BS.length bs+            BS.appendFile efile $ header <> bs+    enc <- newQEncoderS encConf (save 0) blocked immack debug+    withFile qfile ReadMode $ qencode enc save++qencode :: QEncoderS -> (Int -> BS.ByteString -> IO ()) -> Handle -> IO ()+qencode enc save hdl = loop 1+  where+    loop sid = do+        hs <- headerlist hdl+        if null hs+            then return ()+            else do+                bs <- enc sid hs+                save sid bs+                loop (sid + 1)
− util/qif.hs
@@ -1,161 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Main where--import Conduit hiding (yield)-import Control.Concurrent-import Control.Concurrent.STM-import qualified Control.Exception as E-import Control.Monad-import Data.Attoparsec.ByteString (Parser)-import qualified Data.Attoparsec.ByteString as P-import Data.ByteString (ByteString)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Char8 as BS8-import Data.Conduit.Attoparsec-import Network.QUIC (StreamId)-import System.Environment-import System.IO--import Network.QPACK--data Block = Block Int ByteString deriving (Show)--------------------------------------------------------------------main :: IO ()-main = do-    args <- getArgs-    case args of-        [size, efile] -> dump (read size) efile-        [size, efile, qfile] -> test (read size) efile qfile-        _ -> putStrLn "qif size <encode-file> [<qif-file>]"--------------------------------------------------------------------dump :: Int -> FilePath -> IO ()-dump size efile = do-    (dec, insthdr) <--        newQDecoderS-            defaultQDecoderConfig{dcDynamicTableSize = size}-            (\_ -> return ())-            True-    runConduitRes-        ( sourceFile efile-            .| conduitParser block-            .| mapM_C (liftIO . dumpSwitch dec insthdr)-        )--dumpSwitch-    :: (StreamId -> ByteString -> IO [Header])-    -> EncoderInstructionHandlerS-    -> (a, Block)-    -> IO ()-dumpSwitch dec insthdr (_, Block n bs)-    | n == 0 = do-        putStrLn "---- Encoder Stream"-        insthdr bs-    | otherwise = do-        putStrLn $ "---- Stream " ++ show n-        _ <- dec n bs-        return ()--------------------------------------------------------------------test :: Int -> FilePath -> FilePath -> IO ()-test size efile qfile = do-    (dec, insthdr') <--        newQDecoderS-            defaultQDecoderConfig{dcDynamicTableSize = size}-            (\_ -> return ())-            False-    q <- newTQueueIO-    let recv = atomically $ readTQueue q-        send x = atomically $ writeTQueue q x-        insthdr bs = do-            emp <- atomically $ isEmptyTQueue q-            unless emp yield-            insthdr' bs-            yield-    mvar <- newEmptyMVar-    withFile qfile ReadMode $ \h -> do-        tid <- forkIO $ decode dec h recv mvar-        runConduitRes-            ( sourceFile efile-                .| conduitParser block-                .| mapM_C (liftIO . testSwitch send insthdr)-            )-        takeMVar mvar-        killThread tid--testSwitch-    :: (Block -> IO ())-    -> EncoderInstructionHandlerS-    -> (a, Block)-    -> IO ()-testSwitch send insthdr (_, blk@(Block n bs))-    | n == 0 = insthdr bs-    | otherwise = send blk--decode :: QDecoderS -> Handle -> IO Block -> MVar () -> IO ()-decode dec h recv mvar = loop-  where-    loop = do-        hdr' <- fromCaseSensitive <$> headerlist h-        if null hdr'-            then putMVar mvar ()-            else do-                Block n bs <- recv-                hdr <- dec n bs-                if hdr == hdr'-                    then loop-                    else do-                        putStrLn $ "---- Stream " ++ show n-                        mapM_ print hdr-                        putStrLn "----"-                        mapM_ print hdr'-                        putStrLn "----"-                        putMVar mvar ()--fromCaseSensitive :: [Header] -> [Header]-fromCaseSensitive = map (\(k, v) -> (foldedCase $ mk k, v))--------------------------------------------------------------------block :: Parser Block-block = do-    num <- toInt <$> P.take 8-    len <- toInt <$> P.take 4-    dat <- P.take len-    return $ Block num dat--toInt :: ByteString -> Int-toInt = BS.foldl' f 0-  where-    f n w8 = n * 256 + fromIntegral w8--------------------------------------------------------------------headerlist :: Handle -> IO [Header]-headerlist h = loop id-  where-    loop b = do-        ml <- line h-        case ml of-            Nothing -> return $ b []-            Just l-                | l == "" -> return $ b []-                | otherwise -> do-                    let (k, v0) = BS8.break (== '\t') l-                        v = BS8.drop 1 v0-                    loop (b . ((mk k, v) :))--line :: Handle -> IO (Maybe ByteString)-line h = do-    el <- E.try $ BS8.hGetLine h-    case el of-        Left (_ :: E.IOException) -> return Nothing-        Right l-            | BS8.take 1 l == "#" -> line h-            | otherwise -> return $ Just l