module Network.HPACK.HeaderBlock.Encode (
encodeHeader
, encodeTokenHeader
) where
#if __GLASGOW_HASKELL__ < 709
import Control.Applicative ((<$>))
#endif
import Control.Exception (bracket, throwIO)
import qualified Control.Exception as E
import Control.Monad (when)
import Data.Bits (setBit)
import qualified Data.ByteString as BS
import Data.ByteString.Internal (ByteString, create, memcpy)
import Data.IORef
import Data.Word (Word8)
import Foreign.Marshal.Alloc
import Foreign.Ptr (minusPtr)
import Network.HPACK.Buffer
import qualified Network.HPACK.HeaderBlock.Integer as I
import qualified Network.HPACK.Huffman as Huffman
import Network.HPACK.Table
import Network.HPACK.Token
import Network.HPACK.Types
changeTableSize :: DynamicTable -> WorkingBuffer -> IO ()
changeTableSize dyntbl wbuf = do
msiz <- needChangeTableSize dyntbl
case msiz of
Keep -> return ()
Change lim -> do
renewDynamicTable lim dyntbl
change wbuf lim
Ignore lim -> do
resetLimitForEncoding dyntbl
change wbuf lim
encodeHeader :: EncodeStrategy
-> Size
-> DynamicTable
-> HeaderList
-> IO ByteString
encodeHeader stgy siz dyntbl hs = encodeHeader' stgy siz dyntbl hs'
where
hs' = map (\(k,v) -> let !t = toToken k in (t,v)) hs
encodeHeader' :: EncodeStrategy
-> Size
-> DynamicTable
-> TokenHeaderList
-> IO ByteString
encodeHeader' stgy siz dyntbl hs = bracket (mallocBytes siz) free enc
where
enc buf = do
(hs',len) <- encodeTokenHeader buf siz stgy True dyntbl hs
case hs' of
[] -> create len $ \p -> memcpy p buf len
_ -> throwIO BufferOverrun
encodeTokenHeader :: Buffer
-> BufferSize
-> EncodeStrategy
-> Bool
-> DynamicTable
-> TokenHeaderList
-> IO (TokenHeaderList, Int)
encodeTokenHeader buf siz EncodeStrategy{..} first dyntbl hs0 = do
wbuf <- newWorkingBuffer buf siz
when first $ changeTableSize dyntbl wbuf
let fa = indexedHeaderField dyntbl wbuf useHuffman
fb = literalHeaderFieldWithIncrementalIndexingIndexedName dyntbl wbuf useHuffman
fc = literalHeaderFieldWithIncrementalIndexingNewName dyntbl wbuf useHuffman
fd = literalHeaderFieldWithoutIndexingIndexedName dyntbl wbuf useHuffman
fe = literalHeaderFieldWithoutIndexingNewName dyntbl wbuf useHuffman
fe' = literalHeaderFieldWithoutIndexingNewName' dyntbl wbuf useHuffman
rev = getRevIndex dyntbl
step0 = case compressionAlgo of
Naive -> naiveStep fe'
Static -> staticStep fa fd fe
Linear -> linearStep rev fa fb fc fd
ref1 <- currentOffset wbuf >>= newIORef
ref2 <- newIORef hs0
loop wbuf ref1 ref2 step0 hs0 `E.catch` \BufferOverrun -> return ()
end <- readIORef ref1
let !len = end `minusPtr` buf
hs <- readIORef ref2
return (hs, len)
where
loop wbuf ref1 ref2 step hsx = go hsx
where
go [] = return ()
go ((t,v):hs) = do
_ <- step t v
currentOffset wbuf >>= writeIORef ref1
writeIORef ref2 hs
go hs
naiveStep :: (HeaderName -> HeaderValue -> IO ()) -> Token -> HeaderValue -> IO ()
naiveStep fe t v = fe (tokenFoldedKey t) v
staticStep :: FA -> FD -> FE -> Token -> HeaderValue -> IO ()
staticStep fa fd fe t v = lookupRevIndex' t v fa fd fe
linearStep :: RevIndex -> FA -> FB -> FC -> FD -> Token -> HeaderValue -> IO ()
linearStep rev fa fb fc fd t v = lookupRevIndex t v fa fb fc fd rev
type FA = HIndex -> IO ()
type FB = HeaderValue -> Entry -> HIndex -> IO ()
type FC = HeaderName -> HeaderValue -> Entry -> IO ()
type FD = HeaderValue -> HIndex -> IO ()
type FE = HeaderName -> HeaderValue -> IO ()
indexedHeaderField
:: DynamicTable -> WorkingBuffer -> Bool -> FA
indexedHeaderField dyntbl wbuf _ hidx =
fromHIndexToIndex dyntbl hidx >>= index wbuf
literalHeaderFieldWithIncrementalIndexingIndexedName
:: DynamicTable -> WorkingBuffer -> Bool -> FB
literalHeaderFieldWithIncrementalIndexingIndexedName dyntbl wbuf huff v ent hidx = do
fromHIndexToIndex dyntbl hidx >>= indexedName wbuf huff 6 set01 v
insertEntry ent dyntbl
literalHeaderFieldWithIncrementalIndexingNewName
:: DynamicTable -> WorkingBuffer -> Bool -> FC
literalHeaderFieldWithIncrementalIndexingNewName dyntbl wbuf huff k v ent = do
newName wbuf huff set01 k v
insertEntry ent dyntbl
literalHeaderFieldWithoutIndexingIndexedName
:: DynamicTable -> WorkingBuffer -> Bool -> FD
literalHeaderFieldWithoutIndexingIndexedName dyntbl wbuf huff v hidx =
fromHIndexToIndex dyntbl hidx >>= indexedName wbuf huff 4 set0000 v
literalHeaderFieldWithoutIndexingNewName
:: DynamicTable -> WorkingBuffer -> Bool -> FE
literalHeaderFieldWithoutIndexingNewName _ wbuf huff k v =
newName wbuf huff set0000 k v
literalHeaderFieldWithoutIndexingNewName'
:: DynamicTable -> WorkingBuffer -> Bool -> HeaderName -> HeaderValue -> IO ()
literalHeaderFieldWithoutIndexingNewName' _ wbuf huff k v =
newName wbuf huff set0000 k v
change :: WorkingBuffer -> Int -> IO ()
change wbuf i = I.encode wbuf set001 5 i
index :: WorkingBuffer -> Int -> IO ()
index wbuf i = I.encode wbuf set1 7 i
indexedName :: WorkingBuffer -> Bool -> Int -> Setter -> HeaderValue -> Index -> IO ()
indexedName wbuf huff n set v idx = do
I.encode wbuf set n idx
encodeString huff v wbuf
newName :: WorkingBuffer -> Bool -> Setter -> HeaderName -> HeaderValue -> IO ()
newName wbuf huff set k v = do
writeWord8 wbuf $ set 0
encodeString huff k wbuf
encodeString huff v wbuf
type Setter = Word8 -> Word8
set1, set01, set001, set0000, setH :: Setter
set1 x = x `setBit` 7
set01 x = x `setBit` 6
set001 x = x `setBit` 5
set0000 = id
setH = set1
encodeString :: Bool -> ByteString -> WorkingBuffer -> IO ()
encodeString False bs wbuf = do
let !len = BS.length bs
I.encode wbuf id 7 len
copyByteString wbuf bs
encodeString True bs wbuf = do
let !origLen = BS.length bs
!expectedLen = (origLen `div` 10) * 8
!expectedIntLen = integerLength expectedLen
wind wbuf expectedIntLen
len <- Huffman.encode wbuf bs
let !intLen = integerLength len
if intLen == expectedIntLen then do
wind wbuf (negate (expectedIntLen + len))
I.encode wbuf setH 7 len
wind wbuf len
else do
let !gap = intLen expectedIntLen
shiftLastN wbuf gap len
wind wbuf (negate (intLen + len))
I.encode wbuf setH 7 len
wind wbuf len
integerLength :: Int -> Int
integerLength n
| n <= 126 = 1
| n <= 254 = 2
| otherwise = 3