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(** The frame-dispatch path shared by the I/O-free {!Server} and {!Client}.
35
36 Both endpoints handle inbound frames, the SETTINGS/PING/WINDOW_UPDATE
37 exchanges, CONTINUATION accumulation, receive-window bookkeeping and
38 HEADERS/DATA framing identically. {!Make} captures that shared path,
39 parametrised over the per-role differences: the event ADT each endpoint
40 surfaces, whether a client connection preface is consumed, and how an
41 inbound header block / DATA / RST_STREAM / GOAWAY maps to an event. *)
42
43module type ROLE = sig
44 type event
45 (** The endpoint's event ADT (a request on the server, a response on the
46 client). *)
47
48 val role : Connection.role
49 (** The endpoint's role on the connection. *)
50
51 val consume_preface : bool
52 (** Whether to expect and consume a client connection preface before any frame
53 (true for a server, false for a client). *)
54
55 val ensure_stream :
56 Connection.t -> int32 -> (Connection.t, Frame.error_code * string) result
57 (** Register a stream the peer is opening (the server), returning the updated
58 connection, or return it unchanged when the stream is one we opened (the
59 client). An id that is not a valid new peer stream -- even (server space)
60 or not strictly greater than the highest peer id seen -- is a connection
61 error (RFC 9113 sec 5.1.1 / 5.4.1): the [Error (code, msg)] then drives a
62 GOAWAY rather than admitting the offending frame. *)
63
64 val message_opened : Connection.t -> int32 -> bool
65 (** Whether [stream_id] has already received the header block that opens its
66 message: the request block on a server, the final (non-informational)
67 response block on a client -- interim 1xx blocks do not open it (RFC 9113
68 sec 8.1). Exactly one further header block may follow, the trailer
69 section, and it must bear END_STREAM; one that does not makes the message
70 malformed (sec 8.1 / 8.1.1). *)
71
72 val on_headers :
73 Connection.t ->
74 stream_id:int32 ->
75 end_stream:bool ->
76 Hpack.header list ->
77 Connection.t * event
78 (** The event for a completed inbound header block, with the (possibly
79 updated) connection -- the client records response/trailer headers on the
80 stream. *)
81
82 val malformed : end_stream:bool -> Hpack.header list -> bool
83 (** Whether a decoded header block makes its message malformed (RFC 9113 sec
84 8.1), which sec 8.1.1 turns into a stream error of type PROTOCOL_ERROR.
85 The client's rule is that an informational (1xx) response must not bear
86 END_STREAM: an interim response promises a final one, so a block that both
87 is interim and ends the stream promises a response that can never arrive.
88 *)
89
90 val on_data : stream_id:int32 -> data:string -> end_stream:bool -> event
91 (** The event for an inbound DATA frame. *)
92
93 val on_stream_reset : stream_id:int32 -> code:Frame.error_code -> event
94 (** The event for an inbound RST_STREAM. *)
95
96 val on_goaway : last_stream_id:int32 -> code:Frame.error_code -> event
97 (** The event for an inbound GOAWAY. *)
98
99 val data_requires_response : bool
100 (** Whether an inbound DATA frame on a live stream is malformed until the
101 peer's opening HEADERS block has arrived (RFC 9113 sec 8.1: a message is
102 HEADERS followed by DATA). True for the client, whose peer opens its
103 response on a stream the client created; the server's peer-created streams
104 only exist once their opening HEADERS arrived. *)
105end
106
107module Make (R : ROLE) : sig
108 type t
109 (** A connection endpoint: the {!Connection.t} plus inbound buffering and the
110 in-flight header-block state. *)
111
112 val v : Connection.t -> t
113 (** [v conn] wraps [conn] as a fresh endpoint. The caller emits the
114 connection's initial bytes (the client preface / SETTINGS). *)
115
116 val conn : t -> Connection.t
117
118 val with_conn : t -> Connection.t -> t
119 (** [with_conn t conn] is [t] with its underlying connection replaced by
120 [conn]. *)
121
122 val incoming : t -> Connection.hpack -> string -> t * R.event list * string
123 (** [incoming t hpack input] feeds inbound bytes and returns the advanced
124 endpoint, the events observed and the bytes to write back (acks,
125 flow-control WINDOW_UPDATE, or a GOAWAY on a connection error). Partial
126 trailing frames are buffered. [hpack] is the caller-owned codec resource
127 lent for decoding header blocks. *)
128
129 val send_headers :
130 t ->
131 Connection.hpack ->
132 stream_id:int32 ->
133 ?end_stream:bool ->
134 Hpack.header list ->
135 t * string
136 (** [send_headers t hpack ~stream_id headers] is the advanced endpoint and the
137 bytes for a HEADERS frame, driving the stream's send-side state. *)
138
139 val data : t -> stream_id:int32 -> ?end_stream:bool -> Rope.t -> t * Rope.t
140 (** [data t ~stream_id payload] queues [payload] for sending and returns the
141 advanced endpoint and the DATA frame(s) the send window admits right now,
142 split to the peer's maximum frame size (END_STREAM on the final frame when
143 [end_stream]). Any remainder stays queued and is returned by later
144 {!incoming} calls as inbound WINDOW_UPDATEs credit the window, so a sender
145 never emits more DATA than the peer has advertised room for (RFC 9113 sec
146 6.9). *)
147
148 val send_window : t -> stream_id:int32 -> int
149 (** [send_window t ~stream_id] is the bytes that may be sent on [stream_id]
150 now: the smaller of the connection and stream send windows (0 if the
151 stream is gone). *)
152
153 val pending_send : t -> stream_id:int32 -> int
154 (** [pending_send t ~stream_id] is the bytes queued on [stream_id] but not yet
155 emitted. *)
156
157 val trailers :
158 t -> Connection.hpack -> stream_id:int32 -> Hpack.header list -> t * string
159 (** [trailers t hpack ~stream_id headers] is the advanced endpoint and a
160 trailing HEADERS frame with END_STREAM. *)
161
162 val reset : t -> stream_id:int32 -> Frame.error_code -> t * string
163 (** [reset t ~stream_id code] is the advanced endpoint and the RST_STREAM
164 frame for [stream_id]. *)
165
166 val goaway : t -> Frame.error_code -> string -> t * string
167 (** [goaway t code debug] is the advanced endpoint and the GOAWAY frame; it
168 marks the connection closing. *)
169
170 val pp : Format.formatter -> t -> unit
171end