http2 0.8.0 → 0.9.0
raw patch · 7 files changed
+226/−134 lines, 7 filesdep +Glob
Dependencies added: Glob
Files
- Network/HTTP2.hs +2/−0
- Network/HTTP2/Decode.hs +62/−39
- Network/HTTP2/Encode.hs +108/−72
- Network/HTTP2/Types.hs +17/−0
- http2.cabal +2/−1
- test-frame/Case.hs +2/−2
- test-frame/JSON.hs +33/−20
Network/HTTP2.hs view
@@ -6,8 +6,10 @@ Frame(..) , FrameHeader(..) , FramePayload(..)+ , isPaddingDefined -- * Encoding , encodeFrame+ , encodeFrameChunks , encodeFrameHeader , encodeFramePayload , EncodeInfo(..)
Network/HTTP2/Decode.hs view
@@ -42,19 +42,16 @@ decodeFrame :: Settings -- ^ HTTP/2 settings -> ByteString -- ^ Input byte-stream -> Either HTTP2Error Frame -- ^ Decoded frame-decodeFrame settings bs = decode typ+decodeFrame settings bs = checkFrameHeader settings (decodeFrameHeader bs0)+ >>= \(typ,header) -> decodeFramePayload typ header bs1+ >>= \payload -> return $ Frame header payload where (bs0,bs1) = BS.splitAt 9 bs- (typ, header) = decodeFrameHeader bs0- decode (FrameUnknown v) = Right $ Frame header $ decodeUnknownFrame v bs1- decode ftyp = case checkFrameHeader settings ftyp header of- Just h2err -> Left h2err- Nothing -> Right $ Frame header $ decodeFramePayload typ header bs1 - ---------------------------------------------------------------- --- | Must supply 9 bytes.+-- | Decoding an HTTP/2 frame header.+-- Must supply 9 bytes. decodeFrameHeader :: ByteString -> (FrameTypeId, FrameHeader) decodeFrameHeader (PS fptr off _) = inlinePerformIO $ withForeignPtr fptr $ \ptr -> do let p = ptr +. off@@ -76,31 +73,41 @@ -- | Checking a frame header and reporting an error if any. -- -- >>> let stid = toStreamIdentifier 0--- >>> checkFrameHeader defaultSettings FrameData (FrameHeader 100 0 stid)--- Just (ConnectionError ProtocolError "cannot used in control stream")-checkFrameHeader :: Settings -> FrameTypeId -> FrameHeader -> Maybe HTTP2Error-checkFrameHeader Settings {..} typ FrameHeader {..}+-- >>> checkFrameHeader defaultSettings (FrameData,(FrameHeader 100 0 stid))+-- Left (ConnectionError ProtocolError "cannot used in control stream")+checkFrameHeader :: Settings+ -> (FrameTypeId, FrameHeader)+ -> Either HTTP2Error (FrameTypeId, FrameHeader)+checkFrameHeader Settings {..} typfrm@(typ,FrameHeader {..}) | payloadLength > maxFrameSize =- Just $ ConnectionError FrameSizeError "exceeds maximum frame size"+ Left $ ConnectionError FrameSizeError "exceeds maximum frame size" | typ `elem` nonZeroFrameTypes && isControl streamId =- Just $ ConnectionError ProtocolError "cannot used in control stream"+ Left $ ConnectionError ProtocolError "cannot used in control stream" | typ `elem` zeroFrameTypes && not (isControl streamId) =- Just $ ConnectionError ProtocolError "cannot used in non-zero stream"+ Left $ ConnectionError ProtocolError "cannot used in non-zero stream" | otherwise = checkType typ where checkType FramePriority | payloadLength /= 5 =- Just $ StreamError FrameSizeError streamId+ Left $ StreamError FrameSizeError streamId checkType FrameRSTStream | payloadLength /= 4 =- Just $ ConnectionError FrameSizeError "payload length is not 4 in rst stream frame"- checkType FrameSettings | payloadLength `mod` 6 /= 0 =- Just $ ConnectionError FrameSizeError "payload length is not multiple of 6 in settings frame"- checkType FramePushPromise | not enablePush =- Just $ ConnectionError ProtocolError "push not enabled" -- checkme+ Left $ ConnectionError FrameSizeError "payload length is not 4 in rst stream frame"+ checkType FrameSettings+ | payloadLength `mod` 6 /= 0 =+ Left $ ConnectionError FrameSizeError "payload length is not multiple of 6 in settings frame"+ | testAck flags && payloadLength /= 0 =+ Left $ ConnectionError FrameSizeError "payload length must be 0 if ack flag is set"+ checkType FramePushPromise+ | not enablePush =+ Left $ ConnectionError ProtocolError "push not enabled" -- checkme+ | not (isResponse streamId) =+ Left $ ConnectionError ProtocolError "push promise must be used with even stream identifier" checkType FramePing | payloadLength /= 8 =- Just $ ConnectionError FrameSizeError "payload length is 8 in ping frame"+ Left $ ConnectionError FrameSizeError "payload length is 8 in ping frame"+ checkType FrameGoAway | payloadLength < 8 =+ Left $ ConnectionError FrameSizeError "goaway body must be 8 bytes or larger" checkType FrameWindowUpdate | payloadLength /= 4 =- Just $ ConnectionError FrameSizeError "payload length is 4 in window update frame"- checkType _ = Nothing+ Left $ ConnectionError FrameSizeError "payload length is 4 in window update frame"+ checkType _ = Right typfrm zeroFrameTypes :: [FrameTypeId] zeroFrameTypes = [@@ -121,7 +128,8 @@ ---------------------------------------------------------------- -type FramePayloadDecoder = FrameHeader -> ByteString -> FramePayload+type FramePayloadDecoder = FrameHeader -> ByteString+ -> Either HTTP2Error FramePayload payloadDecoders :: Array Word8 FramePayloadDecoder payloadDecoders = listArray (minFrameType, maxFrameType)@@ -137,8 +145,12 @@ , decodeContinuationFrame ] +-- | Decoding an HTTP/2 frame payload. decodeFramePayload :: FrameTypeId -> FramePayloadDecoder-decodeFramePayload ftyp = payloadDecoders ! fromFrameTypeId ftyp+decodeFramePayload (FrameUnknown typ) = checkFrameSize $ decodeUnknownFrame typ+decodeFramePayload ftyp = checkFrameSize decoder+ where+ decoder = payloadDecoders ! fromFrameTypeId ftyp ---------------------------------------------------------------- @@ -157,13 +169,13 @@ hasPriority = testPriority $ flags header decodePriorityFrame :: FramePayloadDecoder-decodePriorityFrame _ bs = PriorityFrame $ priority bs+decodePriorityFrame _ bs = Right $ PriorityFrame $ priority bs decoderstStreamFrame :: FramePayloadDecoder-decoderstStreamFrame _ bs = RSTStreamFrame $ toErrorCodeId (word32 bs)+decoderstStreamFrame _ bs = Right $ RSTStreamFrame $ toErrorCodeId (word32 bs) decodeSettingsFrame :: FramePayloadDecoder-decodeSettingsFrame FrameHeader{..} (PS fptr off _) = SettingsFrame alist+decodeSettingsFrame FrameHeader{..} (PS fptr off _) = Right $ SettingsFrame alist where num = payloadLength `div` 6 alist = inlinePerformIO $ withForeignPtr fptr $ \ptr -> do@@ -188,10 +200,10 @@ in PushPromiseFrame sid bs1 decodePingFrame :: FramePayloadDecoder-decodePingFrame _ bs = PingFrame bs+decodePingFrame _ bs = Right $ PingFrame bs decodeGoAwayFrame :: FramePayloadDecoder-decodeGoAwayFrame _ bs = GoAwayFrame sid ecid bs2+decodeGoAwayFrame _ bs = Right $ GoAwayFrame sid ecid bs2 where (bs0,bs1') = BS.splitAt 4 bs (bs1,bs2) = BS.splitAt 4 bs1'@@ -199,29 +211,40 @@ ecid = toErrorCodeId (word32 bs1) decodeWindowUpdateFrame :: FramePayloadDecoder-decodeWindowUpdateFrame _ bs = WindowUpdateFrame wsi+decodeWindowUpdateFrame _ bs+ | wsi == 0 = Left $ ConnectionError ProtocolError "window update must not be 0"+ | otherwise = Right $ WindowUpdateFrame wsi where !wsi = word32 bs `clearBit` 31 decodeContinuationFrame :: FramePayloadDecoder-decodeContinuationFrame _ bs = ContinuationFrame bs+decodeContinuationFrame _ bs = Right $ ContinuationFrame bs -decodeUnknownFrame :: FrameType -> ByteString -> FramePayload-decodeUnknownFrame typ bs = UnknownFrame typ bs+decodeUnknownFrame :: FrameType -> FramePayloadDecoder+decodeUnknownFrame typ _ bs = Right $ UnknownFrame typ bs ---------------------------------------------------------------- +checkFrameSize :: FramePayloadDecoder -> FramePayloadDecoder+checkFrameSize func header@FrameHeader{..} body+ | payloadLength > BS.length body =+ Left $ ConnectionError FrameSizeError "payload is too short"+ | otherwise = func header body+ -- | Helper function to pull off the padding if its there, and will -- eat up the trailing padding automatically. Calls the decoder func -- passed in with the length of the unpadded portion between the -- padding octet and the actual padding-decodeWithPadding :: FrameHeader -> ByteString -> (ByteString -> FramePayload) -> FramePayload+decodeWithPadding :: FrameHeader -> ByteString -> (ByteString -> FramePayload) -> Either HTTP2Error FramePayload decodeWithPadding FrameHeader{..} bs body | padded = let Just (w8,rest) = BS.uncons bs padlen = intFromWord8 w8- rest' = BS.take (payloadLength - padlen - 1) rest- in body rest'- | otherwise = body bs+ bodylen = payloadLength - padlen - 1+ in if bodylen < 0 then+ Left $ ConnectionError ProtocolError "padding is not enough"+ else+ Right . body $ BS.take bodylen rest+ | otherwise = Right $ body bs where padded = testPadded flags
Network/HTTP2/Encode.hs view
@@ -2,34 +2,33 @@ module Network.HTTP2.Encode ( encodeFrame+ , encodeFrameChunks , encodeFrameHeader , encodeFramePayload- , buildFrame- , buildFrameHeader- , buildFramePayload , EncodeInfo(..) , encodeInfo ) where -import Blaze.ByteString.Builder (Builder)-import qualified Blaze.ByteString.Builder as BB-import Data.Bits-import Data.ByteString (ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL-import Data.Monoid ((<>), mempty)+import Data.Bits (shiftR, (.&.))+import qualified Data.ByteString as BS+import Data.ByteString.Internal (ByteString, unsafeCreate)+import Data.Word (Word8, Word16, Word32)+import Foreign.Ptr (Ptr, plusPtr)+import Foreign.Storable (poke) import Network.HTTP2.Types ---------------------------------------------------------------- +type Builder = [ByteString] -> [ByteString]+ -- | Auxiliary information for frame encoding. data EncodeInfo = EncodeInfo { -- | Flags to be set in a frame header encodeFlags :: FrameFlags -- | Stream id to be set in a frame header , encodeStreamId :: StreamIdentifier- -- | Padding if any+ -- | Padding if any. In the case where this value is set but the priority flag is not set, this value gets preference over the priority flag. So, if this value is set, the priority flag is also set. , encodePadding :: Maybe Padding } deriving (Show,Read) @@ -53,40 +52,35 @@ -- >>> encodeFrame (encodeInfo id 1) (DataFrame "body") -- "\NUL\NUL\EOT\NUL\NUL\NUL\NUL\NUL\SOHbody" encodeFrame :: EncodeInfo -> FramePayload -> ByteString-encodeFrame einfo payload = run $ buildFrame einfo payload--encodeFrameHeader :: FrameTypeId -> FrameHeader -> ByteString-encodeFrameHeader ftid header = run $ buildFrameHeader ftid header+encodeFrame einfo payload = BS.concat $ encodeFrameChunks einfo payload -encodeFramePayload :: EncodeInfo -> FramePayload -> ByteString-encodeFramePayload einfo payload = run payloadBuilder+-- | Encoding an HTTP/2 frame to ['ByteString'].+-- This is suitable for sendMany.+encodeFrameChunks :: EncodeInfo -> FramePayload -> [ByteString]+encodeFrameChunks einfo payload = bs : bss where- (_, payloadBuilder) = buildFramePayload einfo payload--run :: Builder -> ByteString-run = BL.toStrict . BB.toLazyByteString------------------------------------------------------------------+ ftid = framePayloadToFrameTypeId payload+ bs = encodeFrameHeader ftid header+ (header, bss) = encodeFramePayload einfo payload -buildFrame :: EncodeInfo -> FramePayload -> Builder-buildFrame einfo payload = headerBuilder <> payloadBuilder+-- | Encoding an HTTP/2 frame header.+-- The frame header must be completed.+encodeFrameHeader :: FrameTypeId -> FrameHeader -> ByteString+encodeFrameHeader ftid FrameHeader{..} = unsafeCreate frameHeaderLength $ \ptr -> do+ poke24 ptr payloadLength+ poke8 ptr 3 typ+ poke8 ptr 4 flags+ poke32 (ptr `plusPtr` 5) sid where- (header, payloadBuilder) = buildFramePayload einfo payload- ftyp = framePayloadToFrameTypeId payload- headerBuilder = buildFrameHeader ftyp header------------------------------------------------------------------+ typ = fromFrameTypeId ftid+ sid = fromIntegral $ fromStreamIdentifier streamId -buildFrameHeader :: FrameTypeId -> FrameHeader -> Builder-buildFrameHeader ftyp FrameHeader{..} = len <> typ <> flg <> sid+-- | Encoding an HTTP/2 frame payload.+-- This returns a complete frame header and chunks of payload.+encodeFramePayload :: EncodeInfo -> FramePayload -> (FrameHeader, [ByteString])+encodeFramePayload einfo payload = (header, builder []) where- -- fixme: 2^14 check- len1 = BB.fromWord16be (fromIntegral (payloadLength `shiftR` 8))- len2 = BB.fromWord8 (fromIntegral (payloadLength .&. 0xff))- len = len1 <> len2- typ = BB.fromWord8 $ fromFrameTypeId ftyp- flg = BB.fromWord8 flags- sid = BB.fromWord32be . fromIntegral $ fromStreamIdentifier streamId+ (header, builder) = buildFramePayload einfo payload ---------------------------------------------------------------- @@ -128,51 +122,44 @@ (header, builder) where header = FrameHeader len newflags encodeStreamId- builder = bpadlen <> btarget <> bpadding- bpadlen = BB.fromWord8 $ fromIntegral paddingLength- bpadding = BB.fromByteString padding- paddingLength = B.length padding+ builder = (b1 :) . btarget . (padding :)+ b1 = BS.singleton $ fromIntegral paddingLength+ paddingLength = BS.length padding len = targetLength + paddingLength + 1 newflags = setPadded encodeFlags buildPriority :: Priority -> Builder buildPriority Priority{..} = builder where- builder = bstream <> bweight+ builder = (priority :) stream = fromStreamIdentifier streamDependency estream | exclusive = setExclusive stream | otherwise = stream- bstream = BB.fromWord32be $ fromIntegral estream- bweight = BB.fromWord8 $ fromIntegral $ weight - 1---- fixme: clear 31th bit?-buildStream :: StreamIdentifier -> Builder-buildStream = BB.fromWord32be . fromIntegral . fromStreamIdentifier--buildErrorCodeId :: ErrorCodeId -> Builder-buildErrorCodeId = BB.fromWord32be . fromErrorCodeId+ priority = unsafeCreate 5 $ \ptr -> do+ poke32 ptr $ fromIntegral estream+ poke8 ptr 4 $ fromIntegral $ weight - 1 ---------------------------------------------------------------- buildFramePayloadData :: EncodeInfo -> ByteString -> (FrameHeader, Builder) buildFramePayloadData einfo body = buildPadding einfo builder len where- builder = BB.fromByteString body- len = B.length body+ builder = (body :)+ len = BS.length body buildFramePayloadHeaders :: EncodeInfo -> Maybe Priority -> HeaderBlockFragment -> (FrameHeader, Builder) buildFramePayloadHeaders einfo Nothing hdr = buildPadding einfo builder len where- builder = BB.fromByteString hdr- len = B.length hdr+ builder = (hdr :)+ len = BS.length hdr buildFramePayloadHeaders einfo (Just pri) hdr = buildPadding einfo' builder len where- builder = buildPriority pri <> BB.fromByteString hdr- len = B.length hdr + 5+ builder = buildPriority pri . (hdr :)+ len = BS.length hdr + 5 einfo' = einfo { encodeFlags = setPriority (encodeFlags einfo) } buildFramePayloadPriority :: EncodeInfo -> Priority -> (FrameHeader, Builder)@@ -184,50 +171,99 @@ buildFramePayloadRSTStream :: EncodeInfo -> ErrorCodeId -> (FrameHeader, Builder) buildFramePayloadRSTStream EncodeInfo{..} e = (header, builder) where- builder = buildErrorCodeId e+ builder = (b4 :)+ b4 = bytestring4 $ fromErrorCodeId e header = FrameHeader 4 encodeFlags encodeStreamId buildFramePayloadSettings :: EncodeInfo -> SettingsList -> (FrameHeader, Builder) buildFramePayloadSettings EncodeInfo{..} alist = (header, builder) where- builder = foldr op mempty alist- (key, val) `op` x = BB.fromWord16be (fromSettingsKeyId key)- <> BB.fromWord32be (fromIntegral val) <> x+ builder = (settings :)+ settings = unsafeCreate len $ \ptr -> go ptr alist+ go _ [] = return ()+ go p ((k,v):kvs) = do+ poke16 p $ fromSettingsKeyId k+ poke32 (p `plusPtr` 2) $ fromIntegral v+ go (p `plusPtr` 6) kvs len = length alist * 6 header = FrameHeader len encodeFlags encodeStreamId buildFramePayloadPushPromise :: EncodeInfo -> StreamIdentifier -> HeaderBlockFragment -> (FrameHeader, Builder)-buildFramePayloadPushPromise einfo sid hdr = buildPadding einfo builder len+buildFramePayloadPushPromise einfo stid hdr = buildPadding einfo builder len where- builder = buildStream sid <> BB.fromByteString hdr- len = 4 + B.length hdr+ builder = (b4 :) . (hdr :)+ b4 = bytestring4 $ fromIntegral $ fromStreamIdentifier stid+ len = 4 + BS.length hdr buildFramePayloadPing :: EncodeInfo -> ByteString -> (FrameHeader, Builder) buildFramePayloadPing EncodeInfo{..} odata = (header, builder) where- builder = BB.fromByteString odata+ builder = (odata :) header = FrameHeader 8 encodeFlags encodeStreamId buildFramePayloadGoAway :: EncodeInfo -> LastStreamId -> ErrorCodeId -> ByteString -> (FrameHeader, Builder) buildFramePayloadGoAway EncodeInfo{..} sid e debug = (header, builder) where- builder = buildStream sid <> buildErrorCodeId e <> BB.fromByteString debug- len = 4 + 4 + B.length debug+ builder = (b8 :) . (debug :)+ len0 = 8+ b8 = unsafeCreate len0 $ \ptr -> do+ poke32 ptr $ fromIntegral $ fromStreamIdentifier sid+ poke32 (ptr `plusPtr` 4) $ fromErrorCodeId e+ len = len0 + BS.length debug header = FrameHeader len encodeFlags encodeStreamId buildFramePayloadWindowUpdate :: EncodeInfo -> WindowSizeIncrement -> (FrameHeader, Builder) buildFramePayloadWindowUpdate EncodeInfo{..} size = (header, builder) where -- fixme: reserve bit- builder = BB.fromWord32be size+ builder = (b4 :)+ b4 = bytestring4 size header = FrameHeader 4 encodeFlags encodeStreamId buildFramePayloadContinuation :: EncodeInfo -> HeaderBlockFragment -> (FrameHeader, Builder) buildFramePayloadContinuation EncodeInfo{..} hdr = (header, builder) where- builder = BB.fromByteString hdr- len = B.length hdr+ builder = (hdr :)+ len = BS.length hdr header = FrameHeader len encodeFlags encodeStreamId buildFramePayloadUnknown :: EncodeInfo -> ByteString -> (FrameHeader, Builder) buildFramePayloadUnknown = buildFramePayloadData++----------------------------------------------------------------++poke8 :: Ptr Word8 -> Int -> Word8 -> IO ()+poke8 ptr n w = poke (ptr `plusPtr` n) w++poke16 :: Ptr Word8 -> Word16 -> IO ()+poke16 ptr i = do+ poke ptr w0+ poke8 ptr 1 w1+ where+ w0 = fromIntegral ((i `shiftR` 8) .&. 0xff)+ w1 = fromIntegral (i .&. 0xff)++poke24 :: Ptr Word8 -> Int -> IO ()+poke24 ptr i = do+ poke ptr w0+ poke8 ptr 1 w1+ poke8 ptr 2 w2+ where+ w0 = fromIntegral ((i `shiftR` 16) .&. 0xff)+ w1 = fromIntegral ((i `shiftR` 8) .&. 0xff)+ w2 = fromIntegral (i .&. 0xff)++poke32 :: Ptr Word8 -> Word32 -> IO ()+poke32 ptr i = do+ poke ptr w0+ poke8 ptr 1 w1+ poke8 ptr 2 w2+ poke8 ptr 3 w3+ where+ w0 = fromIntegral ((i `shiftR` 24) .&. 0xff)+ w1 = fromIntegral ((i `shiftR` 16) .&. 0xff)+ w2 = fromIntegral ((i `shiftR` 8) .&. 0xff)+ w3 = fromIntegral (i .&. 0xff)++bytestring4 :: Word32 -> ByteString+bytestring4 i = unsafeCreate 4 $ \ptr -> poke32 ptr i
Network/HTTP2/Types.hs view
@@ -36,6 +36,7 @@ , FrameHeader(..) , FramePayload(..) , framePayloadToFrameTypeId+ , isPaddingDefined -- * Stream identifier , StreamIdentifier(..) , fromStreamIdentifier@@ -535,3 +536,19 @@ framePayloadToFrameTypeId (WindowUpdateFrame _) = FrameWindowUpdate framePayloadToFrameTypeId (ContinuationFrame _) = FrameContinuation framePayloadToFrameTypeId (UnknownFrame w8 _) = FrameUnknown w8++----------------------------------------------------------------++-- | Checking if padding is defined in this frame type.+isPaddingDefined :: FramePayload -> Bool+isPaddingDefined (DataFrame _) = True+isPaddingDefined (HeadersFrame _ _) = True+isPaddingDefined (PriorityFrame _) = False+isPaddingDefined (RSTStreamFrame _) = False+isPaddingDefined (SettingsFrame _) = False+isPaddingDefined (PushPromiseFrame _ _) = True+isPaddingDefined (PingFrame _) = False+isPaddingDefined (GoAwayFrame _ _ _) = False+isPaddingDefined (WindowUpdateFrame _) = False+isPaddingDefined (ContinuationFrame _) = False+isPaddingDefined (UnknownFrame _ _) = False
http2.cabal view
@@ -1,5 +1,5 @@ Name: http2-Version: 0.8.0+Version: 0.9.0 Author: Kazu Yamamoto <kazu@iij.ad.jp> Maintainer: Kazu Yamamoto <kazu@iij.ad.jp> License: BSD3@@ -133,6 +133,7 @@ Other-Modules: Case JSON Build-Depends: base >= 4 && < 5+ , Glob , aeson , aeson-pretty , bytestring
test-frame/Case.hs view
@@ -44,8 +44,8 @@ where -- fromJust is unsafe frm = case decodeFrame defaultSettings $ fromJust $ unhex wire_hex of- Left e -> error $ show e- Right (r,_) -> r+ Left e -> error $ show e+ Right r -> r wireToCase CaseWire { wire_error = Just e, ..} = Case { description = wire_description , wire = wire_hex
test-frame/JSON.hs view
@@ -13,7 +13,7 @@ import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as B8 import Data.HashMap.Strict (union)-import Data.Maybe (fromJust)+import Data.Maybe (fromJust, fromMaybe) import Data.Text (Text) import qualified Data.Text as T @@ -63,19 +63,6 @@ instance FromJSON StreamIdentifier where parseJSON x = StreamIdentifier <$> parseJSON x -instance ToJSON Priority where- toJSON (Priority e s w) = object [- "exclusive" .= e- , "stream_dependency" .= s- , "weight" .= w- ]--instance FromJSON Priority where- parseJSON (Object o) = Priority <$> o .: "exclusive"- <*> o .: "stream_dependency"- <*> o .: "weight"- parseJSON _ = mzero- instance ToJSON ErrorCodeId where toJSON e = toJSON $ fromErrorCodeId e @@ -101,11 +88,15 @@ "data" .= body ] toJSON (HeadersFrame mpri hdr) = object [- "priority" .= mpri+ "exclusive" .= fromMaybe Null (toJSON . exclusive <$> mpri)+ , "stream_dependency" .= fromMaybe Null (toJSON . streamDependency <$> mpri)+ , "weight" .= fromMaybe Null (toJSON . weight <$> mpri) , "header_block_fragment" .= hdr ] toJSON (PriorityFrame pri) = object [- "priority" .= pri+ "exclusive" .= exclusive pri+ , "stream_dependency" .= streamDependency pri+ , "weight" .= weight pri ] toJSON (RSTStreamFrame e) = object [ "error_code" .= e@@ -147,7 +138,9 @@ ] where padObj = case toJSON fpPad of- Null -> emptyPad+ Null+ | isPaddingDefined framePayload -> emptyPad+ | otherwise -> noPad x -> x instance FromJSON FramePad where@@ -175,11 +168,27 @@ where ftid = toFrameTypeId ftyp +priority :: Object -> Parser Priority+priority o = Priority <$> o .: "exclusive"+ <*> o .: "stream_dependency"+ <*> o .: "weight"++mpriority :: Object -> Parser (Maybe Priority)+mpriority o = do+ me <- o .:? "exclusive"+ ms <- o .:? "stream_dependency"+ mw <- o .:? "weight"+ return $ case me of+ Nothing -> Nothing+ Just ex -> Just $ Priority ex (fromJust ms) (fromJust mw)+ parsePayload :: FrameTypeId -> Object -> Parser FramePayload parsePayload FrameData o = DataFrame <$> o .: "data"-parsePayload FrameHeaders o = HeadersFrame <$> o .: "priority"- <*> o .: "header_block_fragment"-parsePayload FramePriority o = PriorityFrame <$> o .: "priority"+parsePayload FrameHeaders o = do+ mpri <- mpriority o+ hdr <- o .: "header_block_fragment"+ return $ HeadersFrame mpri hdr+parsePayload FramePriority o = PriorityFrame <$> priority o parsePayload FrameRSTStream o = RSTStreamFrame <$> o .: "error_code" parsePayload FrameSettings o = SettingsFrame <$> o .: "settings" parsePayload FramePushPromise o = PushPromiseFrame <$> o .: "promised_stream_id"@@ -190,6 +199,7 @@ <*> o .: "additional_debug_data" parsePayload FrameWindowUpdate o = WindowUpdateFrame <$> o .: "window_size_increment" parsePayload FrameContinuation o = ContinuationFrame <$> o .: "header_block_fragment"+parsePayload (FrameUnknown typ) o = UnknownFrame typ <$> o .: "dummy" instance ToJSON Pad where toJSON (Pad padding) = object [@@ -202,6 +212,9 @@ "padding_length" .= Null , "padding" .= Null ]++noPad :: Value+noPad = object [] ----------------------------------------------------------------