This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
711
unikernel/duniverse/ocaml-h2/lib/parse.ml
Normal file
711
unikernel/duniverse/ocaml-h2/lib/parse.ml
Normal file
|
|
@ -0,0 +1,711 @@
|
|||
(*----------------------------------------------------------------------------
|
||||
* Copyright (c) 2017 Inhabited Type LLC.
|
||||
* Copyright (c) 2019 Antonio N. Monteiro.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the author nor the names of his contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
|
||||
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*---------------------------------------------------------------------------*)
|
||||
|
||||
open Angstrom
|
||||
|
||||
(* We use the tail-recursive variant of `skip_many` from
|
||||
* https://github.com/inhabitedtype/angstrom/pull/219 to avoid memory leaks in
|
||||
* long-running connections. The original `skip_many` can build up a list of
|
||||
* error handlers that may never be released. *)
|
||||
let skip_many p =
|
||||
fix (fun m ->
|
||||
p >>| (fun _ -> true) <|> return false >>= function
|
||||
| true -> m
|
||||
| false -> return ())
|
||||
|
||||
let default_frame_header =
|
||||
{ Frame.payload_length = 0
|
||||
; flags = Flags.default_flags
|
||||
; stream_id = -1l
|
||||
; frame_type = Unknown (-1)
|
||||
}
|
||||
|
||||
type parse_context =
|
||||
{ mutable frame_header : Frame.frame_header
|
||||
; mutable remaining_bytes_to_skip : int
|
||||
; mutable did_report_stream_error : bool
|
||||
; (* TODO: This should change as new settings frames arrive, but we don't yet
|
||||
* resize the read buffer. *)
|
||||
max_frame_size : int
|
||||
}
|
||||
|
||||
let connection_error error_code msg =
|
||||
Error Error.(ConnectionError (error_code, msg))
|
||||
|
||||
let stream_error error_code stream_id =
|
||||
Error Error.(StreamError (stream_id, error_code))
|
||||
|
||||
let parse_uint24 o1 o2 o3 = (o1 lsl 16) lor (o2 lsl 8) lor o3
|
||||
|
||||
let frame_length =
|
||||
(* From RFC7540§4.1:
|
||||
* Length: The length of the frame payload expressed as an unsigned 24-bit
|
||||
* integer. *)
|
||||
lift3 parse_uint24 any_uint8 any_uint8 any_uint8
|
||||
|
||||
let frame_type =
|
||||
(* From RFC7540§4.1:
|
||||
* Type: The 8-bit type of the frame. The frame type determines the format
|
||||
* and semantics of the frame. Implementations MUST ignore and discard any
|
||||
* frame that has a type that is unknown. *)
|
||||
lift Frame.FrameType.parse any_uint8
|
||||
|
||||
let flags =
|
||||
(* From RFC7540§4.1:
|
||||
* Flags: An 8-bit field reserved for boolean flags specific to the frame
|
||||
* type. *)
|
||||
any_uint8
|
||||
|
||||
let parse_stream_identifier n =
|
||||
(* From RFC7540§4.1:
|
||||
* Stream Identifier: A stream identifier (see Section 5.1.1) expressed as
|
||||
* an unsigned 31-bit integer. The value 0x0 is reserved for frames that
|
||||
* are associated with the connection as a whole as opposed to an
|
||||
* individual stream. *)
|
||||
Int32.(logand n (sub (shift_left 1l 31) 1l))
|
||||
|
||||
let stream_identifier = lift parse_stream_identifier BE.any_int32
|
||||
|
||||
let parse_frame_header =
|
||||
lift4
|
||||
(fun payload_length frame_type flags stream_id ->
|
||||
{ Frame.flags; payload_length; stream_id; frame_type })
|
||||
frame_length
|
||||
frame_type
|
||||
flags
|
||||
stream_identifier
|
||||
<?> "frame_header"
|
||||
(* The parser commits after parsing the frame header so that the entire
|
||||
* underlying buffer can be used to store the payload length. This matters
|
||||
* because the size of the buffer that gets allocated is the maximum frame
|
||||
* payload negotiated by the HTTP/2 settings synchronization. The 9 octets
|
||||
* that make up the frame header are, therefore, very important in order for
|
||||
* h2 not to return a FRAME_SIZE_ERROR. *)
|
||||
<* commit
|
||||
|
||||
let parse_padded_payload { Frame.payload_length; flags; _ } parser =
|
||||
if Flags.test_padded flags
|
||||
then
|
||||
any_uint8 >>= fun pad_length ->
|
||||
(* From RFC7540§6.1:
|
||||
* Pad Length: An 8-bit field containing the length of the frame
|
||||
* padding in units of octets.
|
||||
*
|
||||
* Data: Application data. The amount of data is the remainder of the
|
||||
* frame payload after subtracting the length of the other fields that
|
||||
* are present.
|
||||
*
|
||||
* Padding: Padding octets that contain no application semantic
|
||||
* value. *)
|
||||
if pad_length >= payload_length
|
||||
then
|
||||
(* From RFC7540§6.1:
|
||||
* If the length of the padding is the length of the frame payload or
|
||||
* greater, the recipient MUST treat this as a connection error
|
||||
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance (payload_length - 1) >>| fun () ->
|
||||
connection_error ProtocolError "Padding size exceeds payload size"
|
||||
else
|
||||
(* Subtract the octet that contains the length of padding, and the
|
||||
* padding octets. *)
|
||||
let relevant_length = payload_length - 1 - pad_length in
|
||||
parser relevant_length <* advance pad_length
|
||||
else parser payload_length
|
||||
|
||||
let parse_data_frame ({ Frame.stream_id; payload_length; _ } as frame_header) =
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.1:
|
||||
* DATA frames MUST be associated with a stream. If a DATA frame is
|
||||
* received whose stream identifier field is 0x0, the recipient MUST
|
||||
* respond with a connection error (Section 5.4.1) of type
|
||||
* PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
ProtocolError
|
||||
"Data frames must be associated with a stream"
|
||||
else
|
||||
let parse_data length =
|
||||
lift (fun bs -> Ok (Frame.Data bs)) (take_bigstring length)
|
||||
in
|
||||
parse_padded_payload frame_header parse_data
|
||||
|
||||
let parse_priority =
|
||||
lift2
|
||||
(fun stream_dependency weight ->
|
||||
let e = Priority.test_exclusive stream_dependency in
|
||||
{ Priority.exclusive =
|
||||
e
|
||||
(* From RFC7540§6.3:
|
||||
* An unsigned 8-bit integer representing a priority weight for the
|
||||
* stream (see Section 5.3). Add one to the value to obtain a
|
||||
* weight between 1 and 256. *)
|
||||
; weight = weight + 1
|
||||
; stream_dependency = parse_stream_identifier stream_dependency
|
||||
})
|
||||
BE.any_int32
|
||||
any_uint8
|
||||
|
||||
let parse_headers_frame frame_header =
|
||||
let { Frame.payload_length; stream_id; flags; _ } = frame_header in
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.2:
|
||||
* HEADERS frames MUST be associated with a stream. If a HEADERS frame is
|
||||
* received whose stream identifier field is 0x0, the recipient MUST
|
||||
* respond with a connection error (Section 5.4.1) of type
|
||||
* PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "HEADERS must be associated with a stream"
|
||||
else
|
||||
let parse_headers length =
|
||||
if Flags.test_priority flags
|
||||
then
|
||||
lift2
|
||||
(fun priority headers -> Ok (Frame.Headers (priority, headers)))
|
||||
parse_priority
|
||||
(* See RFC7540§6.3:
|
||||
* Stream Dependency (4 octets) + Weight (1 octet). *)
|
||||
(take_bigstring (length - 5))
|
||||
else
|
||||
lift
|
||||
(fun headers_block ->
|
||||
Ok (Frame.Headers (Priority.default_priority, headers_block)))
|
||||
(take_bigstring length)
|
||||
in
|
||||
parse_padded_payload frame_header parse_headers
|
||||
|
||||
let parse_priority_frame { Frame.payload_length; stream_id; _ } =
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.3:
|
||||
* The PRIORITY frame always identifies a stream. If a PRIORITY frame is
|
||||
* received with a stream identifier of 0x0, the recipient MUST respond
|
||||
* with a connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "PRIORITY must be associated with a stream"
|
||||
else if payload_length <> 5
|
||||
then
|
||||
(* From RFC7540§6.3:
|
||||
* A PRIORITY frame with a length other than 5 octets MUST be treated as
|
||||
* a stream error (Section 5.4.2) of type FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () -> stream_error FrameSizeError stream_id
|
||||
else lift (fun priority -> Ok (Frame.Priority priority)) parse_priority
|
||||
|
||||
let parse_error_code = lift Error_code.parse BE.any_int32
|
||||
|
||||
let parse_rst_stream_frame { Frame.payload_length; stream_id; _ } =
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.4:
|
||||
* RST_STREAM frames MUST be associated with a stream. If a RST_STREAM
|
||||
* frame is received with a stream identifier of 0x0, the recipient MUST
|
||||
* treat this as a connection error (Section 5.4.1) of type
|
||||
* PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "RST_STREAM must be associated with a stream"
|
||||
else if payload_length <> 4
|
||||
then
|
||||
(* From RFC7540§6.4:
|
||||
* A RST_STREAM frame with a length other than 4 octets MUST be treated
|
||||
* as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
FrameSizeError
|
||||
"RST_STREAM payload must be 4 octets in length"
|
||||
else lift (fun error_code -> Ok (Frame.RSTStream error_code)) parse_error_code
|
||||
|
||||
let parse_settings_frame { Frame.payload_length; stream_id; flags; _ } =
|
||||
if not (Stream_identifier.is_connection stream_id)
|
||||
then
|
||||
(* From RFC7540§6.5:
|
||||
* If an endpoint receives a SETTINGS frame whose stream identifier field
|
||||
* is anything other than 0x0, the endpoint MUST respond with a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
ProtocolError
|
||||
"SETTINGS must be associated with stream id 0x0"
|
||||
else if payload_length mod 6 <> 0
|
||||
then
|
||||
(* From RFC7540§6.5:
|
||||
* A SETTINGS frame with a length other than a multiple of 6 octets MUST
|
||||
* be treated as a connection error (Section 5.4.1) of type
|
||||
* FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
FrameSizeError
|
||||
"SETTINGS payload size must be a multiple of 6"
|
||||
else if Flags.test_ack flags && payload_length <> 0
|
||||
then
|
||||
(* From RFC7540§6.5:
|
||||
* Receipt of a SETTINGS frame with the ACK flag set and a length field
|
||||
* value other than 0 MUST be treated as a connection error
|
||||
* (Section 5.4.1) of type FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error FrameSizeError "SETTINGS with ACK must be empty"
|
||||
else
|
||||
let num_settings = payload_length / Settings.octets_per_setting in
|
||||
Settings.parse_settings_payload num_settings >>| fun xs ->
|
||||
Ok (Frame.Settings xs)
|
||||
|
||||
let parse_push_promise_frame frame_header =
|
||||
let { Frame.payload_length; stream_id; _ } = frame_header in
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.6:
|
||||
* The stream identifier of a PUSH_PROMISE frame indicates the
|
||||
* stream it is associated with. If the stream identifier field
|
||||
* specifies the value 0x0, a recipient MUST respond with a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "PUSH must be associated with a stream"
|
||||
else
|
||||
let parse_push_promise length =
|
||||
lift2
|
||||
(fun promised_stream_id fragment ->
|
||||
if Stream_identifier.is_connection promised_stream_id
|
||||
then
|
||||
(* From RFC7540§6.6:
|
||||
* A receiver MUST treat the receipt of a PUSH_PROMISE that
|
||||
* promises an illegal stream identifier (Section 5.1.1) as a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
connection_error
|
||||
ProtocolError
|
||||
"PUSH must not promise stream id 0x0"
|
||||
else if Stream_identifier.is_request promised_stream_id
|
||||
then
|
||||
(* From RFC7540§6.6:
|
||||
* A receiver MUST treat the receipt of a PUSH_PROMISE that
|
||||
* promises an illegal stream identifier (Section 5.1.1) as a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
||||
*
|
||||
* Note: An odd-numbered stream is an invalid stream identifier for
|
||||
* the server, and only the server can send PUSH_PROMISE frames:
|
||||
*
|
||||
* From RFC7540§8.2.1:
|
||||
* PUSH_PROMISE frames MUST NOT be sent by the client. *)
|
||||
connection_error
|
||||
ProtocolError
|
||||
"PUSH must be associated with an even-numbered stream id"
|
||||
else Ok Frame.(PushPromise (promised_stream_id, fragment)))
|
||||
stream_identifier
|
||||
(* From RFC7540§6.6:
|
||||
* The PUSH_PROMISE frame includes the unsigned 31-bit identifier of
|
||||
* the stream the endpoint plans to create along with a set of
|
||||
* headers that provide additional context for the stream. *)
|
||||
(take_bigstring (length - 4))
|
||||
in
|
||||
parse_padded_payload frame_header parse_push_promise
|
||||
|
||||
let parse_ping_frame { Frame.payload_length; stream_id; _ } =
|
||||
if not (Stream_identifier.is_connection stream_id)
|
||||
then
|
||||
(* From RFC7540§6.7:
|
||||
* PING frames are not associated with any individual stream. If a PING
|
||||
* frame is received with a stream identifier field value other than
|
||||
* 0x0, the recipient MUST respond with a connection error
|
||||
* (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error ProtocolError "PING must be associated with stream id 0x0"
|
||||
else if payload_length <> 8
|
||||
then
|
||||
(* From RFC7540§6.7:
|
||||
* Receipt of a PING frame with a length field value other than 8 MUST
|
||||
* be treated as a connection error (Section 5.4.1) of type
|
||||
* FRAME_SIZE_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error FrameSizeError "PING payload must be 8 octets in length"
|
||||
else lift (fun bs -> Ok (Frame.Ping bs)) (take_bigstring payload_length)
|
||||
|
||||
let parse_go_away_frame { Frame.payload_length; stream_id; _ } =
|
||||
if not (Stream_identifier.is_connection stream_id)
|
||||
then
|
||||
(* From RFC7540§6.8:
|
||||
* The GOAWAY frame applies to the connection, not a specific stream. An
|
||||
* endpoint MUST treat a GOAWAY frame with a stream identifier other than
|
||||
* 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
ProtocolError
|
||||
"GOAWAY must be associated with stream id 0x0"
|
||||
else
|
||||
lift3
|
||||
(fun last_stream_id err debug_data ->
|
||||
Ok (Frame.GoAway (last_stream_id, err, debug_data)))
|
||||
stream_identifier
|
||||
parse_error_code
|
||||
(take_bigstring (payload_length - 8))
|
||||
|
||||
let parse_window_update_frame { Frame.stream_id; payload_length; _ } =
|
||||
(* From RFC7540§6.9:
|
||||
* A WINDOW_UPDATE frame with a length other than 4 octets MUST be treated
|
||||
* as a connection error (Section 5.4.1) of type FRAME_SIZE_ERROR. *)
|
||||
if payload_length <> 4
|
||||
then
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
FrameSizeError
|
||||
"WINDOW_UPDATE payload must be 4 octets in length"
|
||||
else
|
||||
lift
|
||||
(fun uint ->
|
||||
(* From RFC7540§6.9:
|
||||
* The frame payload of a WINDOW_UPDATE frame is one reserved bit
|
||||
* plus an unsigned 31-bit integer indicating the number of octets
|
||||
* that the sender can transmit in addition to the existing
|
||||
* flow-control window. *)
|
||||
let window_size_increment = Util.clear_bit_int32 uint 31 in
|
||||
if Int32.equal window_size_increment 0l
|
||||
then
|
||||
if (* From RFC7540§6.9:
|
||||
* A receiver MUST treat the receipt of a WINDOW_UPDATE frame
|
||||
* with an flow-control window increment of 0 as a stream error
|
||||
* (Section 5.4.2) of type PROTOCOL_ERROR; errors on the
|
||||
* connection flow-control window MUST be treated as a connection
|
||||
* error (Section 5.4.1). *)
|
||||
Stream_identifier.is_connection stream_id
|
||||
then connection_error ProtocolError "Window update must not be 0"
|
||||
else stream_error ProtocolError stream_id
|
||||
else Ok (Frame.WindowUpdate window_size_increment))
|
||||
BE.any_int32
|
||||
|
||||
let parse_continuation_frame { Frame.payload_length; stream_id; _ } =
|
||||
if Stream_identifier.is_connection stream_id
|
||||
then
|
||||
(* From RFC7540§6.10:
|
||||
* CONTINUATION frames MUST be associated with a stream. If a
|
||||
* CONTINUATION frame is received whose stream identifier field is 0x0,
|
||||
* the recipient MUST respond with a connection error (Section 5.4.1) of
|
||||
* type PROTOCOL_ERROR. *)
|
||||
advance payload_length >>| fun () ->
|
||||
connection_error
|
||||
ProtocolError
|
||||
"CONTINUATION must be associated with a stream"
|
||||
else
|
||||
lift
|
||||
(fun block_fragment -> Ok (Frame.Continuation block_fragment))
|
||||
(take_bigstring payload_length)
|
||||
|
||||
let parse_unknown_frame typ { Frame.payload_length; _ } =
|
||||
lift
|
||||
(fun bigstring -> Ok (Frame.Unknown (typ, bigstring)))
|
||||
(take_bigstring payload_length)
|
||||
|
||||
let parse_frame_payload ({ Frame.frame_type; _ } as frame_header) =
|
||||
(match frame_type with
|
||||
| Frame.FrameType.Data -> parse_data_frame frame_header
|
||||
| Headers -> parse_headers_frame frame_header
|
||||
| Priority -> parse_priority_frame frame_header
|
||||
| RSTStream -> parse_rst_stream_frame frame_header
|
||||
| Settings -> parse_settings_frame frame_header
|
||||
| PushPromise -> parse_push_promise_frame frame_header
|
||||
| Ping -> parse_ping_frame frame_header
|
||||
| GoAway -> parse_go_away_frame frame_header
|
||||
| WindowUpdate -> parse_window_update_frame frame_header
|
||||
| Continuation -> parse_continuation_frame frame_header
|
||||
| Unknown typ -> parse_unknown_frame typ frame_header)
|
||||
<?> "frame_payload"
|
||||
|
||||
let parse_frame parse_context =
|
||||
parse_frame_header >>= fun ({ Frame.payload_length; _ } as frame_header) ->
|
||||
(* If we're parsing a new frame, we didn't yet send a stream error on it *)
|
||||
parse_context.did_report_stream_error <- false;
|
||||
parse_context.frame_header <- frame_header;
|
||||
(* h2 does unbuffered parsing and the bigarray we read input from is
|
||||
* allocated based on the maximum frame payload negotiated by HTTP/2
|
||||
* communication. If the underlying buffer is smaller than what
|
||||
* the frame can fit, we want to skip the remaining input and skip to the
|
||||
* next frame.
|
||||
*
|
||||
* From RFC7540§5.4.2:
|
||||
* A stream error is an error related to a specific stream that does
|
||||
* not affect processing of other streams. *)
|
||||
let is_frame_size_error = payload_length > parse_context.max_frame_size in
|
||||
if is_frame_size_error
|
||||
then
|
||||
parse_context.remaining_bytes_to_skip <-
|
||||
parse_context.remaining_bytes_to_skip + payload_length;
|
||||
lift
|
||||
(function
|
||||
| Ok frame_payload -> Ok { Frame.frame_header; frame_payload }
|
||||
| Error e -> Error e)
|
||||
(parse_frame_payload frame_header)
|
||||
|
||||
(* This is the client connection preface. *)
|
||||
let connection_preface =
|
||||
(* From RFC7540§3.5:
|
||||
* In HTTP/2, each endpoint is required to send a connection preface as a
|
||||
* final confirmation of the protocol in use and to establish the initial
|
||||
* settings for the HTTP/2 connection. *)
|
||||
string Frame.connection_preface <?> "connection preface"
|
||||
|
||||
module Reader = struct
|
||||
module AU = Angstrom.Unbuffered
|
||||
|
||||
type parse_error =
|
||||
(* Parse error reported by Angstrom *)
|
||||
[ `Parse of string list * string
|
||||
| (* Full error information *)
|
||||
`Error of Error.t
|
||||
| (* Just the error code, need to puzzle back connection or stream info *)
|
||||
`Error_code of
|
||||
Error_code.t
|
||||
]
|
||||
|
||||
type 'error parse_state =
|
||||
| Initial
|
||||
| Fail of 'error
|
||||
| Partial of
|
||||
(Bigstringaf.t
|
||||
-> off:int
|
||||
-> len:int
|
||||
-> AU.more
|
||||
-> (unit, 'error) result AU.state)
|
||||
|
||||
type 'error t =
|
||||
{ parser : (unit, 'error) result Angstrom.t
|
||||
; mutable parse_state : 'error parse_state
|
||||
(* The state of the parse for the current request *)
|
||||
; mutable closed : bool
|
||||
(* Whether the input source has left the building, indicating that no
|
||||
further input will be received. *)
|
||||
; parse_context : parse_context
|
||||
(* The current stream identifier being processed, in order to discern
|
||||
whether the error that needs to be assembled is a stream or connection
|
||||
error. *)
|
||||
}
|
||||
|
||||
type frame = parse_error t
|
||||
|
||||
let create parser parse_context =
|
||||
{ parser; parse_state = Initial; closed = false; parse_context }
|
||||
|
||||
let create_parse_context max_frame_size =
|
||||
{ frame_header = default_frame_header
|
||||
; remaining_bytes_to_skip = 0
|
||||
; did_report_stream_error = false
|
||||
; max_frame_size
|
||||
}
|
||||
|
||||
let settings_preface parse_context =
|
||||
(* From RFC7540§3.5:
|
||||
* [...] the connection preface starts with the string
|
||||
* PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n). This sequence MUST be followed by
|
||||
* a SETTINGS frame (Section 6.5), which MAY be empty. *)
|
||||
parse_frame parse_context >>| function
|
||||
| Ok ({ frame_payload = Frame.Settings settings_list; _ } as frame) ->
|
||||
Ok (frame, settings_list)
|
||||
| Ok { frame_payload = Frame.GoAway (_, error_code, debug_data); _ } ->
|
||||
(* From RFC7540§9.2.1:
|
||||
* An endpoint MAY immediately terminate an HTTP/2 connection that does
|
||||
* not meet these TLS requirements with a connection error (Section
|
||||
* 5.4.1) of type INADEQUATE_SECURITY.
|
||||
*
|
||||
* Note: we are liberal on purpose in this branch instead of only
|
||||
* accepting an error of type `INADEQUATE_SECURITY`. If an endpoint is
|
||||
* sending us a `GOAWAY` frame we probably did something wrong and
|
||||
* deserve to know what that is. *)
|
||||
Error
|
||||
(`Error
|
||||
Error.(ConnectionError (error_code, Bigstringaf.to_string debug_data)))
|
||||
| Ok _ ->
|
||||
(* From RFC7540§3.5:
|
||||
* Clients and servers MUST treat an invalid connection preface as a
|
||||
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. A GOAWAY
|
||||
* frame (Section 6.8) MAY be omitted in this case, since an invalid
|
||||
* preface indicates that the peer is not using HTTP/2. *)
|
||||
Error
|
||||
(`Error
|
||||
Error.(ConnectionError (ProtocolError, "Invalid connection preface")))
|
||||
| Error e -> Error (`Error e)
|
||||
|
||||
let connection_preface_and_frames
|
||||
~max_frame_size
|
||||
preface_parser
|
||||
preface_handler
|
||||
frame_handler
|
||||
=
|
||||
let parse_context = create_parse_context max_frame_size in
|
||||
let parser =
|
||||
preface_parser parse_context <* commit >>= function
|
||||
| Ok (frame, settings_list) ->
|
||||
preface_handler frame settings_list;
|
||||
(* After having received a valid connection preface, we can start
|
||||
* reading other frames now. *)
|
||||
skip_many (parse_frame parse_context <* commit >>| frame_handler)
|
||||
>>| fun () -> Ok ()
|
||||
| Error _ as error -> return error
|
||||
in
|
||||
create parser parse_context
|
||||
|
||||
let client_frames preface_handler frame_handler =
|
||||
connection_preface_and_frames
|
||||
(* From RFC7540§3.5:
|
||||
* The server connection preface consists of a potentially empty
|
||||
* SETTINGS frame (Section 6.5) that MUST be the first frame the server
|
||||
* sends in the HTTP/2 connection. *)
|
||||
settings_preface
|
||||
preface_handler
|
||||
frame_handler
|
||||
|
||||
let server_frames ~max_frame_size preface_handler frame_handler =
|
||||
connection_preface_and_frames
|
||||
~max_frame_size
|
||||
(fun parse_context ->
|
||||
(* From RFC7540§3.5:
|
||||
* The client connection preface starts with a sequence of 24 octets,
|
||||
* which in hex notation is:
|
||||
*
|
||||
* 0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
|
||||
* That is, the connection preface starts with the string
|
||||
* PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n). This sequence MUST be followed
|
||||
* by a SETTINGS frame (Section 6.5), which MAY be empty. *)
|
||||
connection_preface *> settings_preface parse_context)
|
||||
preface_handler
|
||||
frame_handler
|
||||
|
||||
let is_closed t = t.closed
|
||||
|
||||
let transition t state =
|
||||
match state with
|
||||
| AU.Done (consumed, Ok ()) ->
|
||||
t.parse_state <- Initial;
|
||||
consumed
|
||||
| Done (consumed, Error error) ->
|
||||
t.parse_state <- Fail error;
|
||||
consumed
|
||||
| Fail (consumed, marks, msg) ->
|
||||
t.parse_state <- Fail (`Parse (marks, msg));
|
||||
consumed
|
||||
| Partial { committed; continue } ->
|
||||
(* If we have bytes to skip over then it means we've spotted a
|
||||
* FRAME_SIZE_ERROR, a case where, due to our unbuffered parsing, the
|
||||
* payload length declared in a frame header is larger than the
|
||||
* underlying buffer can fit. *)
|
||||
if t.parse_context.remaining_bytes_to_skip > 0
|
||||
then t.parse_state <- Fail (`Error_code Error_code.FrameSizeError)
|
||||
else t.parse_state <- Partial continue;
|
||||
committed
|
||||
|
||||
let start t state =
|
||||
match state with
|
||||
| AU.Done _ -> failwith "h2.Parse.Reader.unable to start parser"
|
||||
| Fail (0, marks, msg) -> t.parse_state <- Fail (`Parse (marks, msg))
|
||||
| Partial { committed = 0; continue } -> t.parse_state <- Partial continue
|
||||
| Partial _ | Fail _ -> assert false
|
||||
|
||||
let rec read_with_more t bs ~off ~len more =
|
||||
let consumed =
|
||||
match t.parse_state with
|
||||
| Fail _ ->
|
||||
let parser_ctx = t.parse_context in
|
||||
let remaining_bytes = parser_ctx.remaining_bytes_to_skip in
|
||||
(* Just skip input if we need to *)
|
||||
if remaining_bytes > 0
|
||||
then (
|
||||
assert (remaining_bytes >= len);
|
||||
let remaining_bytes' = remaining_bytes - len in
|
||||
parser_ctx.remaining_bytes_to_skip <- remaining_bytes';
|
||||
assert (remaining_bytes' >= 0);
|
||||
if remaining_bytes' = 0
|
||||
then
|
||||
(* Reset the parser state to `Done` so that we can read the next
|
||||
* frame (after skipping through the bad input) *)
|
||||
t.parse_state <- Initial;
|
||||
len)
|
||||
else 0
|
||||
| Initial ->
|
||||
start t (AU.parse t.parser);
|
||||
read_with_more t bs ~off ~len more
|
||||
| Partial continue -> transition t (continue bs more ~off ~len)
|
||||
in
|
||||
(match more with Complete -> t.closed <- true | Incomplete -> ());
|
||||
consumed
|
||||
|
||||
let force_close t = t.closed <- true
|
||||
let fail_to_string marks err = String.concat " > " marks ^ ": " ^ err
|
||||
|
||||
let next_from_error t ?(msg = "") error_code =
|
||||
if t.parse_context.frame_header == default_frame_header
|
||||
then `Error Error.(ConnectionError (error_code, msg))
|
||||
else
|
||||
match t.parse_context, error_code with
|
||||
| ( { frame_header =
|
||||
{ frame_type =
|
||||
Headers | PushPromise | Continuation | Settings | Unknown _
|
||||
; _
|
||||
}
|
||||
; _
|
||||
}
|
||||
, Error_code.FrameSizeError )
|
||||
| { frame_header = { Frame.stream_id = 0x0l; _ }; _ }, _ ->
|
||||
(* From RFC7540§4.2:
|
||||
* A frame size error in a frame that could alter the state of the
|
||||
* entire connection MUST be treated as a connection error (Section
|
||||
* 5.4.1); this includes any frame carrying a header block (Section
|
||||
* 4.3) (that is, HEADERS, PUSH_PROMISE, and CONTINUATION), SETTINGS,
|
||||
* and any frame with a stream identifier of 0. *)
|
||||
`Error Error.(ConnectionError (error_code, msg))
|
||||
| { did_report_stream_error = true; _ }, _ ->
|
||||
(* If the parser is in a `Fail` state and would report a stream error,
|
||||
* just issue a `Read` operation if we've already reported that error. *)
|
||||
if t.closed then `Close else `Read
|
||||
| { frame_header = { Frame.stream_id; _ }; _ }, _ ->
|
||||
t.parse_context.did_report_stream_error <- true;
|
||||
`Error Error.(StreamError (stream_id, error_code))
|
||||
|
||||
let next t =
|
||||
match t.parse_state with
|
||||
| Fail error ->
|
||||
(match error with
|
||||
| `Error e -> `Error e
|
||||
| `Error_code error_code -> next_from_error t error_code
|
||||
| `Parse (marks, msg) ->
|
||||
let error_code =
|
||||
match marks, msg with
|
||||
| [ "frame_payload" ], "not enough input" ->
|
||||
(* From RFC7540§4.2:
|
||||
* An endpoint MUST send an error code of FRAME_SIZE_ERROR if a
|
||||
* frame exceeds the size defined in SETTINGS_MAX_FRAME_SIZE,
|
||||
* exceeds any limit defined for the frame type, or is too small
|
||||
* to contain mandatory frame data. *)
|
||||
Error_code.FrameSizeError
|
||||
| _ -> Error_code.ProtocolError
|
||||
in
|
||||
next_from_error t ~msg:(fail_to_string marks msg) error_code)
|
||||
| _ when t.closed -> `Close
|
||||
| Partial _ -> `Read
|
||||
| Initial -> if t.closed then `Close else `Read
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue