123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159(*----------------------------------------------------------------------------
* Copyright (c) 2019 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.
*---------------------------------------------------------------------------*)moduleHpack=Dream_hpack.HpackmoduleAB=Angstrom.BufferedmoduleWriter=Serialize.Writertypepartial_headers={mutableparse_state:(Headers.t,Hpack.error)resultAB.state;end_stream:bool}type'active_peerremote_state=(* A stream is in this state when it's waiting for the peer to initiate a
* response. In practice, it only matters for the client implementation, when
* a client has opened a stream but is still waiting on the server to send
* the first bytes of the response. *)|WaitingForPeer(* A PartialHeaders state is entered when the endpoint sees the first HEADERS
* frame from the peer for a given stream. Its payload is an
* Angstrom.Buffered parse state. *)|PartialHeadersofpartial_headers(* A stream transitions from the PartialHeaders state to the FullHeaders
* state when the endpoint has finished parsing all the bytes in a group of
* HEADER / CONTINUATION frames that the peer has sent.
* This state doesn't carry any payload because the stream will immediately
* transition to the ActiveMessage state once the message has been validated
* according to RFC7540§8.1.2. *)|FullHeaders(* The ActiveMessage state carries information about the current remote
* message being processed by the endpoint. *)|ActiveMessageof'active_peertypeclosed_reason=|Finished(* TODO: we could abide by the following by either 1) having I/O runtime
* support for timers or 2) by simply counting the number of frames received
* after we've sent an RST_STREAM?
*
* From RFC7540§5.4.2:
* Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame for
* any stream. However, an endpoint MAY send additional RST_STREAM frames
* if it receives frames on a closed stream after more than a round-trip
* time. This behavior is permitted to deal with misbehaving
* implementations. *)|ResetByUsofError_code.t(* Received an RST_STREAM frame from the peer. *)|ResetByThemofError_code.ttypeclosed={reason:closed_reason(* When a stream is closed, we may want to keep it around in the hash
* table for a while (e.g. to know whether this stream was reset by the
* peer - some error handling code depends on that). We start with a
* default value, and on every writer yield we decrement it. If it
* reaches 0, the stream is finally removed from the hash table. *);mutablettl:int}type('opn,'half_closed)active_state=|Openof'opnremote_state|HalfClosedof'half_closedtype('active_state,'active,'reserved)state=|Idle|Reservedof'reserved|Activeof'active_state*'active|Closedofclosedconstraint'active_state=(_,_)active_statetype'aerror_status=|No_error|Exnofexn|Otherof{error:'a;code:Error_code.t}type('state,'error,'error_handler)stream={id:Stream_identifier.t;writer:Serialize.Writer.t;error_handler:'error_handler;mutableerror_code:'errorerror_status;mutablestate:'state(* The largest frame payload we're allowed to write. *);mutablemax_frame_size:int;on_close:active:bool->closed->unit}constraint'state=(_,_,_)stateletinitial_ttl=10letcreateid~max_frame_sizewritererror_handleron_close={id;writer;error_handler(* From RFC7540§5.1:
* idle: All streams start in the "idle" state. *);state=Idle;error_code=No_error;max_frame_size;on_close}letid{id;_}=idletis_idlet=matcht.statewithIdle->true|_->falseletis_opent=matcht.statewithActive(Open_,_)->true|_->falseletfinish_streamtreason=letactive=matcht.statewithActive_->true|_->falseinletclosed={reason;ttl=initial_ttl}int.on_close~activeclosed;t.state<-Closedclosedleterror_codet=matcht.error_codewith|Exnexn->Some(`Exnexn)|Other{error;_}->Someerror|No_error->Noneleterror_to_codeerrorerror_code=matcherrorwith|`Exnexn->Exnexn|other->Other{error=other;code=error_code}letreset_streamterror_code=letframe_info=Writer.make_frame_infot.idinWriter.write_rst_streamt.writerframe_infoerror_code;finish_streamt(ResetByUserror_code)