module Network.HPACK.HeaderBlock.To (
toHeaderBlock
) where
import Data.List (foldl')
import Network.HPACK.Builder
import Network.HPACK.Context
import Network.HPACK.HeaderBlock.HeaderField
import Network.HPACK.Table
import Network.HPACK.Types
type Ctx = (Context, Builder HeaderField)
type Step = Ctx -> Header -> IO Ctx
toHeaderBlock :: CompressionAlgo
-> Context
-> HeaderSet
-> IO (Context, HeaderBlock)
toHeaderBlock Naive !ctx hs = reset ctx >>= encodeLoop naiveStep hs
toHeaderBlock Static !ctx hs = reset ctx >>= encodeLoop staticStep hs
toHeaderBlock Linear !ctx hs = reset ctx >>= encodeLoop linearStep hs
toHeaderBlock Diff !ctx hs = encodeLoop diffStep hs (ctx, empty)
encodeFinal :: Ctx -> IO (Context, HeaderBlock)
encodeFinal (!ctx, !builder) = do
(is,!ctx') <- emitNotEmittedForEncoding ctx
let builder' = foldl' (\b i -> b << Indexed i) builder is
!hb = run builder'
return (ctx', hb)
encodeLoop :: Step
-> HeaderSet
-> Ctx
-> IO (Context, HeaderBlock)
encodeLoop step (h:hs) !ctx = step ctx h >>= encodeLoop step hs
encodeLoop _ [] !ctx = encodeFinal ctx
reset :: Context -> IO Ctx
reset ctx = do
let ctx' = clearRefSets ctx
initialHeaderBlock = singleton Clear
return (ctx', initialHeaderBlock)
naiveStep :: Step
naiveStep (!ctx,!builder) (k,v) = do
let builder' = builder << Literal NotAdd (Lit k) v
return (ctx, builder')
staticStep :: Step
staticStep (!ctx,!builder) h@(k,v) = do
let cache = lookupHeader h ctx
b = case cache of
None -> Literal NotAdd (Lit k) v
KeyOnly InStaticTable i -> Literal NotAdd (Idx i) v
KeyOnly InHeaderTable _ -> Literal NotAdd (Lit k) v
KeyValue InStaticTable i -> Literal NotAdd (Idx i) v
KeyValue InHeaderTable _ -> Literal NotAdd (Lit k) v
let builder' = builder << b
return (ctx, builder')
linearStep :: Step
linearStep cb@(!ctx,!builder) h = smartStep linear cb h
where
linear i
| i `isPresentIn` ctx = do
let b = builder << Indexed i << Indexed i
return (ctx,b)
| otherwise = do
let b = builder << Indexed i
c = pushRef ctx i
return (c,b)
diffStep :: Step
diffStep cb@(!ctx,!builder) h = smartStep diff cb h
where
diff i = case checkAndUpdate i ctx of
(Z, ctx') -> do
let b = builder << Indexed i
c = pushRef ctx' i
return (c,b)
(E0, ctx') -> return (ctx', builder)
(E2, ctx') -> do
let b = builder << Indexed i << Indexed i
return (ctx',b)
(E4, ctx') -> do
let b = builder << Indexed i << Indexed i << Indexed i << Indexed i
return (ctx',b)
smartStep :: (Index -> IO Ctx) -> Step
smartStep func cb@(!ctx,!builder) h@(k,_) = do
let cache = lookupHeader h ctx
case cache of
None -> check cb h (Lit k)
KeyOnly InStaticTable i -> check cb h (Idx i)
KeyOnly InHeaderTable i -> check cb h (Idx i)
KeyValue InStaticTable i -> do
let e = toEntry h
(is,ctx') <- newEntryForEncoding ctx e
let builder' = double is builder << Indexed i
return (ctx', builder')
KeyValue InHeaderTable i -> func i
double :: [Index] -> Builder HeaderField -> Builder HeaderField
double is bldr = foldl' (\b i -> b << Indexed i << Indexed i) bldr is
check :: Ctx -> Header -> Naming -> IO Ctx
check (ctx,builder) h@(k,v) x
| k `elem` headersNotToIndex = do
let builder' = builder << Literal NotAdd x v
return (ctx, builder')
| otherwise = do
let e = toEntry h
(is,ctx') <- newEntryForEncoding ctx e
let builder' = double is builder << Literal Add x v
return (ctx', builder')
headersNotToIndex :: [HeaderName]
headersNotToIndex = [
":path"
, "content-length"
, "location"
, "etag"
, "set-cookie"
]