packages feed

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

{-# LANGUAGE BinaryLiterals #-}
{-# LANGUAGE RecordWildCards #-}

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

import qualified Data.ByteString as B
import Data.IORef
import Network.ByteOrder
import Network.HPACK (HeaderList, EncodeStrategy(..), TokenHeaderList, CompressionAlgo(..))
import Network.HPACK.Internal
import Network.HPACK.Token
import qualified UnliftIO.Exception as E

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.
encodeHeader :: EncodeStrategy -> DynamicTable -> HeaderList -> IO (EncodedFieldSection,EncodedEncoderInstruction)
encodeHeader stgy dyntbl hs = do
    (hb0, insb) <- withWriteBuffer' 2048 $ \wbuf1 ->
                       withWriteBuffer 2048 $ \wbuf2 -> do
                           hs1 <- encodeTokenHeader wbuf1 wbuf2 stgy 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 k in (t,v)) hs

-- | Converting 'TokenHeaderList' to the QPACK format.
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
    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

encodeStatic :: WriteBuffer -> WriteBuffer
             -> 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

encodeLinear :: WriteBuffer -> WriteBuffer
             -> DynamicTable -> RevIndex -> Bool
             -> IORef TokenHeaderList
             -> TokenHeaderList -> IO ()
encodeLinear 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
            | 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 = InsertWithoutNameReference 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

-- 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
          bp <- getBasePoint dyntbl
          let HBRelativeIndex i = toHBRelativeIndex ai bp
          return (i, set10)
    encodeI wbuf set 6 idx

-- 4.5.3.  Indexed Field Line With Post-Base Index
encodeIndexedFieldLineWithPostBaseIndex :: WriteBuffer
                                          -> DynamicTable
                                          -> AbsoluteIndex -- in Dynamic table
                                          -> IO ()
encodeIndexedFieldLineWithPostBaseIndex wbuf dyntbl ai = do
    bp <- getBasePoint dyntbl
    let HBRelativeIndex idx = toHBRelativeIndex ai bp
    encodeI wbuf set0001 4 idx

-- 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
      SIndex (AbsoluteIndex i) -> return (i, set0101)
      DIndex ai-> do
          updateLargestReference dyntbl ai
          bp <- getBasePoint dyntbl
          let HBRelativeIndex i = toHBRelativeIndex ai bp
          return (i, set0100)
    encodeI wbuf set 4 idx
    encodeS wbuf huff id set1 7 val

-- 4.5.5.  Literal Field Line With Post-Base Name Reference
-- not implemented

-- 4.5.6.  Literal Field Line Without Name Reference
encodeLiteralFieldLineWithoutNameReference :: WriteBuffer -> Token -> ByteString -> Bool -> IO ()
encodeLiteralFieldLineWithoutNameReference wbuf token val huff = do
    let key = tokenFoldedKey token
    encodeS wbuf huff set0010 set00001 3 key
    encodeS wbuf huff id set1 7 val