{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Network.HTTP2.Frame.Decode ( -- * Decoding decodeFrame, decodeFrameHeader, checkFrameHeader, FrameDecodeError (..), -- * Decoding payload decodeFramePayload, FramePayloadDecoder, decodeDataFrame, decodeHeadersFrame, decodePriorityFrame, decodeRSTStreamFrame, decodeSettingsFrame, decodePushPromiseFrame, decodePingFrame, decodeGoAwayFrame, decodeWindowUpdateFrame, decodeContinuationFrame, ) where import Control.Exception (Exception) import Data.Array (Array, listArray, (!)) import qualified Data.ByteString as BS import Foreign.Ptr (Ptr, plusPtr) import qualified Network.ByteOrder as N import System.IO.Unsafe (unsafeDupablePerformIO) import Imports import Network.HTTP2.Frame.Types ---------------------------------------------------------------- data FrameDecodeError = FrameDecodeError ErrorCode StreamId ShortByteString deriving (Eq, Show) instance Exception FrameDecodeError ---------------------------------------------------------------- -- | Decoding an HTTP/2 frame to 'ByteString'. -- The second argument must be include the entire of frame. -- So, this function is not useful for real applications -- but useful for testing. decodeFrame :: ByteString -- ^ Input byte-stream -> Either FrameDecodeError Frame -- ^ Decoded frame decodeFrame bs = checkFrameHeader (decodeFrameHeader bs0) >>= \(typ, header) -> decodeFramePayload typ header bs1 >>= \payload -> return $ Frame header payload where (bs0, bs1) = BS.splitAt 9 bs ---------------------------------------------------------------- -- | Decoding an HTTP/2 frame header. -- Must supply 9 bytes. decodeFrameHeader :: ByteString -> (FrameType, FrameHeader) decodeFrameHeader (PS fptr off _) = unsafeDupablePerformIO $ withForeignPtr fptr $ \ptr -> do let p = ptr +. off len <- fromIntegral <$> N.peek24 p 0 typ <- toFrameType <$> N.peek8 p 3 flg <- N.peek8 p 4 w32 <- N.peek32 p 5 let sid = streamIdentifier w32 return (typ, FrameHeader len flg sid) (+.) :: Ptr Word8 -> Int -> Ptr Word8 (+.) = plusPtr ---------------------------------------------------------------- -- | Checking a frame header and reporting an error if any. -- -- >>> checkFrameHeader (FrameData,(FrameHeader 100 0 0)) -- Left (FrameDecodeError ProtocolError 0 "cannot used in control stream") checkFrameHeader :: (FrameType, FrameHeader) -> Either FrameDecodeError (FrameType, FrameHeader) checkFrameHeader typfrm@(typ, FrameHeader{..}) | typ `elem` nonZeroFrameTypes && isControl streamId = Left $ FrameDecodeError ProtocolError streamId "cannot used in control stream" | typ `elem` zeroFrameTypes && not (isControl streamId) = Left $ FrameDecodeError ProtocolError streamId "cannot used in non-zero stream" | otherwise = checkType typ where checkType FrameHeaders | testPadded flags && payloadLength < 1 = Left $ FrameDecodeError FrameSizeError streamId "insufficient payload for Pad Length" | testPriority flags && payloadLength < 5 = Left $ FrameDecodeError FrameSizeError streamId "insufficient payload for priority fields" | testPadded flags && testPriority flags && payloadLength < 6 = Left $ FrameDecodeError FrameSizeError streamId "insufficient payload for Pad Length and priority fields" checkType FramePriority | payloadLength /= 5 = Left $ FrameDecodeError FrameSizeError streamId "payload length is not 5 in priority frame" checkType FrameRSTStream | payloadLength /= 4 = Left $ FrameDecodeError FrameSizeError streamId "payload length is not 4 in rst stream frame" checkType FrameSettings | payloadLength `mod` 6 /= 0 = Left $ FrameDecodeError FrameSizeError streamId "payload length is not multiple of 6 in settings frame" | testAck flags && payloadLength /= 0 = Left $ FrameDecodeError FrameSizeError streamId "payload length must be 0 if ack flag is set" checkType FramePushPromise | isServerInitiated streamId = Left $ FrameDecodeError ProtocolError streamId "push promise must be used with an odd stream identifier" checkType FramePing | payloadLength /= 8 = Left $ FrameDecodeError FrameSizeError streamId "payload length is 8 in ping frame" checkType FrameGoAway | payloadLength < 8 = Left $ FrameDecodeError FrameSizeError streamId "goaway body must be 8 bytes or larger" checkType FrameWindowUpdate | payloadLength /= 4 = Left $ FrameDecodeError FrameSizeError streamId "payload length is 4 in window update frame" checkType _ = Right typfrm zeroFrameTypes :: [FrameType] zeroFrameTypes = [ FrameSettings , FramePing , FrameGoAway ] nonZeroFrameTypes :: [FrameType] nonZeroFrameTypes = [ FrameData , FrameHeaders , FramePriority , FrameRSTStream , FramePushPromise , FrameContinuation ] ---------------------------------------------------------------- -- | The type for frame payload decoder. type FramePayloadDecoder = FrameHeader -> ByteString -> Either FrameDecodeError FramePayload payloadDecoders :: Array FrameType FramePayloadDecoder payloadDecoders = listArray (minFrameType, maxFrameType) [ decodeDataFrame , decodeHeadersFrame , decodePriorityFrame , decodeRSTStreamFrame , decodeSettingsFrame , decodePushPromiseFrame , decodePingFrame , decodeGoAwayFrame , decodeWindowUpdateFrame , decodeContinuationFrame ] -- | Decoding an HTTP/2 frame payload. -- This function is considered to return a frame payload decoder -- according to a frame type. decodeFramePayload :: FrameType -> FramePayloadDecoder decodeFramePayload ftyp | ftyp > maxFrameType = checkFrameSize $ decodeUnknownFrame ftyp decodeFramePayload ftyp = checkFrameSize decoder where decoder = payloadDecoders ! ftyp ---------------------------------------------------------------- -- | Frame payload decoder for DATA frame. decodeDataFrame :: FramePayloadDecoder decodeDataFrame header bs = decodeWithPadding header bs DataFrame -- | Frame payload decoder for HEADERS frame. decodeHeadersFrame :: FramePayloadDecoder decodeHeadersFrame header bs = decodeWithPadding header bs $ \bs' -> if hasPriority then let (bs0, bs1) = BS.splitAt 5 bs' p = priority bs0 in HeadersFrame (Just p) bs1 else HeadersFrame Nothing bs' where hasPriority = testPriority $ flags header -- | Frame payload decoder for PRIORITY frame. decodePriorityFrame :: FramePayloadDecoder decodePriorityFrame _ bs = Right $ PriorityFrame $ priority bs -- | Frame payload decoder for RST_STREAM frame. decodeRSTStreamFrame :: FramePayloadDecoder decodeRSTStreamFrame _ bs = Right $ RSTStreamFrame $ toErrorCode (N.word32 bs) -- | Frame payload decoder for SETTINGS frame. decodeSettingsFrame :: FramePayloadDecoder decodeSettingsFrame FrameHeader{..} (PS fptr off _) | num > 10 = Left $ FrameDecodeError EnhanceYourCalm streamId "Settings is too large" | otherwise = Right $ SettingsFrame alist where num = payloadLength `div` 6 alist = unsafeDupablePerformIO $ withForeignPtr fptr $ \ptr -> do let p = ptr +. off settings num p id settings 0 _ builder = return $ builder [] settings n p builder = do rawSetting <- N.peek16 p 0 let k = toSettingsKey rawSetting n' = n - 1 w32 <- N.peek32 p 2 let v = fromIntegral w32 settings n' (p +. 6) (builder . ((k, v) :)) -- | Frame payload decoder for PUSH_PROMISE frame. decodePushPromiseFrame :: FramePayloadDecoder decodePushPromiseFrame header bs = decodeWithPadding header bs $ \bs' -> let (bs0, bs1) = BS.splitAt 4 bs' sid = streamIdentifier (N.word32 bs0) in PushPromiseFrame sid bs1 -- | Frame payload decoder for PING frame. decodePingFrame :: FramePayloadDecoder decodePingFrame _ bs = Right $ PingFrame bs -- | Frame payload decoder for GOAWAY frame. decodeGoAwayFrame :: FramePayloadDecoder decodeGoAwayFrame _ bs = Right $ GoAwayFrame sid ecid bs2 where (bs0, bs1') = BS.splitAt 4 bs (bs1, bs2) = BS.splitAt 4 bs1' sid = streamIdentifier (N.word32 bs0) ecid = toErrorCode (N.word32 bs1) -- | Frame payload decoder for WINDOW_UPDATE frame. decodeWindowUpdateFrame :: FramePayloadDecoder decodeWindowUpdateFrame FrameHeader{..} bs | wsi == 0 = Left $ FrameDecodeError ProtocolError streamId "window update must not be 0" | otherwise = Right $ WindowUpdateFrame wsi where wsi = fromIntegral (N.word32 bs `clearBit` 31) -- | Frame payload decoder for CONTINUATION frame. decodeContinuationFrame :: FramePayloadDecoder decodeContinuationFrame _ bs = Right $ ContinuationFrame bs decodeUnknownFrame :: FrameType -> FramePayloadDecoder decodeUnknownFrame typ _ bs = Right $ UnknownFrame typ bs ---------------------------------------------------------------- checkFrameSize :: FramePayloadDecoder -> FramePayloadDecoder checkFrameSize func header@FrameHeader{..} body | payloadLength > BS.length body = Left $ FrameDecodeError FrameSizeError streamId "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) -> Either FrameDecodeError FramePayload decodeWithPadding FrameHeader{..} bs body | padded = let (w8, rest) = fromMaybe (error "decodeWithPadding") $ BS.uncons bs padlen = intFromWord8 w8 bodylen = payloadLength - padlen - 1 in if bodylen < 0 then Left $ FrameDecodeError ProtocolError streamId "padding is not enough" else Right . body $ BS.take bodylen rest | otherwise = Right $ body bs where padded = testPadded flags streamIdentifier :: Word32 -> StreamId streamIdentifier w32 = clearExclusive $ fromIntegral w32 priority :: ByteString -> Priority priority (PS fptr off _) = unsafeDupablePerformIO $ withForeignPtr fptr $ \ptr -> do let p = ptr +. off w32 <- N.peek32 p 0 let streamdId = streamIdentifier w32 exclusive = testExclusive (fromIntegral w32) -- fixme w8 <- N.peek8 p 4 let weight = intFromWord8 w8 + 1 return $ Priority exclusive streamdId weight intFromWord8 :: Word8 -> Int intFromWord8 = fromIntegral