HTTP/2 framing, HPACK and connection state, I/O-free
1(*---------------------------------------------------------------------------
2 Copyright (c) 2019 Antonio Nuno Monteiro. Copyright (c) 2025 Anil Madhavapeddy
3 <anil@recoil.org>.
4
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9
10 1. Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
12
13 2. Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
16
17 3. Neither the name of the copyright holder nor the names of its contributors
18 may be used to endorse or promote products derived from this software without
19 specific prior written permission.
20
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 SPDX-License-Identifier: BSD-3-Clause
32 ---------------------------------------------------------------------------*)
33
34(** HTTP/2 Stream State Machine per RFC 9113 Section 5.1.
35
36 This module implements the HTTP/2 stream lifecycle as defined in
37 {{:https://datatracker.ietf.org/doc/html/rfc9113#section-5.1}RFC 9113
38 Section 5.1}.
39
40 {2 Stream States}
41
42 {v
43 +--------+
44 send PP | | recv PP
45 ,--------+ idle +--------.
46 / | | \
47 v +--------+ v
48 +----------+ | +----------+
49 | | | send H / | |
50 ,------+ reserved | | recv H | reserved +------.
51 | | (local) | | | (remote) | |
52 | +---+------+ v +------+---+ |
53 | | +--------+ | |
54 | | recv ES | | send ES | |
55 | send H | ,-------+ open +-------. | recv H |
56 | | / | | \ | |
57 | v v +---+----+ v v |
58 | +----------+ | +----------+ |
59 | | half- | | | half- | |
60 | | closed | | send R / | closed | |
61 | | (remote) | | recv R | (local) | |
62 | +----+-----+ | +-----+----+ |
63 | | | | |
64 | | send ES / | recv ES / | |
65 | | send R / v send R / | |
66 | | recv R +--------+ recv R | |
67 | send R / `----------->| |<-----------' send R / |
68 | recv R | closed | recv R |
69 `----------------------->| |<-----------------------'
70 +--------+
71 v}
72
73 {2 Usage}
74
75 {[
76 open H2
77
78 let demo () =
79 let stream = Stream.v 1l in
80 match
81 Stream.apply_event stream (Stream.Send_headers { end_stream = false })
82 with
83 | Ok _stream -> ()
84 | Error (_code, _msg) -> ()
85 ]} *)
86
87(** {1 Types} *)
88
89(** Why a stream was closed. *)
90type closed_reason =
91 | Finished (** Both endpoints sent END_STREAM. *)
92 | Reset_by_us of Frame.error_code (** We sent RST_STREAM. *)
93 | Reset_by_them of Frame.error_code (** Peer sent RST_STREAM. *)
94
95(** Stream state per RFC 9113 Section 5.1. *)
96type state =
97 | Idle (** Initial state. *)
98 | Reserved_local (** We sent PUSH_PROMISE. *)
99 | Reserved_remote (** Peer sent PUSH_PROMISE. *)
100 | Open (** Both sides can send frames. *)
101 | Half_closed_local (** We sent END_STREAM. *)
102 | Half_closed_remote (** Peer sent END_STREAM. *)
103 | Closed of closed_reason (** Terminal state. *)
104
105(** Events that cause state transitions. *)
106type event =
107 | Send_headers of { end_stream : bool }
108 | Recv_headers of { end_stream : bool }
109 | Send_data of { end_stream : bool }
110 | Recv_data of { end_stream : bool }
111 | Send_push_promise
112 | Recv_push_promise
113 | Send_rst_stream of Frame.error_code
114 | Recv_rst_stream of Frame.error_code
115 | Send_end_stream
116 | Recv_end_stream
117
118type t
119(** A single HTTP/2 stream. *)
120
121(** {1 Stream Creation} *)
122
123val default_initial_window_size : int
124(** Default initial flow control window size: 65535 bytes. *)
125
126val v : ?initial_send_window:int -> ?initial_recv_window:int -> int32 -> t
127(** [v ?initial_send_window ?initial_recv_window id] creates a new stream.
128 Stream starts in {!constructor-Idle} state. *)
129
130(** {1 Stream Properties} *)
131
132val id : t -> int32
133(** [id t] returns the stream identifier. *)
134
135val state : t -> state
136(** [state t] returns the current stream state. *)
137
138val is_idle : t -> bool
139(** [is_idle t] returns true if stream is in Idle state. *)
140
141val is_active : t -> bool
142(** [is_active t] returns true if stream is Open or Half-closed. *)
143
144val is_open : t -> bool
145(** [is_open t] returns true if stream is fully Open. *)
146
147val is_closed : t -> bool
148(** [is_closed t] returns true if stream is Closed. *)
149
150val can_send : t -> bool
151(** [can_send t] returns true if we can send frames on this stream. *)
152
153val can_recv : t -> bool
154(** [can_recv t] returns true if we can receive frames on this stream. *)
155
156(** {1 State Transitions} *)
157
158val transition : state -> event -> (state, Frame.error_code * string) result
159(** [transition state event] computes the new state after applying [event].
160 Returns [Ok new_state] or [Error (code, msg)] if transition is invalid. *)
161
162val apply_event : t -> event -> (t, Frame.error_code * string) result
163(** [apply_event t event] applies [event] to stream [t], returning the advanced
164 stream, or [Error (code, msg)] on protocol error. *)
165
166val reset : t -> Frame.error_code -> t
167(** [reset t code] is [t] reset with the given error code. *)
168
169(** {1 Flow Control} *)
170
171val send_window : t -> int
172(** [send_window t] returns bytes available in send window. *)
173
174val recv_window : t -> int
175(** [recv_window t] returns bytes available in receive window. *)
176
177val initial_recv_window : t -> int
178(** [initial_recv_window t] returns the initial receive window size. *)
179
180val consume_send_window : t -> int -> int * t
181(** [consume_send_window t bytes] consumes up to [bytes] from the send window,
182 returning the number actually consumed and the advanced stream. *)
183
184val max_flow_window : int64
185(** The RFC 9113 s6.9.1 flow-control window bound, 2^31-1, as an int64 so the
186 comparison is exact on every int width. *)
187
188val credit_send_window : t -> int -> (t, Frame.error_code * string) result
189(** [credit_send_window t increment] adds [increment] to the send window,
190 returning the advanced stream or an error on overflow. *)
191
192val consume_recv_window : t -> int -> t
193(** [consume_recv_window t bytes] consumes [bytes] from the receive window. *)
194
195val credit_recv_window : t -> int -> t
196(** [credit_recv_window t increment] adds [increment] to the receive window. *)
197
198val update_initial_window_size :
199 t -> int -> (t, Frame.error_code * string) result
200(** [update_initial_window_size t new_size] updates the initial window size from
201 a SETTINGS frame, adjusting the current window by the delta. *)
202
203(** {1 Send Queue}
204
205 Outbound DATA the application has handed over but flow control has not yet
206 permitted on the wire. The endpoint drains it as the send window opens. *)
207
208val enqueue_send : t -> ?end_stream:bool -> Rope.t -> t
209(** [enqueue_send t ?end_stream payload] appends [payload] to the send queue.
210 [end_stream] records that the queued data is the last to send. *)
211
212val pending_send : t -> int
213(** [pending_send t] is the number of queued bytes not yet emitted. *)
214
215val send_fin : t -> bool
216(** [send_fin t] is whether END_STREAM has been requested for the queued data.
217*)
218
219val fin_sent : t -> bool
220(** [fin_sent t] is whether the END_STREAM DATA frame has been emitted. *)
221
222val mark_fin_sent : t -> t
223(** [mark_fin_sent t] records that the END_STREAM DATA frame has been emitted.
224*)
225
226val take_send : t -> int -> Rope.t * t
227(** [take_send t n] removes the first [n] queued bytes ([n] is clamped to
228 {!pending_send}), returning them and the advanced stream. *)
229
230(** {1 Stream Identifiers} *)
231
232val connection_stream_id : int32
233(** Stream ID 0 is used for connection-level frames. *)
234
235val is_client_initiated : int32 -> bool
236(** [is_client_initiated id] returns true if [id] is odd (client-initiated). *)
237
238val is_server_initiated : int32 -> bool
239(** [is_server_initiated id] returns true if [id] is even and non-zero. *)
240
241val is_valid_id : int32 -> bool
242(** [is_valid_id id] returns true if [id] is greater than 0. *)
243
244val is_connection_stream : int32 -> bool
245(** [is_connection_stream id] returns true if [id] is 0. *)
246
247(** {1 Headers} *)
248
249val request_headers : t -> Hpack.header list option
250(** [request_headers t] returns the request headers if set. *)
251
252val set_request_headers : t -> Hpack.header list -> t
253(** [set_request_headers t headers] sets the request headers. *)
254
255val response_headers : t -> Hpack.header list option
256(** [response_headers t] returns the response headers if set. *)
257
258val set_response_headers : t -> Hpack.header list -> t
259(** [set_response_headers t headers] sets the response headers. *)
260
261val trailers : t -> Hpack.header list option
262(** [trailers t] returns the trailers if set. *)
263
264val set_trailers : t -> Hpack.header list -> t
265(** [set_trailers t headers] sets the trailers. *)
266
267(** {1 Pretty Printing} *)
268
269val pp_state : Format.formatter -> state -> unit
270(** Pretty print stream state. *)
271
272val pp_closed_reason : Format.formatter -> closed_reason -> unit
273(** Pretty print closed reason. *)
274
275val string_of_state : state -> string
276(** Convert state to string. *)
277
278val pp : Format.formatter -> t -> unit
279(** Pretty print a stream. *)