packages feed

http3-0.1.1: Network/QPACK/HeaderBlock/Encode.hs

{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE OverloadedStrings #-}

module Network.QPACK.HeaderBlock.Encode (
    encodeHeader,
    encodeTokenHeader,
    EncodedFieldSection,
    EncodedEncoderInstruction,
) where

import qualified Control.Exception as E
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C8
import Network.ByteOrder
import Network.Control
import Network.HPACK.Internal (
    encodeI,
    encodeS,
    toEntryToken,
 )
import Network.HTTP.Semantics
import Network.HTTP.Types

import Imports
import Network.QPACK.HeaderBlock.Prefix
import Network.QPACK.Instruction
import Network.QPACK.Table
import Network.QPACK.Types

-- | Encoded field section including prefix.
type EncodedFieldSection = B.ByteString

-- | Encoded encoder instruction.
type EncodedEncoderInstruction = B.ByteString

-- | Encoding headers with QPACK.
--   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
    :: DynamicTable
    -> [Header]
    -> IO (EncodedFieldSection, EncodedEncoderInstruction)
encodeHeader dyntbl hs = do
    setBasePointToInsersionPoint dyntbl
    clearRequiredInsertCount dyntbl
    (hb0, insb) <- withWriteBuffer' 2048 $ \wbuf1 ->
        withWriteBuffer 2048 $ \wbuf2 -> do
            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
    return (hb, insb)
  where
    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
    -> DynamicTable
    -> TokenHeaderList
    -> IO [AbsoluteIndex]
encodeTokenHeader wbuf1 wbuf2 dyntbl ts0 = do
    clearWriteBuffer wbuf1
    clearWriteBuffer wbuf2
    let revidx = getRevIndex dyntbl
    ready <- isTableReady dyntbl
    if ready
        then encodeLinear wbuf1 wbuf2 dyntbl revidx True ts0
        else encodeStatic wbuf1 wbuf2 dyntbl revidx True ts0

encodeStatic
    :: WriteBuffer
    -> WriteBuffer
    -> DynamicTable
    -> RevIndex
    -> Bool
    -> TokenHeaderList
    -> 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
    -> TokenHeaderList
    -> 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
    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
            qpackDebug dyntbl $ checkAbsoluteIndex dyntbl ai "encodeIndexedFieldLine"
            updateRequiredInsertCount dyntbl ai
            bp <- getBasePoint dyntbl
            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
    -> DynamicTable
    -> AbsoluteIndex -- in Dynamic table
    -> IO ()
encodeIndexedFieldLineWithPostBaseIndex wbuf dyntbl ai = do
    updateRequiredInsertCount dyntbl ai
    bp <- getBasePoint dyntbl
    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 hidx val huff = do
    (idx, set) <- case hidx of
        SIndex (AbsoluteIndex i) -> return (i, set0101)
        DIndex ai -> do
            updateRequiredInsertCount dyntbl ai
            bp <- getBasePoint dyntbl
            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
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 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 ++ "\""