http2 0.4.0 → 0.5.0
raw patch · 23 files changed
+233/−459 lines, 23 filesdep ~basedep ~containers
Dependency ranges changed: base, containers
Files
- Network/HPACK.hs +9/−9
- Network/HPACK/Context.hs +12/−96
- Network/HPACK/Context/HeaderList.hs +19/−0
- Network/HPACK/Context/HeaderSet.hs +0/−19
- Network/HPACK/Context/ReferenceSet.hs +0/−96
- Network/HPACK/HeaderBlock.hs +2/−1
- Network/HPACK/HeaderBlock/Decode.hs +20/−6
- Network/HPACK/HeaderBlock/Encode.hs +1/−5
- Network/HPACK/HeaderBlock/From.hs +8/−18
- Network/HPACK/HeaderBlock/HeaderField.hs +1/−2
- Network/HPACK/HeaderBlock/To.hs +20/−69
- Network/HPACK/Table.hs +38/−40
- Network/HPACK/Table/Entry.hs +2/−1
- Network/HPACK/Table/Static.hs +5/−2
- Network/HPACK/Types.hs +0/−1
- http2.cabal +3/−4
- test-hpack/HPACKDecode.hs +8/−5
- test-hpack/Types.hs +4/−4
- test-hpack/hpack-encode.hs +2/−3
- test-hpack/hpack-stat.hs +6/−5
- test/DecodeSpec.hs +8/−12
- test/HeaderBlock.hs +64/−61
- test/HeaderBlockSpec.hs +1/−0
Network/HPACK.hs view
@@ -1,4 +1,4 @@--- | HPACK: encoding and decoding a header set.+-- | HPACK: encoding and decoding a header list. module Network.HPACK ( -- * Encoding and decoding HPACKEncoding@@ -16,7 +16,7 @@ -- * Errors for decoding , DecodeError(..) -- * Headers- , HeaderSet+ , HeaderList , Header , HeaderName , HeaderValue@@ -29,29 +29,29 @@ import Control.Applicative ((<$>)) import Control.Arrow (second) import Control.Exception (throwIO)-import Network.HPACK.Context (Context, newContextForEncoding, newContextForDecoding, HeaderSet)+import Network.HPACK.Context (Context, newContextForEncoding, newContextForDecoding, HeaderList) import Network.HPACK.HeaderBlock (toHeaderBlock, fromHeaderBlock, toByteStream, fromByteStream) import Network.HPACK.Table (Size) import Network.HPACK.Types ---------------------------------------------------------------- --- | HPACK encoding, from 'HeaderSet' to 'ByteStream'.-type HPACKEncoding = Context -> HeaderSet -> IO (Context, ByteStream)+-- | HPACK encoding, from 'HeaderList' to 'ByteStream'.+type HPACKEncoding = Context -> HeaderList -> IO (Context, ByteStream) --- | HPACK decoding, from 'ByteStream' to 'HeaderSet'.-type HPACKDecoding = Context -> ByteStream -> IO (Context, HeaderSet)+-- | HPACK decoding, from 'ByteStream' to 'HeaderList'.+type HPACKDecoding = Context -> ByteStream -> IO (Context, HeaderList) ---------------------------------------------------------------- --- | Converting 'HeaderSet' for HTTP request to the low level format.+-- | Converting 'HeaderList' for HTTP request to the low level format. encodeHeader :: EncodeStrategy -> HPACKEncoding encodeHeader stgy ctx hs = second toBS <$> toHeaderBlock algo ctx hs where algo = compressionAlgo stgy toBS = toByteStream (useHuffman stgy) --- | Converting the low level format for HTTP request to 'HeaderSet'.+-- | Converting the low level format for HTTP request to 'HeaderList'. -- 'DecodeError' would be thrown. decodeHeader :: HPACKDecoding decodeHeader ctx bs = either throwIO (fromHeaderBlock ctx) ehb
Network/HPACK/Context.hs view
@@ -1,25 +1,16 @@ module Network.HPACK.Context ( -- * Types- HeaderSet -- re-exporting+ HeaderList -- re-exporting , Context , newContextForEncoding , newContextForDecoding , changeContextForDecoding , DecodeError(..) , printContext- -- * Initialization and final results- , emitNotEmittedForEncoding- , emitNotEmittedForDecoding -- * Processing- , clearRefSets- , removeRef , newEntryForEncoding , newEntryForDecoding- , pushRef -- * Auxiliary functions- , isPresentIn- , Sequence(..)- , checkAndUpdate , getEntry -- * Table , whichTable@@ -27,8 +18,7 @@ ) where import Control.Applicative ((<$>))-import Network.HPACK.Context.HeaderSet-import Network.HPACK.Context.ReferenceSet+import Network.HPACK.Context.HeaderList import Network.HPACK.Table import Network.HPACK.Types @@ -38,118 +28,44 @@ -- This is destructive! data Context = Context { headerTable :: !HeaderTable -- ^ A cache of headers- , referenceSet :: ReferenceSet -- ^ Reference set } -- | Printing 'Context' printContext :: Context -> IO ()-printContext (Context hdrtbl refs) = do+printContext (Context hdrtbl) = do putStrLn "<<<Header table>>>" printHeaderTable hdrtbl putStr "\n"- putStrLn "<<<Reference set>>>"- print refs ---------------------------------------------------------------- -- | Creating a new 'Context'. -- The first argument is the size of a header table. newContextForEncoding :: Size -> IO Context-newContextForEncoding maxsiz = do- hdrtbl <- newHeaderTableForEncoding maxsiz- return $ Context hdrtbl emptyReferenceSet+newContextForEncoding maxsiz = Context <$> newHeaderTableForEncoding maxsiz -- | Creating a new 'Context'. -- The first argument is the size of a header table. newContextForDecoding :: Size -> IO Context-newContextForDecoding maxsiz = do- hdrtbl <- newHeaderTableForDecoding maxsiz- return $ Context hdrtbl emptyReferenceSet+newContextForDecoding maxsiz = Context <$> newHeaderTableForDecoding maxsiz +-- | Changing the size of Header Table in 'Context'. changeContextForDecoding :: Context -> Size -> IO Context-changeContextForDecoding ctx@(Context hdrtbl refs) siz- | shouldRenew hdrtbl siz = do- (hdrtbl',n) <- renewHeaderTable siz hdrtbl- let refs' = restrictIndices n refs- return $ Context hdrtbl' refs'- | otherwise = return ctx+changeContextForDecoding ctx@(Context hdrtbl) siz+ | shouldRenew hdrtbl siz = Context <$> renewHeaderTable siz hdrtbl+ | otherwise = return ctx ---------------------------------------------------------------- --- | The reference set is emptied.-clearRefSets :: Context -> Context-clearRefSets ctx = ctx {- referenceSet = emptyReferenceSet- }---- | The entry is removed from the reference set.-removeRef :: Context -> Index -> Context-removeRef (Context hdrtbl refs) idx = ctx- where- refs' = removeIndex idx refs- ctx = Context hdrtbl refs'- -- | The header field is inserted at the beginning of the header table. -- A reference to the new entry is added to the reference set.-newEntryForEncoding :: Context -> Entry -> IO ([Index],Context)-newEntryForEncoding (Context hdrtbl refs) e = do- (hdrtbl', i, is) <- insertEntry e hdrtbl- let ns = getCommon is refs- refs' = addIndex 1 $ restrictIndices i $ adjustReferenceSet refs- ctx = Context hdrtbl' refs'- return (ns, ctx)+newEntryForEncoding :: Context -> Entry -> IO Context+newEntryForEncoding (Context hdrtbl) e = Context <$> insertEntry e hdrtbl -- | The header field is inserted at the beginning of the header table. -- A reference to the new entry is added to the reference set. newEntryForDecoding :: Context -> Entry -> IO Context-newEntryForDecoding (Context hdrtbl refs) e = do- (hdrtbl', i, _) <- insertEntry e hdrtbl- let refs' = addIndex 1 $ restrictIndices i $ adjustReferenceSet refs- return $ Context hdrtbl' refs'---- | The referenced header table entry is added to the reference set.-pushRef :: Context -> Index -> Context-pushRef (Context hdrtbl refs) idx = ctx- where- -- isPresentIn ensures that idx does not exist in- -- newref and oldref.- refs' = addIndex idx refs- ctx = Context hdrtbl refs'---------------------------------------------------------------------- | Emitting non-emitted headers.-emitNotEmittedForEncoding :: Context -> IO ([Index],Context)-emitNotEmittedForEncoding (Context hdrtbl refs) = do- let (removedIndces,refs') = renewForEncoding refs- ctx' = Context hdrtbl refs'- return (removedIndces, ctx')---- | Emitting non-emitted headers.-emitNotEmittedForDecoding :: Context -> IO (HeaderSet,Context)-emitNotEmittedForDecoding ctx@(Context hdrtbl refs) = do- hs <- getNotEmitted ctx- let refs' = renewForDecoding refs- ctx' = Context hdrtbl refs'- return (hs,ctx')--getNotEmitted :: Context -> IO HeaderSet-getNotEmitted ctx = do- let is = getNotEmittedIndices $ referenceSet ctx- hdrtbl = headerTable ctx- map (fromEntry . snd) <$> mapM (which hdrtbl) is---------------------------------------------------------------------- | Is 'Index' present in the reference set?-isPresentIn :: Index -> Context -> Bool-isPresentIn idx ctx = idx `isMember` referenceSet ctx--checkAndUpdate :: Index -> Context -> (Sequence, Context)-checkAndUpdate idx ctx = (s, ctx')- where- (s,refs') = lookupAndUpdate idx $ referenceSet ctx- ctx' = ctx { referenceSet = refs' }+newEntryForDecoding (Context hdrtbl) e = Context <$> insertEntry e hdrtbl ----------------------------------------------------------------
+ Network/HPACK/Context/HeaderList.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.HPACK.Context.HeaderList where++import qualified Data.ByteString.Char8 as BS+import Network.HPACK.Table++-- | Header list.+type HeaderList = [Header]++-- | Printing 'HeaderList'.+printHeaderList :: HeaderList -> IO ()+printHeaderList hs = mapM_ printHeader hs+ where+ printHeader (k,v) = do+ BS.putStr k+ putStr ": "+ BS.putStr v+ putStr "\n"
− Network/HPACK/Context/HeaderSet.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Network.HPACK.Context.HeaderSet where--import qualified Data.ByteString.Char8 as BS-import Network.HPACK.Table---- | Header set.-type HeaderSet = [Header]---- | Printing 'HeaderSet'.-printHeaderSet :: HeaderSet -> IO ()-printHeaderSet hs = mapM_ printHeader hs- where- printHeader (k,v) = do- BS.putStr k- putStr ": "- BS.putStr v- putStr "\n"
− Network/HPACK/Context/ReferenceSet.hs
@@ -1,96 +0,0 @@-module Network.HPACK.Context.ReferenceSet (- -- * Type- ReferenceSet- , emptyReferenceSet- -- * Index and reference set- , isMember- , addIndex- , removeIndex- , restrictIndices- -- * Managing reference set- , getNotEmittedIndices- , adjustReferenceSet- , renewForEncoding- , renewForDecoding- , Sequence(..)- , lookupAndUpdate- , getCommon- ) where--import Data.Map.Strict (Map)-import qualified Data.Map.Strict as M-import Network.HPACK.Table--------------------------------------------------------------------data Status = Old -- E: Initial state for encoding- | NotEmitted -- E&D: Initial state for decoding- | Emitted -- E&D:- deriving (Eq,Show)---- | Type for the reference set.-newtype ReferenceSet = ReferenceSet (Map Index Status) deriving Show--- Must not use IntMap because its mapKeysMonotonic is slow---- | Empty reference set.-emptyReferenceSet :: ReferenceSet-emptyReferenceSet = ReferenceSet M.empty---------------------------------------------------------------------- | Is 'Index' a member of 'ReferenceSet'?-isMember :: Index -> ReferenceSet -> Bool-isMember idx (ReferenceSet m) = idx `M.member` m---- | Adding 'Index' to 'ReferenceSet'.-addIndex :: Index -> ReferenceSet -> ReferenceSet-addIndex idx (ReferenceSet m) = ReferenceSet $ M.insert idx Emitted m---- | Removing 'Index' from 'ReferenceSet'.-removeIndex :: Index -> ReferenceSet -> ReferenceSet-removeIndex idx (ReferenceSet m) = ReferenceSet $ M.delete idx m--restrictIndices :: Index -> ReferenceSet -> ReferenceSet-restrictIndices idx (ReferenceSet m) =- ReferenceSet $ M.filterWithKey (\k _ -> k <= idx) m---------------------------------------------------------------------- | Get all not emitted 'Index' from 'ReferenceSet'.-getNotEmittedIndices :: ReferenceSet -> [Index]-getNotEmittedIndices (ReferenceSet m) = M.keys $ M.filter (== NotEmitted) m---- | Renewing 'ReferenceSet' for the next encoding step.-renewForEncoding :: ReferenceSet -> ([Index],ReferenceSet)-renewForEncoding (ReferenceSet m) = (removedIndces, ReferenceSet m')- where- (oldm,newm) = M.partition (== Old) m- removedIndces = M.keys oldm- m' = M.map (const Old) newm---- | Renewing 'ReferenceSet' for the next decoding step.-renewForDecoding :: ReferenceSet -> ReferenceSet-renewForDecoding (ReferenceSet m) = ReferenceSet m'- where- m' = M.map (const NotEmitted) m---- | Incrementing all 'Index' by one.-adjustReferenceSet :: ReferenceSet -> ReferenceSet-adjustReferenceSet (ReferenceSet m) = ReferenceSet $ M.mapKeysMonotonic (+1) m--data Sequence = Z | E0 | E2 | E4--lookupAndUpdate :: Index -> ReferenceSet -> (Sequence,ReferenceSet)-lookupAndUpdate idx rs@(ReferenceSet m) = case M.lookup idx m of- Nothing -> (Z, rs)- Just Old -> (E0, ReferenceSet $ M.adjust (const NotEmitted) idx m)- Just NotEmitted -> (E4, ReferenceSet $ M.adjust (const Emitted) idx m)- Just Emitted -> (E2, rs)--getCommon :: [Index] -> ReferenceSet -> [Index]-getCommon is (ReferenceSet m) = go is []- where- go [] ret = ret- go (n:ns) ret = case M.lookup n m of- Just NotEmitted -> go ns (n:ret)- _ -> go ns ret
Network/HPACK/HeaderBlock.hs view
@@ -4,7 +4,8 @@ -- * Header block from/to Low level , toByteStream , fromByteStream- -- * Header block from/to header set+ , fromByteStreamDebug+ -- * Header block from/to header list , toHeaderBlock , fromHeaderBlock ) where
Network/HPACK/HeaderBlock/Decode.hs view
@@ -1,5 +1,6 @@ module Network.HPACK.HeaderBlock.Decode ( fromByteStream+ , fromByteStreamDebug ) where import Data.Bits (testBit, clearBit, (.&.))@@ -24,13 +25,26 @@ (hf, bs') <- toHeaderField bs go bs' (builder << hf) +-- | Converting the low level format to 'HeaderBlock'.+-- 'HeaderBlock' forms a pair with corresponding 'ByteString'.+fromByteStreamDebug :: ByteStream -> Either DecodeError [(ByteString,HeaderField)]+fromByteStreamDebug inp = go inp empty+ where+ go bs builder+ | BS.null bs = Right $ run builder+ | otherwise = do+ (hf, bs') <- toHeaderField bs+ let len = BS.length bs - BS.length bs'+ consumed = BS.take len bs+ go bs' (builder << (consumed,hf))+ toHeaderField :: ByteString -> Either DecodeError (HeaderField, ByteString) toHeaderField bs | BS.null bs = Left EmptyBlock | w `testBit` 7 = indexed w bs' | w `testBit` 6 = incrementalIndexing w bs'- | w `testBit` 5 = if w `testBit` 4 then clear w bs' else maxSize w bs'+ | w `testBit` 5 = maxSize w bs' | w `testBit` 4 = neverIndexing w bs' | otherwise = withoutIndexing w bs' where@@ -51,14 +65,11 @@ | isIndexedName1 w = indexedName Add w ws 6 mask6 | otherwise = newName Add ws -clear :: Word8 -> ByteString -> Either DecodeError (HeaderField, ByteString)-clear _ ws = Right (Clear, ws)- maxSize :: Word8 -> ByteString -> Either DecodeError (HeaderField, ByteString) maxSize w ws = Right (ChangeTableSize siz, ws') where- w' = mask4 w- (siz, ws') = I.parseInteger 4 w' ws+ w' = mask5 w+ (siz, ws') = I.parseInteger 5 w' ws withoutIndexing :: Word8 -> ByteString -> Either DecodeError (HeaderField, ByteString)@@ -110,6 +121,9 @@ mask6 :: Word8 -> Word8 mask6 w = w .&. 63++mask5 :: Word8 -> Word8+mask5 w = w .&. 31 mask4 :: Word8 -> Word8 mask4 w = w .&. 15
Network/HPACK/HeaderBlock/Encode.hs view
@@ -23,7 +23,6 @@ toBB = fromHeaderField huff fromHeaderField :: Bool -> HeaderField -> Builder-fromHeaderField _ Clear = clear fromHeaderField _ (ChangeTableSize siz) = change siz fromHeaderField _ (Indexed idx) = index idx fromHeaderField huff (Literal Add (Idx idx) v) = indexedName huff 6 set01 idx v@@ -35,13 +34,10 @@ ---------------------------------------------------------------- -clear :: Builder-clear = BB.fromWord8s [48]- change :: Int -> Builder change i = BB.fromWord8s (w':ws) where- (w:ws) = I.encode 4 i+ (w:ws) = I.encode 5 i w' = set001 w index :: Int -> Builder
Network/HPACK/HeaderBlock/From.hs view
@@ -16,15 +16,15 @@ type Ctx = (Context, Builder Header) type Step = Ctx -> HeaderField -> IO Ctx --- | Decoding 'HeaderBlock' to 'HeaderSet'.+-- | Decoding 'HeaderBlock' to 'HeaderList'. fromHeaderBlock :: Context -> HeaderBlock- -> IO (Context, HeaderSet)+ -> IO (Context, HeaderList) fromHeaderBlock !ctx rs = decodeLoop rs (ctx,empty) ---------------------------------------------------------------- -decodeLoop :: HeaderBlock -> Ctx -> IO (Context, HeaderSet)+decodeLoop :: HeaderBlock -> Ctx -> IO (Context, HeaderList) decodeLoop (r:rs) !ctx = decodeStep ctx r >>= decodeLoop rs decodeLoop [] !ctx = decodeFinal ctx @@ -33,30 +33,23 @@ -- | Decoding step for one 'HeaderField'. Exporting for the -- test purpose. decodeStep :: Step-decodeStep (!ctx,!builder) Clear = return (clearRefSets ctx,builder) decodeStep (!ctx,!builder) (ChangeTableSize siz) = do ctx' <- changeContextForDecoding ctx siz return (ctx',builder)-decodeStep (!ctx,!builder) (Indexed idx)- | isPresent = return (removeRef ctx idx, builder)- | otherwise = do+decodeStep (!ctx,!builder) (Indexed idx) = do w <- whichTable idx ctx case w of (InStaticTable, e) -> do- c <- newEntryForDecoding ctx e let b = builder << fromEntry e- return (c,b)+ return (ctx,b) (InHeaderTable, e) -> do- let c = pushRef ctx idx+ let c = ctx b = builder << fromEntry e return (c,b)- where- isPresent = idx `isPresentIn` ctx decodeStep (!ctx,!builder) (Literal NotAdd naming v) = do k <- fromNaming naming ctx let b = builder << (k,v) return (ctx, b)--- fixme: how to treat Never? decodeStep (!ctx,!builder) (Literal Never naming v) = do k <- fromNaming naming ctx let b = builder << (k,v)@@ -69,11 +62,8 @@ c <- newEntryForDecoding ctx e return (c,b) -decodeFinal :: Ctx -> IO (Context, HeaderSet)-decodeFinal (!ctx, !builder) = do- (hs,!ctx') <- emitNotEmittedForDecoding ctx- let hs' = run builder ++ hs- return (ctx', hs')+decodeFinal :: Ctx -> IO (Context, HeaderList)+decodeFinal (!ctx, !builder) = return (ctx, run builder) ----------------------------------------------------------------
Network/HPACK/HeaderBlock/HeaderField.hs view
@@ -22,8 +22,7 @@ emptyHeaderBlock = [] -- | Type for representation.-data HeaderField = Clear- | ChangeTableSize Int+data HeaderField = ChangeTableSize Int | Indexed Index | Literal Indexing Naming HeaderValue deriving (Eq,Show)
Network/HPACK/HeaderBlock/To.hs view
@@ -4,7 +4,6 @@ toHeaderBlock ) where -import Data.List (foldl') import Network.HPACK.Builder import Network.HPACK.Context import Network.HPACK.HeaderBlock.HeaderField@@ -14,27 +13,22 @@ type Ctx = (Context, Builder HeaderField) type Step = Ctx -> Header -> IO Ctx --- | Encoding 'HeaderSet' to 'HeaderBlock'.+-- | Encoding 'HeaderList' to 'HeaderBlock'. toHeaderBlock :: CompressionAlgo -> Context- -> HeaderSet+ -> HeaderList -> 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)+toHeaderBlock Naive !ctx hs = encodeLoop naiveStep hs (ctx,empty)+toHeaderBlock Static !ctx hs = encodeLoop staticStep hs (ctx,empty)+toHeaderBlock Linear !ctx hs = encodeLoop linearStep 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)+encodeFinal (!ctx, !builder) = return (ctx, run builder) encodeLoop :: Step- -> HeaderSet+ -> HeaderList -> Ctx -> IO (Context, HeaderBlock) encodeLoop step (h:hs) !ctx = step ctx h >>= encodeLoop step hs@@ -42,15 +36,6 @@ ---------------------------------------------------------------- --- for naiveStep and linearStep-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@@ -59,16 +44,15 @@ ---------------------------------------------------------------- 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')+staticStep (!ctx,!builder) h@(k,v) = return (ctx, builder')+ where+ b = case lookupHeader h ctx 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+ builder' = builder << b ---------------------------------------------------------------- -- A simple encoding strategy to reset the reference set first@@ -77,33 +61,7 @@ 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)--------------------------------------------------------------------- See http://lists.w3.org/Archives/Public/ietf-http-wg/2013JulSep/1135.html--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)+ linear i = return (ctx,builder << Indexed i) ---------------------------------------------------------------- @@ -114,16 +72,9 @@ 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 InStaticTable i -> return (ctx, builder << Indexed i) 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@@ -131,8 +82,8 @@ return (ctx, builder') | otherwise = do let e = toEntry h- (is,ctx') <- newEntryForEncoding ctx e- let builder' = double is builder << Literal Add x v+ ctx' <- newEntryForEncoding ctx e+ let builder' = builder << Literal Add x v return (ctx', builder') headersNotToIndex :: [HeaderName]
Network/HPACK/Table.hs view
@@ -5,12 +5,12 @@ HeaderTable , newHeaderTableForEncoding , newHeaderTableForDecoding+ , shouldRenew , renewHeaderTable , printHeaderTable- , shouldRenew -- * Insertion , insertEntry- -- * Lookup+ -- * Header to index , HeaderCache(..) , lookupTable -- * Entry@@ -66,7 +66,9 @@ , headerTableSize :: !Size -- | The max header table size (defined in HPACK) , maxHeaderTableSize :: !Size- -- | Header-to-index+ -- | Header to the index in Header Table for encoder.+ -- Static Table is not included.+ -- Nothing for decoder. , reverseIndex :: Maybe (HP.HashPSQ HIndex) } @@ -81,7 +83,7 @@ es <- mapM (readArray circularTable . adj maxNumOfEntries) [beg .. end] let ts = zip [1..] es mapM_ printEntry ts- putStrLn $ " Table size: " ++ show headerTableSize+ putStrLn $ " Table size: " ++ show headerTableSize ++ "/" ++ show maxHeaderTableSize print reverseIndex where beg = offset + 1@@ -103,12 +105,14 @@ -- | Which table does `Index` refer to? data WhichTable = InHeaderTable | InStaticTable deriving (Eq,Show) +-- | Is header key-value stored in the tables? data HeaderCache = None | KeyOnly WhichTable Index | KeyValue WhichTable Index deriving Show ---------------------------------------------------------------- +-- Physical array index for Header Table. newtype HIndex = HIndex Int deriving (Eq, Ord, Show) ----------------------------------------------------------------@@ -116,20 +120,18 @@ fromHIndexToIndex :: HeaderTable -> HIndex -> Index fromHIndexToIndex HeaderTable{..} (HIndex hidx) = idx where- idx = adj maxNumOfEntries (maxNumOfEntries + hidx - offset)+ idx = adj maxNumOfEntries (hidx - offset) + staticTableSize fromIndexToHIndex :: HeaderTable -> Index -> HIndex fromIndexToHIndex HeaderTable{..} idx = HIndex hidx where- hidx = adj maxNumOfEntries (offset + idx)+ hidx = adj maxNumOfEntries (idx + offset - staticTableSize) fromSIndexToIndex :: HeaderTable -> SIndex -> Index-fromSIndexToIndex HeaderTable{..} sidx = fromStaticIndex sidx + numOfEntries+fromSIndexToIndex _ sidx = fromStaticIndex sidx fromIndexToSIndex :: HeaderTable -> Index -> SIndex-fromIndexToSIndex HeaderTable{..} idx = toStaticIndex sidx- where- sidx = idx - numOfEntries+fromIndexToSIndex _ idx = toStaticIndex idx ---------------------------------------------------------------- @@ -161,23 +163,17 @@ maxN = maxNumbers maxsiz end = maxN - 1 -renewHeaderTable :: Size -> HeaderTable -> IO (HeaderTable, Int)-renewHeaderTable maxsiz oldhdrtbl = do- putStrLn $ "numOfEntries oldhdrtbl: " ++ show (numOfEntries oldhdrtbl)- hdrtbl <- newHeaderTable maxsiz mhp- newhdrtbl <- copyTable oldhdrtbl hdrtbl- putStrLn $ "numOfEntries newhdrtbl: " ++ show (numOfEntries newhdrtbl)- return (newhdrtbl, numOfEntries newhdrtbl)+-- | Renewing 'HeaderTable' with necessary entries copied.+renewHeaderTable :: Size -> HeaderTable -> IO HeaderTable+renewHeaderTable maxsiz oldhdrtbl =+ newHeaderTable maxsiz mhp >>= copyTable oldhdrtbl where mhp = case reverseIndex oldhdrtbl of Nothing -> Nothing _ -> Just HP.empty copyTable :: HeaderTable -> HeaderTable -> IO HeaderTable-copyTable oldhdrtbl newhdrtbl = do- ents <- getEntries oldhdrtbl- putStrLn $ "length of entries: " ++ show (length ents)- copyEntries newhdrtbl ents+copyTable oldhdrtbl newhdrtbl = getEntries oldhdrtbl >>= copyEntries newhdrtbl getEntries :: HeaderTable -> IO [Entry] getEntries HeaderTable{..} = forM [1 .. numOfEntries] readTable@@ -192,6 +188,7 @@ copyEntries hdrtbl' es | otherwise = return hdrtbl +-- | Is the size of 'HeaderTable' really changed? shouldRenew :: HeaderTable -> Size -> Bool shouldRenew HeaderTable{..} maxsiz = maxHeaderTableSize /= maxsiz @@ -201,13 +198,13 @@ -- New 'HeaderTable', the largest new 'Index' -- and a set of dropped OLD 'Index' -- are returned.-insertEntry :: Entry -> HeaderTable -> IO (HeaderTable,Index,[Index])+insertEntry :: Entry -> HeaderTable -> IO HeaderTable insertEntry e hdrtbl = do- (hdrtbl', is, hs) <- insertFront e hdrtbl >>= adjustTableSize+ (hdrtbl', hs) <- insertFront e hdrtbl >>= adjustTableSize let hdrtbl'' = case reverseIndex hdrtbl' of Nothing -> hdrtbl' Just rev -> hdrtbl' { reverseIndex = Just (HP.deleteList hs rev) }- return (hdrtbl'', numOfEntries hdrtbl'', is)+ return hdrtbl'' insertFront :: Entry -> HeaderTable -> IO HeaderTable insertFront e hdrtbl@HeaderTable{..} = do@@ -226,15 +223,15 @@ Nothing -> Nothing Just rev -> Just $ HP.insert (entryHeader e) (HIndex i) rev -adjustTableSize :: HeaderTable -> IO (HeaderTable, [Index], [Header])-adjustTableSize hdrtbl = adjust hdrtbl [] []+adjustTableSize :: HeaderTable -> IO (HeaderTable, [Header])+adjustTableSize hdrtbl = adjust hdrtbl [] -adjust :: HeaderTable -> [Index] -> [Header] -> IO (HeaderTable, [Index], [Header])-adjust hdrtbl is hs- | tsize <= maxtsize = return (hdrtbl, is, hs)+adjust :: HeaderTable -> [Header] -> IO (HeaderTable, [Header])+adjust hdrtbl hs+ | tsize <= maxtsize = return (hdrtbl, hs) | otherwise = do- (hdrtbl', i, h) <- removeEnd hdrtbl- adjust hdrtbl' (i:is) (h:hs)+ (hdrtbl', h) <- removeEnd hdrtbl+ adjust hdrtbl' (h:hs) where tsize = headerTableSize hdrtbl maxtsize = maxHeaderTableSize hdrtbl@@ -251,15 +248,14 @@ } where i = adj maxNumOfEntries (offset + numOfEntries + 1)- hi = numOfEntries + 1 headerTableSize' = headerTableSize + entrySize e reverseIndex' = case reverseIndex of Nothing -> Nothing- Just rev -> Just $ HP.insert (entryHeader e) (HIndex hi) rev+ Just rev -> Just $ HP.insert (entryHeader e) (HIndex i) rev ---------------------------------------------------------------- -removeEnd :: HeaderTable -> IO (HeaderTable,Index,Header)+removeEnd :: HeaderTable -> IO (HeaderTable,Header) removeEnd hdrtbl@HeaderTable{..} = do let i = adj maxNumOfEntries (offset + numOfEntries) e <- readArray circularTable i@@ -270,27 +266,29 @@ numOfEntries = numOfEntries - 1 , headerTableSize = tsize }- return (hdrtbl', numOfEntries - 1, h)+ return (hdrtbl', h) ---------------------------------------------------------------- +-- | Resolving an index from a header. lookupTable :: Header -> HeaderTable -> HeaderCache-lookupTable h hdrtbl = case mrev of+lookupTable h hdrtbl = case reverseIndex hdrtbl of Nothing -> None Just rev -> case HP.search h rev of HP.N -> case HP.search h staticHashPSQ of HP.N -> None HP.K sidx -> KeyOnly InStaticTable (fromSIndexToIndex hdrtbl sidx) HP.KV sidx -> KeyValue InStaticTable (fromSIndexToIndex hdrtbl sidx)- HP.K hidx -> KeyOnly InHeaderTable (fromHIndexToIndex hdrtbl hidx)+ HP.K hidx -> case HP.search h staticHashPSQ of+ HP.N -> KeyOnly InHeaderTable (fromHIndexToIndex hdrtbl hidx)+ HP.K sidx -> KeyOnly InStaticTable (fromSIndexToIndex hdrtbl sidx)+ HP.KV sidx -> KeyValue InStaticTable (fromSIndexToIndex hdrtbl sidx) HP.KV hidx -> KeyValue InHeaderTable (fromHIndexToIndex hdrtbl hidx)- where- mrev = reverseIndex hdrtbl ---------------------------------------------------------------- isIn :: Int -> HeaderTable -> Bool-isIn idx HeaderTable{..} = idx <= numOfEntries+isIn idx HeaderTable{..} = idx > staticTableSize -- | Which table does 'Index' belong to? which :: HeaderTable -> Index -> IO (WhichTable, Entry)
Network/HPACK/Table/Entry.hs view
@@ -74,9 +74,10 @@ ---------------------------------------------------------------- --- | Dummy 'Entry' to initialize a table.+-- | Dummy 'Entry' to initialize a header table. dummyEntry :: Entry dummyEntry = (0,("dummy","dummy")) +-- | How many entries can be stored in a header table? maxNumbers :: Size -> Int maxNumbers siz = siz `div` headerSizeMagicNumber
Network/HPACK/Table/Static.hs view
@@ -7,6 +7,7 @@ , isSIndexValid , toStaticEntry , staticHashPSQ+ , staticTableSize -- fixme ) where import Data.Array (Array, listArray, (!))@@ -30,10 +31,12 @@ -- | The size of static table. staticTableSize :: Size-staticTableSize = 60+staticTableSize = length staticTableList -- | Get 'Entry' from the static table. --+-- >>> toStaticEntry (SIndex 1)+-- (42,(":authority","")) -- >>> toStaticEntry (SIndex 8) -- (42,(":status","200")) -- >>> toStaticEntry (SIndex 50)@@ -43,7 +46,7 @@ -- | Pre-defined static table. staticTable :: Array Index Entry-staticTable = listArray (1,60) $ map toEntry staticTableList+staticTable = listArray (1,staticTableSize) $ map toEntry staticTableList staticHashPSQ :: HP.HashPSQ SIndex staticHashPSQ = HP.fromList alist
Network/HPACK/Types.hs view
@@ -42,7 +42,6 @@ data CompressionAlgo = Naive -- ^ No compression | Static -- ^ Using the static table only | Linear -- ^ Using indices only- | Diff -- ^ Calculating difference -- | Strategy for HPACK encoding. data EncodeStrategy = EncodeStrategy {
http2.cabal view
@@ -1,12 +1,12 @@ Name: http2-Version: 0.4.0+Version: 0.5.0 Author: Kazu Yamamoto <kazu@iij.ad.jp> Maintainer: Kazu Yamamoto <kazu@iij.ad.jp> License: BSD3 License-File: LICENSE Synopsis: HTTP/2.0 library including HPACK Description: HTTP/2.0 library including HPACK.- Currently only HPACK 08 is supported.+ Currently only HPACK 09 is supported. Category: Network Cabal-Version: >= 1.10 Build-Type: Simple@@ -18,8 +18,7 @@ Other-Modules: Network.HPACK.Builder Network.HPACK.Builder.Word8 Network.HPACK.Context- Network.HPACK.Context.HeaderSet- Network.HPACK.Context.ReferenceSet+ Network.HPACK.Context.HeaderList Network.HPACK.Huffman Network.HPACK.Huffman.Bit Network.HPACK.Huffman.ByteString
test-hpack/HPACKDecode.hs view
@@ -8,13 +8,14 @@ , CompressionAlgo(..) ) where +import Control.Applicative ((<$>)) import Control.Exception import Control.Monad (when) import qualified Data.ByteString.Char8 as B8 import Data.List (sort) import Network.HPACK import Network.HPACK.Context-import Network.HPACK.Context.HeaderSet+import Network.HPACK.Context.HeaderList import Network.HPACK.HeaderBlock import HexString@@ -53,14 +54,14 @@ -- context is destructive!!! when (debug conf) $ do putStrLn "--------------------------------"- putStrLn "---- Input headerset"- printHeaderSet $ sort hs+ putStrLn "---- Input headerlist"+ printHeaderList $ sort hs putStrLn "---- Input context" printContext dctx putStrLn "---- Input Hex" B8.putStrLn hex putStrLn "---- Input header block"- print hd+ print bshd' dctx0 <- case size c of Nothing -> return dctx Just siz -> changeContextForDecoding dctx siz@@ -77,4 +78,6 @@ hex = wire c inp = fromHexString hex hs = headers c- hd = fromByteStream inp+ bshd = fromByteStreamDebug inp+ hd = map snd <$> bshd+ bshd' = map (\(x,y)->(toHexString x,y)) <$> bshd
test-hpack/Types.hs view
@@ -4,7 +4,7 @@ module Types ( Test(..) , Case(..)- , HeaderSet+ , HeaderList ) where import Control.Applicative@@ -39,7 +39,7 @@ data Case = Case { size :: Maybe Int , wire :: ByteString- , headers :: HeaderSet+ , headers :: HeaderList , seqno :: Maybe Int } deriving Show @@ -73,11 +73,11 @@ ,"seqno" .= no ] -instance FromJSON HeaderSet where+instance FromJSON HeaderList where parseJSON (Array a) = mapM parseJSON $ V.toList a parseJSON _ = mzero -instance ToJSON HeaderSet where+instance ToJSON HeaderList where toJSON hs = toJSON $ map toJSON hs instance FromJSON Header where
test-hpack/hpack-encode.hs view
@@ -15,7 +15,7 @@ main = do args <- getArgs when (length args /= 3) $ do- hPutStrLn stderr "hpack-encode on/off naive|linear|diff <desc>"+ hPutStrLn stderr "hpack-encode on/off naive|linear <desc>" exitFailure let [arg1,arg2,desc] = args huffman@@ -24,8 +24,7 @@ algo | arg2 == "naive" = Naive | arg2 == "static" = Static- | arg2 == "linear" = Linear- | otherwise = Diff+ | otherwise = Linear stgy = EncodeStrategy algo huffman hpackEncode stgy desc
test-hpack/hpack-stat.hs view
@@ -14,7 +14,7 @@ import Types hdir :: FilePath-hdir = "test-hpack/hpack-test-case/haskell-http2"+hdir = "test-hpack/hpack-test-case/nghttp2" wdir1 :: FilePath wdir1 = "test-hpack/hpack-test-case/haskell-http2-naive"@@ -23,16 +23,17 @@ wdir2 = "test-hpack/hpack-test-case/haskell-http2-naive-huffman" wdir3 :: FilePath-wdir3 = "test-hpack/hpack-test-case/haskell-http2-linear"+wdir3 = "test-hpack/hpack-test-case/haskell-http2-static" wdir4 :: FilePath-wdir4 = "test-hpack/hpack-test-case/haskell-http2-linear-huffman"+wdir4 = "test-hpack/hpack-test-case/haskell-http2-static-huffman" wdir5 :: FilePath-wdir5 = "test-hpack/hpack-test-case/haskell-http2-diff"+wdir5 = "test-hpack/hpack-test-case/haskell-http2-linear" wdir6 :: FilePath-wdir6 = "test-hpack/hpack-test-case/haskell-http2-diff-huffman"+wdir6 = "test-hpack/hpack-test-case/haskell-http2-linear-huffman"+ main :: IO ()
test/DecodeSpec.hs view
@@ -1,29 +1,25 @@ module DecodeSpec where -import Data.List (sort) import Network.HPACK.Context import Network.HPACK.HeaderBlock import Test.Hspec import HeaderBlock -shouldBeSameAs :: (Ord a, Show a) => [a] -> [a] -> Expectation-shouldBeSameAs xs ys = sort xs `shouldBe` sort ys- spec :: Spec spec = do describe "fromHeaderBlock" $ do- it "decodes HeaderSet in request" $ do+ it "decodes HeaderList in request" $ do (c1,h1) <- newContextForDecoding 4096 >>= flip fromHeaderBlock d41- h1 `shouldBeSameAs` d41h+ h1 `shouldBe` d41h (c2,h2) <- fromHeaderBlock c1 d42- h2 `shouldBeSameAs` d42h+ h2 `shouldBe` d42h (_,h3) <- fromHeaderBlock c2 d43- h3 `shouldBeSameAs` d43h- it "decodes HeaderSet in response" $ do+ h3 `shouldBe` d43h+ it "decodes HeaderList in response" $ do (c1,h1) <- newContextForDecoding 256 >>= flip fromHeaderBlock d61- h1 `shouldBeSameAs` d61h+ h1 `shouldBe` d61h (c2,h2) <- fromHeaderBlock c1 d62- h2 `shouldBeSameAs` d62h+ h2 `shouldBe` d62h (_,h3) <- fromHeaderBlock c2 d63- h3 `shouldBeSameAs` d63h+ h3 `shouldBe` d63h
test/HeaderBlock.hs view
@@ -13,112 +13,115 @@ d41 :: HeaderBlock d41 = [ Indexed 2- , Indexed 7 , Indexed 6- , Literal Add (Idx 4) "www.example.com"+ , Indexed 4+ , Literal Add (Idx 1) "www.example.com" ] -d41h :: HeaderSet+d41h :: HeaderList d41h = [(":method","GET")- ,(":scheme","http")- ,(":path","/")- ,(":authority","www.example.com")- ]+ ,(":scheme","http")+ ,(":path","/")+ ,(":authority","www.example.com")+ ] d41b :: ByteStream-d41b = fromHexString "828786448cf1e3c2e5f23a6ba0ab90f4ff"+d41b = fromHexString "828684418cf1e3c2e5f23a6ba0ab90f4ff" d42 :: HeaderBlock d42 = [- Literal Add (Idx 28) "no-cache"+ Indexed 2+ , Indexed 6+ , Indexed 4+ , Indexed 62+ , Literal Add (Idx 24) "no-cache" ] -d42h :: HeaderSet-d42h = [("cache-control","no-cache")- ,(":method","GET")- ,(":scheme","http")- ,(":path","/")- ,(":authority","www.example.com")]+d42h :: HeaderList+d42h = [(":method","GET")+ ,(":scheme","http")+ ,(":path","/")+ ,(":authority","www.example.com")+ ,("cache-control","no-cache")] d42b :: ByteStream-d42b = fromHexString "5c86a8eb10649cbf"+d42b = fromHexString "828684be5886a8eb10649cbf" d43 :: HeaderBlock d43 = [- Clear+ Indexed 2+ , Indexed 7 , Indexed 5- , Indexed 12- , Indexed 11- , Indexed 4+ , Indexed 63 , Literal Add (Lit "custom-key") "custom-value" ] -d43h :: HeaderSet+d43h :: HeaderList d43h = [(":method","GET")- ,(":scheme","https")- ,(":path","/index.html")- ,(":authority","www.example.com")- ,("custom-key","custom-value")- ]+ ,(":scheme","https")+ ,(":path","/index.html")+ ,(":authority","www.example.com")+ ,("custom-key","custom-value")+ ] d43b :: ByteStream-d43b = fromHexString "30858c8b84408825a849e95ba97d7f8925a849e95bb8e8b4bf"+d43b = fromHexString "828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf" ---------------------------------------------------------------- d61 :: HeaderBlock d61 = [ Literal Add (Idx 8) "302"- , Literal Add (Idx 25) "private"- , Literal Add (Idx 35) "Mon, 21 Oct 2013 20:13:21 GMT"- , Literal Add (Idx 49) "https://www.example.com"+ , Literal Add (Idx 24) "private"+ , Literal Add (Idx 33) "Mon, 21 Oct 2013 20:13:21 GMT"+ , Literal Add (Idx 46) "https://www.example.com" ] -d61h :: HeaderSet+d61h :: HeaderList d61h = [(":status","302")- ,("cache-control","private")- ,("date","Mon, 21 Oct 2013 20:13:21 GMT")- ,("location","https://www.example.com")- ]+ ,("cache-control","private")+ ,("date","Mon, 21 Oct 2013 20:13:21 GMT")+ ,("location","https://www.example.com")+ ] d61b :: ByteStream-d61b = fromHexString "488264025985aec3771a4b6396d07abe941054d444a8200595040b8166e082a62d1bff71919d29ad171863c78f0b97c8e9ae82ae43d3"+d61b = fromHexString "488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3" d62 :: HeaderBlock d62 = [- Indexed 12+ Literal Add (Idx 8) "307"+ , Indexed 65+ , Indexed 64+ , Indexed 63 ] -d62h :: HeaderSet-d62h = [(":status","200")- ,("cache-control","private")- ,("date","Mon, 21 Oct 2013 20:13:21 GMT")- ,("location","https://www.example.com")- ]+d62h :: HeaderList+d62h = [(":status","307")+ ,("cache-control","private")+ ,("date","Mon, 21 Oct 2013 20:13:21 GMT")+ ,("location","https://www.example.com")+ ] d62b :: ByteStream-d62b = fromHexString "8c"+d62b = fromHexString "4883640effc1c0bf" d63 :: HeaderBlock d63 = [- Indexed 4- , Indexed 4- , Literal Add (Idx 3) "Mon, 21 Oct 2013 20:13:22 GMT"- , Literal Add (Idx 30) "gzip"- , Indexed 4- , Indexed 4- , Indexed 3- , Indexed 3- ,Literal Add (Idx 59) "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"+ Indexed 8+ , Indexed 65+ , Literal Add (Idx 33) "Mon, 21 Oct 2013 20:13:22 GMT"+ , Indexed 64+ , Literal Add (Idx 26) "gzip"+ ,Literal Add (Idx 55) "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1" ] -d63h :: HeaderSet-d63h = [("cache-control","private")- ,("date","Mon, 21 Oct 2013 20:13:22 GMT")- ,("content-encoding","gzip")- ,("location","https://www.example.com")- ,(":status","200")- ,("set-cookie","foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1")]+d63h :: HeaderList+d63h = [(":status","200")+ ,("cache-control","private")+ ,("date","Mon, 21 Oct 2013 20:13:22 GMT")+ ,("location","https://www.example.com")+ ,("content-encoding","gzip")+ ,("set-cookie","foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1")] d63b :: ByteStream-d63b = fromHexString "84844396d07abe941054d444a8200595040b8166e084a62d1bff5e839bd9ab848483837bad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007"+d63b = fromHexString "88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007"
test/HeaderBlockSpec.hs view
@@ -28,6 +28,7 @@ fromByteStream d61b `shouldBe` Right d61 fromByteStream d62b `shouldBe` Right d62 fromByteStream d63b `shouldBe` Right d63+ describe "toByteStream & fromByteStream" $ do prop "duality for request" $ \k v -> do let key = BS.pack ('k':k)