(*---------------------------------------------------------------------------- * 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. *---------------------------------------------------------------------------*) module AB = Angstrom.Buffered module Reader = Parse.Reader module Writer = Serialize.Writer module StreamsTbl = Scheduler.StreamsTbl module Scheduler = Scheduler.Make (struct include Stream type t = Respd.t let flush_write_body = Respd.flush_request_body let requires_output = Respd.requires_output end) module Queue = struct include Queue let take_opt t = match is_empty t with true -> None | false -> Some (take t) end type error = Respd.error type trailers_handler = Headers.t -> unit type response_handler = Response.t -> Body.Reader.t -> unit type error_handler = error -> unit type t = { mutable settings : Settings.t ; reader : Reader.frame ; writer : Writer.t ; config : Config.t ; streams : Scheduler.t ; mutable current_stream_id : Stream_identifier.t ; mutable max_pushed_stream_id : Stream_identifier.t ; mutable current_server_streams : int ; mutable receiving_headers_for_stream : Stream_identifier.t option ; mutable did_send_go_away : bool ; mutable unacked_settings : int ; pending_pings : ((unit, [ `EOF ]) result -> unit) Queue.t ; error_handler : error -> unit ; push_handler : Request.t -> (response_handler, unit) result (* From RFC7540§4.3: * Header compression is stateful. One compression context and one * decompression context are used for the entire connection. *) ; hpack_encoder : Hpack.Encoder.t ; hpack_decoder : Hpack.Decoder.t } let default_push_handler = Sys.opaque_identity (fun _ -> Ok (fun _ _ -> ())) let is_closed t = Reader.is_closed t.reader && Writer.is_closed t.writer let shutdown_reader t = Reader.force_close t.reader let flush_request_body t = Scheduler.flush t.streams (t.current_stream_id, t.max_pushed_stream_id) let shutdown_writer t = flush_request_body t; Writer.close t.writer; Writer.wakeup t.writer (* Handling frames against closed streams is hard. See: * https://docs.google.com/presentation/d/1iG_U2bKTc9CnKr0jPTrNfmxyLufx_cK2nNh9VjrKH6s *) let was_closed_or_implicitly_closed t stream_id = if Stream_identifier.is_request stream_id then Stream_identifier.(stream_id <= t.current_stream_id) else Stream_identifier.(stream_id <= t.max_pushed_stream_id) let report_error = let shutdown_rw t = shutdown_reader t; shutdown_writer t in fun t e -> match e with | Error.ConnectionError (error, data) -> if not t.did_send_go_away then ( (* From RFC7540§5.4.1: * An endpoint that encounters a connection error SHOULD first send a * GOAWAY frame (Section 6.8) with the stream identifier of the last * stream that it successfully received from its peer. The GOAWAY frame * includes an error code that indicates why the connection is * terminating. After sending the GOAWAY frame for an error condition, * the endpoint MUST close the TCP connection. *) let debug_data = if String.length data = 0 then Bigstringaf.empty else Bigstringaf.of_string ~off:0 ~len:(String.length data) data in let frame_info = Writer.make_frame_info Stream_identifier.connection in (* TODO: Only write if not already shutdown. *) Writer.write_go_away t.writer frame_info ~debug_data ~last_stream_id: (if Stream_identifier.(t.current_stream_id === -1l) then Stream_identifier.connection else t.current_stream_id) error; t.did_send_go_away <- true; if error <> Error_code.NoError then t.error_handler (`Protocol_error (error, data)); Writer.flush t.writer (fun _reason -> (* XXX: We need to allow lower numbered streams to complete before * shutting down. *) shutdown_rw t); Writer.wakeup t.writer) | StreamError (stream_id, error) -> (match Scheduler.find t.streams stream_id with | Some respd -> Respd.report_error respd (`Protocol_error (error, "")) error | None -> if not (was_closed_or_implicitly_closed t stream_id) then (* Possible if the stream was going to enter the Idle state (first time * we saw e.g. a PRIORITY frame for it) but had e.g. a * FRAME_SIZE_ERROR. *) let frame_info = Writer.make_frame_info stream_id in Writer.write_rst_stream t.writer frame_info error); Writer.wakeup t.writer let report_connection_error t ?(reason = "") error = report_error t (ConnectionError (error, reason)) let report_stream_error t stream_id error = report_error t (StreamError (stream_id, error)) let shutdown t = (* From RFC7540§6.8: * A server that is attempting to gracefully shut down a connection SHOULD * send an initial GOAWAY frame with the last stream identifier set to * 2^31-1 and a NO_ERROR code. *) report_connection_error t Error_code.NoError let set_error_and_handle t stream error error_code = Respd.report_error stream error error_code; Writer.wakeup t.writer let report_exn t exn = if not (is_closed t) then let reason = Printexc.to_string exn in report_connection_error t ~reason Error_code.InternalError let send_window_update : type a. t -> a Scheduler.PriorityTreeNode.node -> int32 -> unit = fun t stream n -> let send_window_update_frame stream_id n = let valid_inflow = Scheduler.add_inflow stream n in assert valid_inflow; let frame_info = Writer.make_frame_info stream_id in Writer.write_window_update t.writer frame_info n in if Int32.compare n 0l > 0 then ( let max_window_size = Settings.WindowSize.max_window_size in let stream_id = Scheduler.stream_id stream in let rec loop n = if n > max_window_size then ( send_window_update_frame stream_id max_window_size; loop (Int32.sub n max_window_size)) else send_window_update_frame stream_id n in loop n; Writer.wakeup t.writer) let handle_push_promise_headers t respd headers = (* From RFC7540§8.2.2: * The header fields in PUSH_PROMISE and any subsequent CONTINUATION frames * MUST be a valid and complete set of request header fields (Section * 8.1.2.3). *) match Headers.method_path_and_scheme_or_malformed headers with | `Malformed -> (* From RFC7540§8.2.2: * If a client receives a PUSH_PROMISE that does not include a complete * and valid set of header fields [...] it MUST respond with a stream * error (Section 5.4.2) of type PROTOCOL_ERROR. *) report_stream_error t respd.Stream.id Error_code.ProtocolError | `Valid (meth, path, scheme) -> let meth = Httpun_types.Method.of_string meth in (match meth, Headers.get_pseudo headers "authority", Message.body_length headers with | (#Httpun_types.Method.standard as meth), _, _ when not Httpun_types.Method.(is_cacheable meth && is_safe meth) -> report_stream_error t respd.id Error_code.ProtocolError | _, _, `Fixed len when not (Int64.equal len 0L) -> (* From RFC7540§8.2: * Clients that receive a promised request that is not cacheable, * that is not known to be safe or that indicates the presence of a * request body MUST reset the promised stream with a stream error * (Section 5.4.2) of type PROTOCOL_ERROR. * * From RFC7231§4.2.3 (Cacheable Methods): * [...] this specification defines GET, HEAD, and POST as cacheable * [...]. * * From RFC7231§4.2.1 (Safe Methods): * Of the request methods defined by this specification, the GET, HEAD, * OPTIONS, and TRACE methods are defined to be safe. * * Note: the intersection of safe and cacheable are the GET and HEAD * methods. *) report_stream_error t respd.id Error_code.ProtocolError (* From RFC7540§8.2: * The server MUST include a value in the :authority pseudo-header field * for which the server is authoritative (see Section 10.1). A client * MUST treat a PUSH_PROMISE for which the server is not authoritative as * a stream error (Section 5.4.2) of type PROTOCOL_ERROR. *) | _, None, _ | _, _, `Error _ -> report_stream_error t respd.id Error_code.ProtocolError | _ -> let request = Request.create ~scheme ~headers meth path in (match t.push_handler request with | Ok response_handler -> (* From RFC7540§8.2: * Promised requests [...] MUST NOT include a request body. *) let request_body = Body.Writer.create_empty ~writer:t.writer in (* From RFC7540§5.1: * reserved (remote): [...] Receiving a HEADERS frame causes the * stream to transition to "half-closed (local)". *) respd.state <- Active ( HalfClosed Stream.WaitingForPeer , { Respd.request ; request_body ; response_handler ; trailers_handler = ignore } ) | Error _ -> (* From RFC7540§6.6: * Recipients of PUSH_PROMISE frames can choose to reject promised * streams by returning a RST_STREAM referencing the promised stream * identifier back to the sender of the PUSH_PROMISE. *) Stream.reset_stream respd Error_code.Cancel)) let handle_response_headers t stream ~end_stream active_request headers = let (Scheduler.Stream { descriptor = respd; _ }) = stream in (* From RFC7540§8.1.2.6: * Clients MUST NOT accept a malformed response. * * Note: in the branches where a malformed response is detected, the response * handler is not called. *) match Headers.get_multi_pseudo headers "status" with | [ status ] -> let response = Response.create ~headers (Status.of_string status) in (* Note: we don't need to check for `end_stream` flag + a non-zero body * length, as the spec allows for non-zero content-length headers and no * DATA frames. * * From RFC7540§8.1.2.6: * A response that is defined to have no payload, as described in * [RFC7230], Section 3.3.2, can have a non-zero content-length header * field, even though no content is included in DATA frames. *) (match Message.body_length headers with | `Error _ -> set_error_and_handle t respd (`Invalid_response_body_length response) ProtocolError | `Fixed _ | `Unknown -> let response_body = if end_stream then Body.Reader.empty else Body.Reader.create (Bigstringaf.create t.config.response_body_buffer_size) ~done_reading:(fun len -> let len = Int32.of_int len in send_window_update t t.streams len; send_window_update t stream len) in let new_response_state = Respd.create_active_response response response_body in respd.state <- Active ( (if Stream.is_open respd then Open new_response_state else HalfClosed new_response_state) , active_request ); active_request.response_handler response response_body; if end_stream then ( (* Deliver EOF to the response body, as the handler might be waiting * on it to act. *) Body.Reader.close response_body; (* From RFC7540§5.1: * [...] an endpoint receiving an END_STREAM flag causes the stream * state to become "half-closed (remote)". *) Respd.close_stream respd)) | _ -> (* From RFC7540§8.1.2.4: * For HTTP/2 responses, a single :status pseudo-header field is defined * that carries the HTTP status code field (see [RFC7231], Section 6). * This pseudo-header field MUST be included in all responses; otherwise, * the response is malformed (Section 8.1.2.6). *) let message = "HTTP/2 responses must include a single `:status` pseudo-header" in set_error_and_handle t respd (`Malformed_response message) ProtocolError let handle_headers t ~end_stream stream headers = let (Scheduler.Stream { descriptor = respd; _ }) = stream in (* From RFC7540§5.1.2: * Endpoints MUST NOT exceed the limit set by their peer. An endpoint that * receives a HEADERS frame that causes its advertised concurrent stream * limit to be exceeded MUST treat this as a stream error (Section 5.4.2) * of type PROTOCOL_ERROR or REFUSED_STREAM. *) if Int32.( compare (of_int (t.current_server_streams + 1)) t.config.max_concurrent_streams) > 0 then if t.unacked_settings > 0 then (* From RFC7540§8.1.4: * The REFUSED_STREAM error code can be included in a RST_STREAM frame * to indicate that the stream is being closed prior to any processing * having occurred. Any request that was sent on the reset stream can * be safely retried. * * Note: if there are pending SETTINGS to acknowledge, assume there was a * race condition and let the client retry. *) report_stream_error t respd.Stream.id Error_code.RefusedStream else report_stream_error t respd.Stream.id Error_code.ProtocolError else ( (* From RFC7540§5.1.2: * Streams that are in the "open" state or in either of the "half-closed" * states count toward the maximum number of streams that an endpoint is * permitted to open. *) t.current_server_streams <- t.current_server_streams + 1; match respd.state with | Reserved _ -> respd.state <- Reserved Stream.FullHeaders; handle_push_promise_headers t respd headers | Active (active_state, active_request) -> (match active_state with | Open _ -> respd.state <- Active (Open FullHeaders, active_request) | HalfClosed _ -> respd.state <- Active (HalfClosed FullHeaders, active_request)); handle_response_headers t stream ~end_stream active_request headers | _ -> (* Unreachable. This function is only invoked if the stream is active. *) assert false) let handle_headers_block t ?(is_trailers = false) stream partial_headers flags headers_block = let open AB in let (Scheduler.Stream { descriptor = respd; _ }) = stream in let end_headers = Flags.test_end_header flags in (* From RFC7540§6.10: * An endpoint receiving HEADERS, PUSH_PROMISE, or CONTINUATION * frames needs to reassemble header blocks and perform decompression * even if the frames are to be discarded *) let parse_state' = AB.feed partial_headers.Stream.parse_state (`Bigstring headers_block) in if end_headers then ( t.receiving_headers_for_stream <- None; let parse_state' = AB.feed parse_state' `Eof in match parse_state' with | Done (_, Ok headers) -> if not is_trailers then (* `handle_headers` will take care of transitioning the stream state *) let end_stream = partial_headers.end_stream in handle_headers t ~end_stream stream headers else if Headers.trailers_valid headers then ( Respd.deliver_trailer_headers respd headers; let response_body = Respd.response_body_exn respd in Body.Reader.close response_body) else (* From RFC7540§8.1.2.1: * Pseudo-header fields MUST NOT appear in trailers. Endpoints MUST * treat a request or response that contains undefined or invalid * pseudo-header fields as malformed (Section 8.1.2.6). *) let message = "Pseudo-header fields must not appear in trailers" in set_error_and_handle t respd (`Malformed_response message) ProtocolError (* From RFC7540§4.3: * A decoding error in a header block MUST be treated as a connection * error (Section 5.4.1) of type COMPRESSION_ERROR. *) | Done (_, Error _) | Partial _ -> report_connection_error t Error_code.CompressionError | Fail (_, _, message) -> report_connection_error t ~reason:message Error_code.CompressionError) else partial_headers.parse_state <- parse_state' let handle_trailer_headers = handle_headers_block ~is_trailers:true let create_partial_headers t flags headers_block = let end_headers = Flags.test_end_header flags in let headers_block_length = Bigstringaf.length headers_block in let initial_buffer_size = if end_headers then headers_block_length else (* Conservative estimate that there's only going to be one CONTINUATION * frame. *) 2 * headers_block_length in { Stream.parse_state = AB.parse ~initial_buffer_size (Hpack.Decoder.decode_headers t.hpack_decoder) ; end_stream = Flags.test_end_stream flags } let handle_first_response_bytes t stream active_request frame_header headers_block = let (Scheduler.Stream { descriptor; _ }) = stream in let { Frame.flags; stream_id; _ } = frame_header in let partial_headers = create_partial_headers t flags headers_block in let remote_state = Stream.PartialHeaders partial_headers in descriptor.Stream.state <- (if Stream.is_open descriptor then Active (Open remote_state, active_request) else Active (HalfClosed remote_state, active_request)); if not (Flags.test_end_header flags) then t.receiving_headers_for_stream <- Some stream_id; handle_headers_block t stream partial_headers flags headers_block let process_trailer_headers t stream active_response frame_header headers_block = let (Scheduler.Stream { descriptor = respd; _ }) = stream in let { Frame.stream_id; flags; _ } = frame_header in let end_stream = Flags.test_end_stream flags in if not end_stream then (* From RFC7540§8.1: * A HEADERS frame (and associated CONTINUATION frames) can only appear * at the start or end of a stream. An endpoint that receives a HEADERS * frame without the END_STREAM flag set after receiving a final * (non-informational) status code MUST treat the corresponding request * or response as malformed (Section 8.1.2.6). *) let message = "HEADERS frames containing trailers must set the END_STREAM flag" in set_error_and_handle t respd (`Malformed_response message) ProtocolError else let partial_headers = { Stream.parse_state = AB.parse (Hpack.Decoder.decode_headers t.hpack_decoder) (* obviously true at this point. *) ; end_stream } in active_response.Respd.trailers_parser <- Some partial_headers; if not Flags.(test_end_header flags) then t.receiving_headers_for_stream <- Some stream_id; (* trailer headers: RFC7230§4.4 *) handle_trailer_headers t stream partial_headers flags headers_block let process_headers_frame t { Frame.frame_header; _ } headers_block = let { Frame.stream_id; _ } = frame_header in match Scheduler.get_node t.streams stream_id with | None -> (* If we're receiving a response for a stream that's no longer in the * priority tree, assume this is a network race - we canceled a request * but a responnse was already in flight. * * However, if the stream identifer is greater than the largest stream * identifier we have produced, they should know better. In this case, * send an RST_STREAM. *) if Stream_identifier.( stream_id >= t.current_stream_id && is_request stream_id) then report_stream_error t stream_id Error_code.StreamClosed | Some (Scheduler.Stream { descriptor; _ } as stream) -> (match descriptor.state with | Idle -> (* From RFC7540§6.2: * HEADERS frames can be sent on a stream in the "idle", "reserved * (local)", "open", or "half-closed (remote)" state. *) report_connection_error t Error_code.ProtocolError | Active ((Open WaitingForPeer | HalfClosed WaitingForPeer), active_request) -> handle_first_response_bytes t stream active_request frame_header headers_block | Active ( ( Open (FullHeaders | PartialHeaders _) | HalfClosed (FullHeaders | PartialHeaders _) ) , _ ) -> assert false (* if we're getting a HEADERS frame at this point, they must be * trailers, and the END_STREAM flag needs to be set. *) | Active ( ( Open (ActiveMessage active_response) | HalfClosed (ActiveMessage active_response) ) , _ ) -> process_trailer_headers t stream active_response frame_header headers_block; Stream.finish_stream descriptor Finished | Closed { reason = ResetByThem _; _ } -> (* From RFC7540§5.1: * closed: [...] An endpoint that receives any frame other than * PRIORITY after receiving a RST_STREAM MUST treat that as a * stream error (Section 5.4.2) of type STREAM_CLOSED. *) report_stream_error t stream_id Error_code.StreamClosed (* From RFC7540§5.1: * reserved (local): [...] Receiving any type of frame other than * RST_STREAM, PRIORITY, or WINDOW_UPDATE on a stream in this state * MUST be treated as a connection error (Section 5.4.1) of type * PROTOCOL_ERROR. *) | Reserved _ | Closed _ -> (* From RFC7540§5.1: * Similarly, an endpoint that receives any frames after receiving * a frame with the END_STREAM flag set MUST treat that as a * connection error (Section 5.4.1) of type STREAM_CLOSED [...]. *) report_connection_error t Error_code.StreamClosed) let process_data_frame t { Frame.frame_header; _ } bstr = let open Scheduler in let { Frame.flags; stream_id; payload_length; _ } = frame_header in let payload_len32 = Int32.of_int payload_length in match Scheduler.get_node t.streams stream_id with | Some (Stream { descriptor; _ } as stream) -> (match descriptor.state with | Active ( ( Open (ActiveMessage response_info) | HalfClosed (ActiveMessage response_info) ) , _ ) -> let { Respd.response; response_body; response_body_bytes; _ } = response_info in response_info.response_body_bytes <- Int64.(add response_body_bytes (of_int (Bigstringaf.length bstr))); (* First, calculate whether we're allowed to receive this frame based * on the _current_ inflow. *) let allowed_to_receive = Scheduler.(allowed_to_receive t.streams stream payload_len32) in (* Then, deduct inflow from the connection flow-control window, as * mandated by the protocol. * * From RFC7540§6.9: * A receiver that receives a flow-controlled frame MUST always account * for its contribution against the connection flow-control window, * unless the receiver treats this as a connection error (Section 5.4.1). * This is necessary even if the frame is in error. *) Scheduler.deduct_inflow t.streams payload_len32; if not allowed_to_receive then ( (* From RFC7540§6.9: * A receiver MAY respond with a stream error (Section 5.4.2) or * connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR if it * is unable to accept a frame. *) send_window_update t t.streams payload_len32; report_stream_error t stream_id Error_code.FlowControlError) else ( Scheduler.deduct_inflow stream payload_len32; match Message.body_length response.headers with | `Fixed len (* Getting more than the server declared *) when Int64.compare response_info.response_body_bytes len > 0 -> (* Give back connection-level flow-controlled bytes (we use payload * length to include any padding bytes that the frame might have * included - which were ignored at parse time). *) send_window_update t t.streams payload_len32; (* From RFC7540§8.1.2.6: * A request or response is also malformed if the value of a * content-length header field does not equal the sum of the * DATA frame payload lengths that form the body. *) set_error_and_handle t descriptor (`Invalid_response_body_length response) ProtocolError | _ -> let end_stream = Flags.test_end_stream flags in (* From RFC7540§6.9.1: * The receiver of a frame sends a WINDOW_UPDATE frame as it * consumes data and frees up space in flow-control windows. * Separate WINDOW_UPDATE frames are sent for the stream- and * connection-level flow-control windows. * * Note: we send these WINDOW_UPDATE frames once the body bytes * have been surfaced to the application. This is done in the * record field `done_reading` of `Body.t`. *) let faraday = Body.Reader.unsafe_faraday response_body in if not (Faraday.is_closed faraday) then ( Faraday.schedule_bigstring faraday bstr; if end_stream then Body.Reader.close response_body); Respd.flush_response_body descriptor; if end_stream && not (Respd.requires_output descriptor) then (* From RFC7540§6.1: * When set, bit 0 indicates that this frame is the last that * the endpoint will send for the identified stream. Setting * this flag causes the stream to enter one of the * "half-closed" states or the "closed" state (Section 5.1). * * Transition to the "closed" state if this is the last DATA frame * that the server will send and we're done sending. *) Stream.finish_stream descriptor Finished) | Idle -> (* From RFC7540§5.1: * idle: [...] Receiving any frame other than HEADERS or PRIORITY on * a stream in this state MUST be treated as a connection error * (Section 5.4.1) of type PROTOCOL_ERROR. *) report_connection_error t Error_code.ProtocolError (* This is technically in the half-closed (local) state *) | Closed { reason = ResetByUs NoError; _ } -> (* From RFC7540§6.9: * A receiver that receives a flow-controlled frame MUST always * account for its contribution against the connection flow-control * window, unless the receiver treats this as a connection error * (Section 5.4.1). This is necessary even if the frame is in * error. *) send_window_update t t.streams payload_len32 (* From RFC7540§6.4: * [...] after sending the RST_STREAM, the sending endpoint MUST be * prepared to receive and process additional frames sent on the * stream that might have been sent by the peer prior to the arrival * of the RST_STREAM. * * Note: after some writer yields / wake ups, we will have stopped * keeping state information for the stream. This functions effectively * as a way of only accepting frames after an RST_STREAM from us up to * a time limit. *) | _ -> send_window_update t t.streams payload_len32; (* From RFC7540§6.1: * If a DATA frame is received whose stream is not in "open" or * "half-closed (local)" state, the recipient MUST respond with a * stream error (Section 5.4.2) of type STREAM_CLOSED. *) report_stream_error t stream_id Error_code.StreamClosed) | None -> if not (was_closed_or_implicitly_closed t stream_id) then (* From RFC7540§5.1: * idle: [...] Receiving any frame other than HEADERS or PRIORITY on * a stream in this state MUST be treated as a connection error * (Section 5.4.1) of type PROTOCOL_ERROR. *) report_connection_error t Error_code.ProtocolError let on_close_stream t id ~active closed = if active then (* From RFC7540§5.1.2: * Streams that are in the "open" state or in either of the "half-closed" * states count toward the maximum number of streams that an endpoint is * permitted to open. *) t.current_server_streams <- t.current_server_streams - 1; Scheduler.mark_for_removal t.streams id closed let process_priority_frame t { Frame.frame_header; _ } priority = let { Frame.stream_id; _ } = frame_header in let { Priority.stream_dependency; _ } = priority in if Stream_identifier.(stream_id === stream_dependency) then (* From RFC7540§5.3.1: * A stream cannot depend on itself. An endpoint MUST treat this as a * stream error (Section 5.4.2) of type PROTOCOL_ERROR. *) report_stream_error t stream_id Error_code.ProtocolError else match Scheduler.get_node t.streams stream_id with | Some stream -> Scheduler.reprioritize_stream t.streams ~priority stream | None -> (* From RFC7540§5.3: * A client can assign a priority for a new stream by including * prioritization information in the HEADERS frame (Section 6.2) that * opens the stream. At any other time, the PRIORITY frame (Section * 6.3) can be used to change the priority of a stream. * * Note: The spec mostly only mentions that clients are the endpoints * that make use of PRIORITY frames. As such, we don't make too * much of an effort to process PRIORITY frames coming from a * server. If we know about a stream, we reprioritize it (meaning * prioritization is an input to the process of allocating * resources when flushing request bodies). Otherwise, we ignore * it. We don't, however, report any errors if the frame is * well-formed, as section 5. clearly mentions that PRIORITY frames * must be accepted in all stream states. * * From RFC7540§5.1: * Note that PRIORITY can be sent and received in any stream state. *) () let process_rst_stream_frame t { Frame.frame_header; _ } error_code = let { Frame.stream_id; _ } = frame_header in match Scheduler.find t.streams stream_id with | Some respd -> (match respd.state, error_code with | Idle, _ -> (* From RFC7540§6.4: * RST_STREAM frames MUST NOT be sent for a stream in the "idle" * state. If a RST_STREAM frame identifying an idle stream is * received, the recipient MUST treat this as a connection error * (Section 5.4.1) of type PROTOCOL_ERROR. *) report_connection_error t Error_code.ProtocolError | Active _, Error_code.NoError -> (* If we're active (i.e. not done sending the request body), finish the * stream, in order to mark it for cleanup. * * Note: we don't close the request body here because the client may be * in the process of writing to it, and while we're not going to send * those bytes to the output channel, we don't want to fail when writing * either. *) Stream.finish_stream respd (ResetByThem error_code) | Closed _, _ -> (* From RFC7540§8.1: * A server can send a complete response prior to the client sending an * entire request if the response does not depend on any portion of the * request that has not been sent and received. When this is true, a * server MAY request that the client abort transmission of a request * without error by sending a RST_STREAM with an error code of NO_ERROR * after sending a complete response (i.e., a frame with the END_STREAM * flag). * * If we're done sending the request there's nothing to do here, allow * the stream to finish successfully. * * From RFC7540§5.1: * Endpoints MUST ignore WINDOW_UPDATE or RST_STREAM frames received * in this state, though endpoints MAY choose to treat frames that * arrive a significant time after sending END_STREAM as a connection * error (Section 5.4.1) of type PROTOCOL_ERROR. * * We ignore further RST_STREAM frames. *) (* XXX(anmonteiro): When we add logging support, add something here. *) () | _ -> (* From RFC7540§6.4: * The RST_STREAM frame fully terminates the referenced stream and * causes it to enter the "closed" state. After receiving a * RST_STREAM on a stream, the receiver MUST NOT send additional * frames for that stream, with the exception of PRIORITY. * * Note: * This match branch also accepts streams in the `Closed` state. We * do that to comply with the following: * * From RFC7540§6.4: * [...] after sending the RST_STREAM, the sending endpoint MUST be * prepared to receive and process additional frames sent on the * stream that might have been sent by the peer prior to the arrival * of the RST_STREAM. *) Stream.finish_stream respd (ResetByThem error_code); (* From RFC7540§5.4.2: * To avoid looping, an endpoint MUST NOT send a RST_STREAM in response * to a RST_STREAM frame. * * Note: the {!Respd.report_error} function does not send an RST_STREAM * frame for streams in the closed state. So we close the stream before * reporting the error. *) set_error_and_handle t respd (`Protocol_error (error_code, "")) error_code) | None -> (* We might have removed the stream from the hash table. If its stream * id is smaller than or equal to the max client stream id we've generated, * then it must have been closed. *) if not (was_closed_or_implicitly_closed t stream_id) then (* From RFC7540§6.4: * RST_STREAM frames MUST NOT be sent for a stream in the "idle" * state. If a RST_STREAM frame identifying an idle stream is * received, the recipient MUST treat this as a connection error * (Section 5.4.1) of type PROTOCOL_ERROR. * * Note: * If we didn't find the stream in the hash table it must be "idle". *) report_connection_error t Error_code.ProtocolError let process_settings_frame t { Frame.frame_header; _ } settings = let open Scheduler in let { Frame.flags; _ } = frame_header in (* We already checked that an acked SETTINGS is empty. Don't need to do * anything else in that case *) if Flags.(test_ack flags) then ( t.unacked_settings <- t.unacked_settings - 1; if t.unacked_settings < 0 then (* The server is ACKing a SETTINGS frame that we didn't send *) report_connection_error t ~reason:"Received unexpected SETTINGS frame with acknowledgement" Error_code.ProtocolError) else match Settings.check_settings_list ~is_client:true settings with | Ok () -> (* From RFC7540§6.5: * Each parameter in a SETTINGS frame replaces any existing value for * that parameter. Parameters are processed in the order in which they * appear, and a receiver of a SETTINGS frame does not need to maintain * any state other than the current value of its parameters. *) let new_settings = List.fold_left (fun (acc : Settings.t) item -> match item with | Settings.HeaderTableSize x -> (* From RFC7540§6.5.2: * Allows the sender to inform the remote endpoint of the maximum * size of the header compression table used to decode header * blocks, in octets. *) Hpack.Encoder.set_capacity t.hpack_encoder x; { acc with header_table_size = x } | EnablePush x -> (* We've already verified that this setting is either 0 or 1 in the * call to `Settings.check_settings_list` above. *) { acc with enable_push = x = 1 } | MaxConcurrentStreams x -> { acc with max_concurrent_streams = x } | InitialWindowSize new_val -> (* From RFC7540§6.9.2: * In addition to changing the flow-control window for streams * that are not yet active, a SETTINGS frame can alter the * initial flow-control window size for streams with active * flow-control windows (that is, streams in the "open" or * "half-closed (remote)" state). When the value of * SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust * the size of all stream flow-control windows that it * maintains by the difference between the new value and the * old value. * * [...] A SETTINGS frame cannot alter the connection * flow-control window. *) let old_val = t.settings.initial_window_size in let growth = Int32.sub new_val old_val in let exception Local in (match Scheduler.iter ~f:(fun stream -> (* From RFC7540§6.9.2: * An endpoint MUST treat a change to * SETTINGS_INITIAL_WINDOW_SIZE that causes any * flow-control window to exceed the maximum size as a * connection error (Section 5.4.1) of type * FLOW_CONTROL_ERROR. *) if not (Scheduler.add_flow stream growth) then raise Local) t.streams with | () -> () | exception Local -> report_connection_error t ~reason: (Format.sprintf "Window size for stream would exceed %ld" Settings.WindowSize.max_window_size) Error_code.FlowControlError); { acc with initial_window_size = new_val } | MaxFrameSize x -> Scheduler.iter ~f:(fun (Stream { descriptor; _ }) -> if Respd.requires_output descriptor then descriptor.max_frame_size <- x) t.streams; { acc with max_frame_size = x } | MaxHeaderListSize x -> { acc with max_header_list_size = Some x }) t.settings settings in t.settings <- new_settings; let frame_info = Writer.make_frame_info ~flags:Flags.(set_ack default_flags) Stream_identifier.connection in (* From RFC7540§6.5: * ACK (0x1): [...] When this bit is set, the payload of the SETTINGS * frame MUST be empty. *) Writer.write_settings t.writer frame_info []; t.unacked_settings <- t.unacked_settings + 1; Writer.wakeup t.writer | Error error -> report_error t error let reserve_stream t { Frame.frame_header; _ } promised_stream_id headers_block = let { Frame.flags; stream_id; _ } = frame_header in (* From RFC7540§6.6: * The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in * advance of streams the sender intends to initiate. *) let respd = Stream.create promised_stream_id ~max_frame_size:t.settings.max_frame_size t.writer t.error_handler (on_close_stream t promised_stream_id) in (* From RFC7540§5.3.5: * All streams are initially assigned a non-exclusive dependency on stream * 0x0. Pushed streams (Section 8.2) initially depend on their associated * stream. In both cases, streams are assigned a default weight of 16. *) let stream : Scheduler.nonroot Scheduler.node = Scheduler.add t.streams ~priority:{ Priority.default_priority with stream_dependency = stream_id } ~initial_send_window_size:t.settings.initial_window_size ~initial_recv_window_size:t.config.initial_window_size respd in let partial_headers = create_partial_headers t flags headers_block in respd.state <- Reserved (PartialHeaders partial_headers); if not (Flags.test_end_header flags) then t.receiving_headers_for_stream <- Some promised_stream_id; handle_headers_block t stream partial_headers flags headers_block let process_push_promise_frame t ({ Frame.frame_header; _ } as frame) promised_stream_id headers_block = let { Frame.stream_id; _ } = frame_header in (* At this point, `promised_stream_id` has already been validated by the * parser *) if not t.settings.enable_push then (* From RFC7540§6.6: * PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH setting of * the peer endpoint is set to 0. An endpoint that has set this setting * and has received acknowledgement MUST treat the receipt of a * PUSH_PROMISE frame as a connection error (Section 5.4.1) of type * PROTOCOL_ERROR. *) report_connection_error t ~reason:"Push is disabled for this connection" Error_code.ProtocolError else if not Stream_identifier.(promised_stream_id > t.max_pushed_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 that an illegal stream * identifier is an identifier for a stream that is not currently in the * "idle" state. *) report_connection_error t ~reason:"Illegal stream identifier promised by PUSH_PROMISE" Error_code.ProtocolError else let send_connection_error () = report_connection_error t ~reason: "Received PUSH_PROMISE on a stream that is neither open nor \ half-closed (local)" Error_code.ProtocolError in t.max_pushed_stream_id <- promised_stream_id; match Scheduler.find t.streams stream_id with | None -> (* From RFC7540§6.6: * A receiver MUST treat the receipt of a PUSH_PROMISE on a stream that * is neither "open" nor "half-closed (local)" as a connection error * (Section 5.4.1) of type PROTOCOL_ERROR. *) send_connection_error () | Some respd -> (match respd.state with | Active ((Open _ | HalfClosed _), _) -> reserve_stream t frame promised_stream_id headers_block | _ -> send_connection_error ()) let ok = Ok () let process_ping_frame t { Frame.frame_header; _ } payload = let { Frame.flags; _ } = frame_header in (* From RFC7540§6.7: * ACK (0x1): When set, bit 0 indicates that this PING frame is a PING * response. [...] An endpoint MUST NOT respond to PING frames containing * this flag. *) if Flags.test_ack flags then match Queue.take_opt t.pending_pings with | Some callback -> callback ok | None -> (* server is ACKing a PING that we didn't send? *) report_connection_error t ~reason:"Unexpected PING acknowledgement" Error_code.ProtocolError else (* From RFC7540§6.7: * Receivers of a PING frame that does not include an ACK flag MUST send * a PING frame with the ACK flag set in response, with an identical * payload. PING responses SHOULD be given higher priority than any other * frame. *) let frame_info = Writer.make_frame_info (* From RFC7540§6.7: * ACK (0x1): When set, bit 0 indicates that this PING frame is a * PING response. An endpoint MUST set this flag in PING * responses. *) ~flags:Flags.(set_ack default_flags) Stream_identifier.connection in (* From RFC7540§6.7: * Receivers of a PING frame that does not include an ACK flag MUST send * a PING frame with the ACK flag set in response, with an identical * payload. *) Writer.write_ping t.writer frame_info payload; Writer.wakeup t.writer let process_goaway_frame t _frame ~last_stream_id:_ ~data error = let reason = match Bigstringaf.length data with | 0 -> None | len -> Some (Bigstringaf.substring data ~off:0 ~len) in (* TODO(anmonteiro): I think we need to allow lower numbered streams to * complete. *) report_connection_error t ?reason error let add_window_increment : type a. t -> a Scheduler.PriorityTreeNode.node -> int32 -> unit = fun t stream increment -> let open Scheduler in let did_add = Scheduler.add_flow stream increment in let stream_id = Scheduler.stream_id stream in let new_flow = match stream with | Connection { flow; _ } -> flow | Stream { flow; _ } -> flow in if did_add then ( if Int32.compare new_flow 0l > 0 then (* Don't bother waking up the writer if the new flow doesn't allow * the stream to write. *) Writer.wakeup t.writer) else if Stream_identifier.is_connection stream_id then report_connection_error t ~reason: (Printf.sprintf "Window size for stream would exceed %ld" Settings.WindowSize.max_window_size) Error_code.FlowControlError else report_stream_error t stream_id Error_code.FlowControlError let process_window_update_frame t { Frame.frame_header; _ } window_increment = let open Scheduler in let { Frame.stream_id; _ } = frame_header in (* From RFC7540§6.9: * The WINDOW_UPDATE frame can be specific to a stream or to the entire * connection. In the former case, the frame's stream identifier indicates * the affected stream; in the latter, the value "0" indicates that the * entire connection is the subject of the frame. *) if Stream_identifier.is_connection stream_id then add_window_increment t t.streams window_increment else match Scheduler.get_node t.streams stream_id with | Some (Stream { descriptor; _ } as stream_node) -> (match descriptor.state with | Idle -> (* From RFC7540§5.1: * idle: [...] Receiving any frame other than HEADERS or PRIORITY on * a stream in this state MUST be treated as a connection error * (Section 5.4.1) of type PROTOCOL_ERROR. *) report_connection_error t Error_code.ProtocolError | Active _ (* From RFC7540§5.1: * reserved (local): [...] A PRIORITY or WINDOW_UPDATE frame MAY be * received in this state. *) | Reserved _ -> add_window_increment t stream_node window_increment | Closed _ -> (* From RFC7540§5.1: * Endpoints MUST ignore WINDOW_UPDATE or RST_STREAM frames received * in this state, though endpoints MAY choose to treat frames that * arrive a significant time after sending END_STREAM as a connection * error (Section 5.4.1) of type PROTOCOL_ERROR. *) ()) | None -> if not (was_closed_or_implicitly_closed t stream_id) then (* From RFC7540§5.1: * idle: [...] Receiving any frame other than HEADERS or PRIORITY on * a stream in this state MUST be treated as a connection error * (Section 5.4.1) of type PROTOCOL_ERROR. *) report_connection_error t Error_code.ProtocolError let process_continuation_frame t { Frame.frame_header; _ } headers_block = let { Frame.stream_id; flags; _ } = frame_header in match Scheduler.get_node t.streams stream_id with | Some (Scheduler.Stream { descriptor; _ } as stream) -> (match descriptor.state with | Active ( ( Open (PartialHeaders partial_headers) | HalfClosed (PartialHeaders partial_headers) ) , _ ) -> handle_headers_block t stream partial_headers flags headers_block | Active ( ( Open (ActiveMessage { trailers_parser = Some partial_headers; _ }) | HalfClosed (ActiveMessage { trailers_parser = Some partial_headers; _ }) ) , _ ) -> handle_trailer_headers t stream partial_headers flags headers_block | _ -> (* TODO: maybe need to handle the case where the stream has been closed * due to a stream error. *) (* From RFC7540§6.10: * A RST_STREAM is the last frame that an endpoint can send on a * stream. The peer that sends the RST_STREAM frame MUST be prepared * to receive any frames that were sent or enqueued for sending by * the remote peer. These frames can be ignored, except where they * modify connection state (such as the state maintained for header * compression (Section 4.3) or flow control). *) report_connection_error t Error_code.ProtocolError) | None -> (* From RFC7540§6.10: * A CONTINUATION frame MUST be preceded by a HEADERS, PUSH_PROMISE or * CONTINUATION frame without the END_HEADERS flag set. A recipient that * observes violation of this rule MUST respond with a connection error * (Section 5.4.1) of type PROTOCOL_ERROR. *) report_connection_error t Error_code.ProtocolError (* From RFC7540§1: * HTTP/2 [...] allows interleaving of request and response messages on the * same connection and uses an efficient coding for HTTP header fields. *) let create ?(config = Config.default) ?push_handler ~error_handler () = let push_handler = match push_handler with | Some push_handler -> push_handler | None -> default_push_handler in let settings = { (Config.to_settings config) with (* If the caller is not going to process PUSH_PROMISE frames, just * disable it. *) enable_push = config.enable_server_push && push_handler != default_push_handler } in let rec connection_preface_handler recv_frame settings_list = let t = Lazy.force t in (* Now process the client's SETTINGS frame. `process_settings_frame` will * take care of calling `wakeup_writer`. *) process_settings_frame t recv_frame settings_list and frame_handler r = let t = Lazy.force t in match r with | Error e -> report_error t e | Ok ({ Frame.frame_payload; frame_header } as frame) -> (match t.receiving_headers_for_stream with | Some stream_id when (not Stream_identifier.(stream_id === frame_header.stream_id)) || frame_header.frame_type <> Continuation -> (* From RFC7540§6.2: * A HEADERS frame without the END_HEADERS flag set MUST be followed * by a CONTINUATION frame for the same stream. A receiver MUST treat * the receipt of any other type of frame or a frame on a different * stream as a connection error (Section 5.4.1) of type * PROTOCOL_ERROR. *) report_connection_error t ~reason: "HEADERS or PUSH_PROMISE without the END_HEADERS flag set must be \ followed by a CONTINUATION frame for the same stream" Error_code.ProtocolError | _ -> (match frame_payload with | Headers (_priority, headers_block) -> process_headers_frame t frame headers_block | Data bs -> process_data_frame t frame bs | Priority priority -> process_priority_frame t frame priority | RSTStream error_code -> process_rst_stream_frame t frame error_code | Settings settings -> process_settings_frame t frame settings | PushPromise (promised_stream_id, bs) -> process_push_promise_frame t frame promised_stream_id bs | Ping data -> process_ping_frame t frame data | GoAway (last_stream_id, error, data) -> process_goaway_frame t frame ~last_stream_id ~data error | WindowUpdate window_size -> process_window_update_frame t frame window_size | Continuation headers_block -> process_continuation_frame t frame headers_block | Unknown _ -> (* From RFC7540§5.1: * Frames of unknown types are ignored. *) ())) and t = lazy { settings ; config (* From RFC7540§5.1.1: * Streams initiated by a client MUST use odd-numbered stream * identifiers *) ; current_stream_id = -1l ; max_pushed_stream_id = 0l ; current_server_streams = 0 ; receiving_headers_for_stream = None ; did_send_go_away = false ; unacked_settings = 0 ; pending_pings = Queue.create () ; error_handler ; push_handler ; reader = Reader.client_frames ~max_frame_size:settings.max_frame_size connection_preface_handler frame_handler ; writer = Writer.create settings.max_frame_size ; streams = Scheduler.make_root () (* From RFC7540§4.3: * Header compression is stateful. One compression context and one * decompression context are used for the entire connection. *) ; hpack_encoder = Hpack.Encoder.(create settings.header_table_size) ; hpack_decoder = Hpack.Decoder.(create settings.header_table_size) } in let t = Lazy.force t in (* Check if the settings for the connection are different than the default * HTTP/2 settings. In the event that they are, we need to send a non-empty * SETTINGS frame advertising our configuration. *) let settings = Settings.settings_for_the_connection t.settings in (* From RFC7540§6.9.2: * When an HTTP/2 connection is first established, new streams are created * with an initial flow-control window size of 65,535 octets. The * connection flow-control window is also 65,535 octets. * * XXX(anmonteiro): the starting setting for the initial window size for * _sending_ is the default of 65535 octets. We're effectively overwriting it * here to enforce this default after abusing the settings implementation to * send our (receiving) in-flow setting to the peer. Throughout other parts * of the code we (should) refer to it through * [t.config.initial_window_size]. This should probably be cleaned up in the * future. *) t.settings <- { t.settings with initial_window_size = Settings.default.initial_window_size }; (* Send the client connection preface *) Writer.write_connection_preface t.writer settings; (* If a higher value for initial window size is configured, add more tokens * to the connection (we have no streams at this point) -- the peer is * allowed to send more than the defaults. * * From RFC7540§6.9.2: * The connection flow-control window can only be changed using * WINDOW_UPDATE frames. *) (if t.config.initial_window_size > Settings.default.initial_window_size then let diff = Int32.sub t.config.initial_window_size Settings.default.initial_window_size in send_window_update t t.streams diff); t let create_and_add_stream t ~error_handler = let max_frame_size = t.settings.max_frame_size in t.current_stream_id <- Int32.add t.current_stream_id 2l; let stream_id = t.current_stream_id in let respd = Stream.create stream_id ~max_frame_size t.writer error_handler (on_close_stream t stream_id) in (* TODO: custom priority *) let _stream : Scheduler.nonroot Scheduler.node = Scheduler.add t.streams ~priority:Priority.default_priority ~initial_send_window_size:t.settings.initial_window_size ~initial_recv_window_size:t.config.initial_window_size respd in respd (* Meant to be called after receiving an HTTP/1.1 `101 Switching_protocols` * response upgrading to HTTP/2. *) let create_h2c ?config ?push_handler ~headers ~target ~meth ~error_handler (response_handler, response_error_handler) = match Headers.of_http1 ~headers ~target ~meth with | Ok headers -> (* From RFC7540§3.2: * Upon receiving the 101 response, the client MUST send a connection * preface (Section 3.5), which includes a SETTINGS frame. *) let t = create ?config ?push_handler ~error_handler () in let respd = create_and_add_stream t ~error_handler:response_error_handler in assert (Stream_identifier.(t.current_stream_id === 1l)); assert (Stream_identifier.(respd.id === 1l)); let request = Request.create ~headers ~scheme:"http" meth target in (* From RFC7540§3.2: * The HTTP/1.1 request that is sent prior to upgrade is assigned a * stream identifier of 1 (see Section 5.1.1) with default priority * values (Section 5.3.5). Stream 1 is implicitly "half-closed" from the * client toward the server (see Section 5.1), since the request is * completed as an HTTP/1.1 request. *) respd.state <- Active ( HalfClosed WaitingForPeer , { request (* The request body is no more than a placeholder. The HTTP/1.1 * connection that we're upgrading from already sent it to the * server. Application code knows what it is. * * From RFC7540§3.2: * Requests that contain a payload body MUST be sent in their * entirety before the client can send HTTP/2 frames. This means * that a large request can block the use of the connection until * it is completely sent. *) ; request_body = Body.Writer.create_empty ~writer:t.writer ; response_handler ; trailers_handler = ignore } ); Writer.wakeup t.writer; Ok t | Error msg -> Error msg let request t ?(flush_headers_immediately = false) ?(trailers_handler = ignore) request ~error_handler ~response_handler = let max_frame_size = t.settings.max_frame_size in let respd = create_and_add_stream t ~error_handler in let request_body = Body.Writer.create (Bigstringaf.create max_frame_size) ~writer:t.writer in let frame_info = Writer.make_frame_info ~max_frame_size:t.settings.max_frame_size ~flags:Flags.default_flags respd.id in Writer.write_request_headers t.writer t.hpack_encoder ~priority: (* TODO: allow setting the priority of the request. *) Priority.default_priority frame_info request; respd.state <- Active ( Open WaitingForPeer , { request; request_body; response_handler; trailers_handler } ); if not flush_headers_immediately then Writer.yield t.writer; Writer.wakeup t.writer; (* Closing the request body puts the stream in the half-closed (local) state. * This is handled by {!Respd.flush_request_body}, which transitions the * state once it verifies that there's no more data to send for the * stream. *) request_body (* XXX: we store PING callbacks in FIFO order. Would it ever be the case that * the receipt of a PING frame acknowledges a later callback? If so, we'd need * to disallow sending custom PING payloads and generate a random payload that * we store in a Hashtbl. *) let ping t ?payload ?(off = 0) callback = let payload = match payload with | None -> Serialize.default_ping_payload | Some payload -> if Bigstringaf.length payload - off < 8 then failwith "PING payload must have at least 8 octets in length"; payload in (* From RFC7540§6.7: * ACK (0x1): When set, bit 0 indicates that this PING frame is a PING * response. * * Note: this is not a PING response, quite the opposite, so we don't set * the ACK flag. *) let frame_info = Writer.make_frame_info Stream_identifier.connection in Queue.add callback t.pending_pings; Writer.write_ping t.writer frame_info ~off payload; Writer.wakeup t.writer let next_read_operation t = if Reader.is_closed t.reader then shutdown_reader t; match Reader.next t.reader with | (`Read | `Close) as operation -> operation | `Error e -> report_error t e; (match e with | ConnectionError _ -> (* From RFC7540§5.4.1: * A connection error is any error that prevents further processing * of the frame layer or corrupts any connection state. *) `Close | StreamError _ -> (* From RFC7540§5.4.2: * A stream error is an error related to a specific stream that does * not affect processing of other streams. *) `Read) let read t bs ~off ~len = Reader.read_with_more t.reader bs ~off ~len Incomplete let unexpected_eof t = Scheduler.iter ~f:(fun (Stream { descriptor; _ }) -> match descriptor.state with | Idle | Reserved _ | Closed _ -> () | Active _ -> Respd.report_error descriptor (`Malformed_response "unexpected eof") ProtocolError) t.streams; Queue.iter (fun f -> f (Error `EOF)) t.pending_pings; report_connection_error t ~reason:"unexpected eof" ProtocolError let read_eof t bs ~off ~len = let bytes_read = Reader.read_with_more t.reader bs ~off ~len Complete in unexpected_eof t; bytes_read (* XXX(anmonteiro): this function is here to please the Gluten `RUNTIME` * interface. * * We don't expect this function to ever be called. H2 never issues `Yield` * commands because the connection is multiplexed, and it's therefore always * looking to read frames from the peer. *) let yield_reader _t k = k () let next_write_operation t = flush_request_body t; Writer.next t.writer let yield_writer t k = Writer.on_wakeup_writer t.writer k let report_write_result t result = Writer.report_result t.writer result