{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.HTTP2.Types (
frameHeaderLength
, maxPayloadLength
, SettingsKeyId(..)
, checkSettingsList
, fromSettingsKeyId
, SettingsValue
, SettingsList
, toSettingsKeyId
, Settings(..)
, defaultSettings
, updateSettings
, HTTP2Error(..)
, errorCodeId
, ErrorCode
, ErrorCodeId(..)
, fromErrorCodeId
, toErrorCodeId
, FrameType
, minFrameType
, maxFrameType
, FrameTypeId(..)
, fromFrameTypeId
, toFrameTypeId
, Frame(..)
, FrameHeader(..)
, FramePayload(..)
, framePayloadToFrameTypeId
, isPaddingDefined
, StreamId
, isControl
, isRequest
, isResponse
, testExclusive
, setExclusive
, clearExclusive
, FrameFlags
, defaultFlags
, testEndStream
, testAck
, testEndHeader
, testPadded
, testPriority
, setEndStream
, setAck
, setEndHeader
, setPadded
, setPriority
, WindowSize
, defaultInitialWindowSize
, maxWindowSize
, isWindowOverflow
, recommendedConcurrency
, HeaderBlockFragment
, Weight
, Priority(..)
, defaultPriority
, highestPriority
, Padding
) where
import qualified Control.Exception as E
import Data.Bits (setBit, testBit, clearBit)
import Data.ByteString (ByteString)
import Data.List (foldl')
import Data.Maybe (mapMaybe)
import Data.Typeable
import Data.Word (Word8, Word16, Word32)
frameHeaderLength :: Int
frameHeaderLength = 9
type ErrorCode = Word32
data ErrorCodeId = NoError
| ProtocolError
| InternalError
| FlowControlError
| SettingsTimeout
| StreamClosed
| FrameSizeError
| RefusedStream
| Cancel
| CompressionError
| ConnectError
| EnhanceYourCalm
| InadequateSecurity
| HTTP11Required
| UnknownErrorCode ErrorCode
deriving (Show, Read, Eq, Ord)
fromErrorCodeId :: ErrorCodeId -> ErrorCode
fromErrorCodeId NoError = 0x0
fromErrorCodeId ProtocolError = 0x1
fromErrorCodeId InternalError = 0x2
fromErrorCodeId FlowControlError = 0x3
fromErrorCodeId SettingsTimeout = 0x4
fromErrorCodeId StreamClosed = 0x5
fromErrorCodeId FrameSizeError = 0x6
fromErrorCodeId RefusedStream = 0x7
fromErrorCodeId Cancel = 0x8
fromErrorCodeId CompressionError = 0x9
fromErrorCodeId ConnectError = 0xa
fromErrorCodeId EnhanceYourCalm = 0xb
fromErrorCodeId InadequateSecurity = 0xc
fromErrorCodeId HTTP11Required = 0xd
fromErrorCodeId (UnknownErrorCode w) = w
toErrorCodeId :: ErrorCode -> ErrorCodeId
toErrorCodeId 0x0 = NoError
toErrorCodeId 0x1 = ProtocolError
toErrorCodeId 0x2 = InternalError
toErrorCodeId 0x3 = FlowControlError
toErrorCodeId 0x4 = SettingsTimeout
toErrorCodeId 0x5 = StreamClosed
toErrorCodeId 0x6 = FrameSizeError
toErrorCodeId 0x7 = RefusedStream
toErrorCodeId 0x8 = Cancel
toErrorCodeId 0x9 = CompressionError
toErrorCodeId 0xa = ConnectError
toErrorCodeId 0xb = EnhanceYourCalm
toErrorCodeId 0xc = InadequateSecurity
toErrorCodeId 0xd = HTTP11Required
toErrorCodeId w = UnknownErrorCode w
data HTTP2Error = ConnectionError !ErrorCodeId !ByteString
| StreamError !ErrorCodeId !StreamId
deriving (Eq, Show, Typeable, Read)
instance E.Exception HTTP2Error
errorCodeId :: HTTP2Error -> ErrorCodeId
errorCodeId (ConnectionError err _) = err
errorCodeId (StreamError err _) = err
data SettingsKeyId = SettingsHeaderTableSize
| SettingsEnablePush
| SettingsMaxConcurrentStreams
| SettingsInitialWindowSize
| SettingsMaxFrameSize
| SettingsMaxHeaderBlockSize
deriving (Show, Read, Eq, Ord, Enum, Bounded)
type SettingsValue = Int
fromSettingsKeyId :: SettingsKeyId -> Word16
fromSettingsKeyId x = fromIntegral (fromEnum x) + 1
minSettingsKeyId :: Word16
minSettingsKeyId = fromIntegral $ fromEnum (minBound :: SettingsKeyId)
maxSettingsKeyId :: Word16
maxSettingsKeyId = fromIntegral $ fromEnum (maxBound :: SettingsKeyId)
toSettingsKeyId :: Word16 -> Maybe SettingsKeyId
toSettingsKeyId x
| minSettingsKeyId <= n && n <= maxSettingsKeyId = Just . toEnum . fromIntegral $ n
| otherwise = Nothing
where
n = x - 1
type SettingsList = [(SettingsKeyId,SettingsValue)]
checkSettingsList :: SettingsList -> Maybe HTTP2Error
checkSettingsList settings = case mapMaybe checkSettingsValue settings of
[] -> Nothing
(x:_) -> Just x
checkSettingsValue :: (SettingsKeyId,SettingsValue) -> Maybe HTTP2Error
checkSettingsValue (SettingsEnablePush,v)
| v /= 0 && v /= 1 = Just $ ConnectionError ProtocolError "enable push must be 0 or 1"
checkSettingsValue (SettingsInitialWindowSize,v)
| v > 2147483647 = Just $ ConnectionError FlowControlError "Window size must be less than or equal to 65535"
checkSettingsValue (SettingsMaxFrameSize,v)
| v < 16384 || v > 16777215 = Just $ ConnectionError ProtocolError "Max frame size must be in between 16384 and 16777215"
checkSettingsValue _ = Nothing
data Settings = Settings {
headerTableSize :: !Int
, enablePush :: !Bool
, maxConcurrentStreams :: !(Maybe Int)
, initialWindowSize :: !WindowSize
, maxFrameSize :: !Int
, maxHeaderBlockSize :: !(Maybe Int)
} deriving (Show)
defaultSettings :: Settings
defaultSettings = Settings {
headerTableSize = 4096
, enablePush = True
, maxConcurrentStreams = Nothing
, initialWindowSize = defaultInitialWindowSize
, maxFrameSize = 16384
, maxHeaderBlockSize = Nothing
}
updateSettings :: Settings -> SettingsList -> Settings
updateSettings settings kvs = foldl' update settings kvs
where
update def (SettingsHeaderTableSize,x) = def { headerTableSize = x }
update def (SettingsEnablePush,x) = def { enablePush = x > 0 }
update def (SettingsMaxConcurrentStreams,x) = def { maxConcurrentStreams = Just x }
update def (SettingsInitialWindowSize,x) = def { initialWindowSize = x }
update def (SettingsMaxFrameSize,x) = def { maxFrameSize = x }
update def (SettingsMaxHeaderBlockSize,x) = def { maxHeaderBlockSize = Just x }
type WindowSize = Int
defaultInitialWindowSize :: WindowSize
defaultInitialWindowSize = 65535
maxWindowSize :: WindowSize
maxWindowSize = 2147483647
isWindowOverflow :: WindowSize -> Bool
isWindowOverflow w = testBit w 31
recommendedConcurrency :: Int
recommendedConcurrency = 100
type Weight = Int
data Priority = Priority {
exclusive :: !Bool
, streamDependency :: !StreamId
, weight :: !Weight
} deriving (Show, Read, Eq)
defaultPriority :: Priority
defaultPriority = Priority False 0 16
highestPriority :: Priority
highestPriority = Priority False 0 256
type FrameType = Word8
minFrameType :: FrameType
minFrameType = 0
maxFrameType :: FrameType
maxFrameType = 9
data FrameTypeId = FrameData
| FrameHeaders
| FramePriority
| FrameRSTStream
| FrameSettings
| FramePushPromise
| FramePing
| FrameGoAway
| FrameWindowUpdate
| FrameContinuation
| FrameUnknown FrameType
deriving (Show, Eq, Ord)
fromFrameTypeId :: FrameTypeId -> FrameType
fromFrameTypeId FrameData = 0
fromFrameTypeId FrameHeaders = 1
fromFrameTypeId FramePriority = 2
fromFrameTypeId FrameRSTStream = 3
fromFrameTypeId FrameSettings = 4
fromFrameTypeId FramePushPromise = 5
fromFrameTypeId FramePing = 6
fromFrameTypeId FrameGoAway = 7
fromFrameTypeId FrameWindowUpdate = 8
fromFrameTypeId FrameContinuation = 9
fromFrameTypeId (FrameUnknown x) = x
toFrameTypeId :: FrameType -> FrameTypeId
toFrameTypeId 0 = FrameData
toFrameTypeId 1 = FrameHeaders
toFrameTypeId 2 = FramePriority
toFrameTypeId 3 = FrameRSTStream
toFrameTypeId 4 = FrameSettings
toFrameTypeId 5 = FramePushPromise
toFrameTypeId 6 = FramePing
toFrameTypeId 7 = FrameGoAway
toFrameTypeId 8 = FrameWindowUpdate
toFrameTypeId 9 = FrameContinuation
toFrameTypeId x = FrameUnknown x
maxPayloadLength :: Int
maxPayloadLength = 2^(14::Int)
type FrameFlags = Word8
defaultFlags :: FrameFlags
defaultFlags = 0
testEndStream :: FrameFlags -> Bool
testEndStream x = x `testBit` 0
testAck :: FrameFlags -> Bool
testAck x = x `testBit` 0
testEndHeader :: FrameFlags -> Bool
testEndHeader x = x `testBit` 2
testPadded :: FrameFlags -> Bool
testPadded x = x `testBit` 3
testPriority :: FrameFlags -> Bool
testPriority x = x `testBit` 5
setEndStream :: FrameFlags -> FrameFlags
setEndStream x = x `setBit` 0
setAck :: FrameFlags -> FrameFlags
setAck x = x `setBit` 0
setEndHeader :: FrameFlags -> FrameFlags
setEndHeader x = x `setBit` 2
setPadded :: FrameFlags -> FrameFlags
setPadded x = x `setBit` 3
setPriority :: FrameFlags -> FrameFlags
setPriority x = x `setBit` 5
type StreamId = Int
isControl :: StreamId -> Bool
isControl 0 = True
isControl _ = False
isRequest :: StreamId -> Bool
isRequest = odd
isResponse :: StreamId -> Bool
isResponse 0 = False
isResponse n = even n
testExclusive :: StreamId -> Bool
testExclusive n = n `testBit` 31
setExclusive :: StreamId -> StreamId
setExclusive n = n `setBit` 31
clearExclusive :: StreamId -> StreamId
clearExclusive n = n `clearBit` 31
type HeaderBlockFragment = ByteString
type Padding = ByteString
data Frame = Frame
{ frameHeader :: !FrameHeader
, framePayload :: !FramePayload
} deriving (Show, Read, Eq)
data FrameHeader = FrameHeader
{ payloadLength :: !Int
, flags :: !FrameFlags
, streamId :: !StreamId
} deriving (Show, Read, Eq)
data FramePayload =
DataFrame !ByteString
| HeadersFrame !(Maybe Priority) !HeaderBlockFragment
| PriorityFrame !Priority
| RSTStreamFrame !ErrorCodeId
| SettingsFrame !SettingsList
| PushPromiseFrame !StreamId !HeaderBlockFragment
| PingFrame !ByteString
| GoAwayFrame !StreamId !ErrorCodeId !ByteString
| WindowUpdateFrame !WindowSize
| ContinuationFrame !HeaderBlockFragment
| UnknownFrame !FrameType !ByteString
deriving (Show, Read, Eq)
framePayloadToFrameTypeId :: FramePayload -> FrameTypeId
framePayloadToFrameTypeId (DataFrame _) = FrameData
framePayloadToFrameTypeId (HeadersFrame _ _) = FrameHeaders
framePayloadToFrameTypeId (PriorityFrame _) = FramePriority
framePayloadToFrameTypeId (RSTStreamFrame _) = FrameRSTStream
framePayloadToFrameTypeId (SettingsFrame _) = FrameSettings
framePayloadToFrameTypeId (PushPromiseFrame _ _) = FramePushPromise
framePayloadToFrameTypeId (PingFrame _) = FramePing
framePayloadToFrameTypeId (GoAwayFrame _ _ _) = FrameGoAway
framePayloadToFrameTypeId (WindowUpdateFrame _) = FrameWindowUpdate
framePayloadToFrameTypeId (ContinuationFrame _) = FrameContinuation
framePayloadToFrameTypeId (UnknownFrame w8 _) = FrameUnknown w8
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