mte/unikernel/duniverse/ocaml-h2/lib/settings.ml

292 lines
11 KiB
OCaml
Raw Normal View History

2025-11-11 02:07:51 +01:00
(*----------------------------------------------------------------------------
* Copyright (c) 2019-2020 António Nuno 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 copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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 WindowSize = struct
type t = int32
(* 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. *)
let default_initial_window_size = 65535l
(* From RFC7540§6.9:
* The legal range for the increment to the flow-control window is 1 to
* 2^31-1 (2,147,483,647) octets. *)
let max_window_size = Int32.max_int
(* Ideally `n` here would be an unsigned 32-bit integer, but OCaml doesn't
* support them. We avoid introducing a new dependency on an unsigned integer
* library by letting it overflow at parse time and checking if bit 31 is set
* here, since * `Window.max_window_size` is never allowed to be above
* 2^31-1 (see `max_window_size` above).
* See http://caml.inria.fr/pub/ml-archives/caml-list/2004/07/f1c483068cc62075c916f7ad7d640ce0.fr.html
* for more info. *)
let is_window_overflow n = Util.test_bit_int32 n 31
end
type setting =
| HeaderTableSize of int
| EnablePush of int
| MaxConcurrentStreams of int32
| InitialWindowSize of int32
| MaxFrameSize (* this means payload size *) of int
| MaxHeaderListSize of int
type settings_list = setting list
(* From RFC7540§6.5.1:
* The payload of a SETTINGS frame consists of zero or more parameters,
* each consisting of an unsigned 16-bit setting identifier and an
* unsigned 32-bit value. *)
let octets_per_setting = 6
let serialize_key = function
| HeaderTableSize _ -> 0x1
| EnablePush _ -> 0x2
| MaxConcurrentStreams _ -> 0x3
| InitialWindowSize _ -> 0x4
| MaxFrameSize _ -> 0x5
| MaxHeaderListSize _ -> 0x6
let check_value ~is_client = function
| EnablePush v ->
if v <> 0 && v <> 1
then
(* From RFC7540§6.5.2
* The initial value is 1, which indicates that server push is
* permitted. Any value other than 0 or 1 MUST be treated as a
* connection error (Section 5.4.1) of type PROTOCOL_ERROR. *)
Error
Error.(
ConnectionError (ProtocolError, "SETTINGS_ENABLE_PUSH must be 0 or 1"))
else if is_client && v = 1
then
(* From RFC7540§8.2:
* Clients MUST reject any attempt to change the
* SETTINGS_ENABLE_PUSH setting to a value other than 0 by
* treating the message as a connection error (Section 5.4.1) of
* type PROTOCOL_ERROR. *)
Error
Error.(
ConnectionError
(ProtocolError, "Server must not try to enable SETTINGS_ENABLE_PUSH"))
else Ok ()
| InitialWindowSize v when WindowSize.is_window_overflow v ->
(* From RFC7540§6.5.2
* Values above the maximum flow-control window size of 2^31-1 MUST be
* treated as a connection error (Section 5.4.1) of type
* FLOW_CONTROL_ERROR. *)
Error
Error.(
ConnectionError
( FlowControlError
, Format.sprintf
"Window size must be less than or equal to %ld"
WindowSize.max_window_size ))
| MaxFrameSize v when v < 16384 || v > 16777215 ->
(* From RFC7540§6.5.2
* The initial value is 214 (16,384) octets. The value advertised by an
* endpoint MUST be between this initial value and the maximum allowed
* frame size (224-1 or 16,777,215 octets), inclusive. Values outside
* this range MUST be treated as a connection error (Section 5.4.1) of
* type PROTOCOL_ERROR. *)
Error
Error.(
ConnectionError
(ProtocolError, "Max frame size must be in the 16384 - 16777215 range"))
| _ -> Ok ()
(* Check incoming settings and report an error if any. *)
let check_settings_list ?(is_client = false) settings =
let rec loop = function
| [] -> Ok ()
| x :: xs ->
(match check_value ~is_client x with
| Ok () -> loop xs
| Error _ as err -> err)
in
loop settings
type t =
{ header_table_size : int
; enable_push : bool
; max_concurrent_streams : int32
; (* Indicates the amount tokens the peer allows an H2 endpoint to send. *)
initial_window_size : WindowSize.t
; max_frame_size : int
; max_header_list_size : int option
}
(* From RFC7540§11.3 *)
let default =
{ header_table_size = 0x1000
; enable_push =
true
(* From RFC7540§6.5.2:
* SETTINGS_MAX_CONCURRENT_STREAMS (0x3): [...] Initially, there is no
* limit to this value. *)
; max_concurrent_streams = Int32.max_int
; initial_window_size = WindowSize.default_initial_window_size
; max_frame_size = 0x4000
; max_header_list_size = None
}
let settings_for_the_connection settings =
let settings_list =
if settings.max_frame_size <> default.max_frame_size
then [ MaxFrameSize settings.max_frame_size ]
else []
in
let settings_list =
if settings.max_concurrent_streams <> default.max_concurrent_streams
then MaxConcurrentStreams settings.max_concurrent_streams :: settings_list
else settings_list
in
let settings_list =
if settings.initial_window_size <> default.initial_window_size
then
(* FIXME: don't convert *)
InitialWindowSize settings.initial_window_size :: settings_list
else settings_list
in
let settings_list =
if settings.enable_push <> default.enable_push
then EnablePush (if settings.enable_push then 1 else 0) :: settings_list
else settings_list
in
settings_list
let parse_settings_payload num_settings =
let open Angstrom in
let rec parse_inner acc remaining =
(* From RFC7540§6.5.3:
* The values in the SETTINGS frame MUST be processed in the order
* they appear, with no other frame processing between values. *)
if remaining <= 0
then return (List.rev acc)
else
lift2
(fun k (v : int32) ->
match k with
| 0x1 -> HeaderTableSize (Int32.to_int v) :: acc
| 0x2 -> EnablePush (Int32.to_int v) :: acc
| 0x3 -> MaxConcurrentStreams v :: acc
| 0x4 -> InitialWindowSize v :: acc
| 0x5 -> MaxFrameSize (Int32.to_int v) :: acc
| 0x6 -> MaxHeaderListSize (Int32.to_int v) :: acc
| _ ->
(* Note: This ignores unknown settings.
*
* From RFC7540§6.5.3:
* Unsupported parameters MUST be ignored.
*)
acc)
BE.any_uint16
BE.any_int32
>>= fun acc' -> parse_inner acc' (remaining - 1)
in
parse_inner [] num_settings
let write_settings_payload t settings_list =
let open Faraday in
List.iter
(fun setting ->
(* From RFC7540§6.5.1:
* The payload of a SETTINGS frame consists of zero or more parameters,
* each consisting of an unsigned 16-bit setting identifier and an
* unsigned 32-bit value. *)
BE.write_uint16 t (serialize_key setting);
match setting with
| MaxConcurrentStreams value | InitialWindowSize value ->
BE.write_uint32 t value
| HeaderTableSize value
| EnablePush value
| MaxFrameSize value
| MaxHeaderListSize value ->
BE.write_uint32 t (Int32.of_int value))
settings_list
let of_settings_list settings =
List.fold_left
(fun (acc : t) item ->
match item with
| HeaderTableSize x -> { acc with header_table_size = x }
| EnablePush x -> { acc with enable_push = x = 1 }
| MaxConcurrentStreams x -> { acc with max_concurrent_streams = x }
| InitialWindowSize new_val -> { acc with initial_window_size = new_val }
| MaxFrameSize x -> { acc with max_frame_size = x }
| MaxHeaderListSize x -> { acc with max_header_list_size = Some x })
default
settings
let of_base64 encoded =
match Base64.decode ~alphabet:Base64.uri_safe_alphabet encoded with
| Ok settings_payload ->
let settings_payload_length =
String.length settings_payload / octets_per_setting
in
(match
Angstrom.parse_string
~consume:All
(parse_settings_payload settings_payload_length)
settings_payload
with
| Ok settings -> Ok (of_settings_list settings)
| Error _ as e -> e)
| Error (`Msg msg) -> Error msg
let to_base64 t =
let settings = settings_for_the_connection t in
let faraday = Faraday.create (List.length settings * 6) in
write_settings_payload faraday settings;
let settings_hex = Faraday.serialize_to_string faraday in
match Base64.encode ~alphabet:Base64.uri_safe_alphabet settings_hex with
| Ok r -> Ok r
| Error (`Msg msg) -> Error msg
let pp_hum formatter t =
let pp_elem formatter setting =
let key, value =
match setting with
| HeaderTableSize v -> "HEADER_TABLE_SIZE", Int64.of_int v
| EnablePush v -> "ENABLE_PUSH", Int64.of_int v
| MaxConcurrentStreams v -> "MAX_CONCURRENT_STREAMS", Int64.of_int32 v
| InitialWindowSize v -> "INITIAL_WINDOW_SIZE", Int64.of_int32 v
| MaxFrameSize v -> "MAX_FRAME_SIZE", Int64.of_int v
| MaxHeaderListSize v -> "MAX_HEADER_LIST_SIZE", Int64.of_int v
in
Format.fprintf formatter "@[(%S %Ld)@]" key value
in
Format.fprintf formatter "@[(";
Format.pp_print_list pp_elem formatter (settings_for_the_connection t);
Format.fprintf formatter ")@]"