123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197open!CoreopenAsynctypet={opcode:Opcode.t;final:bool;content:string}[@@derivingsexp_of]moduleError=structtypet={code:Connection_close_reason.t;message:string}[@@derivingsexp_of]end(* Extensions aren't implemented *)letcreate~opcode?(final=true)content={opcode;final;content}(* See rfc6455 - 5.5.1
The Close frame MAY contain a body (the "Application data" portion of
the frame) that indicates a reason for closing, such as an endpoint
shutting down, an endpoint having received a frame too large, or an
endpoint having received a frame that does not conform to the format
expected by the endpoint. If there is a body, the first two bytes of
the body MUST be a 2-byte unsigned integer (in network byte order)
representing a status code with value /code/ defined in Section 7.4.
Following the 2-byte integer, the body MAY contain UTF-8-encoded data
with value /reason/, the interpretation of which is not defined by
this specification. This data is not necessarily human readable but
may be useful for debugging or passing information relevant to the
script that opened the connection. As the data is not guaranteed to
be human readable, clients MUST NOT show it to end users. *)letcreate_close~code?finalcontent=letlen=String.lengthcontentinletcontent'=Bytes.create(len+2)inBytes.From_string.blit~src:content~src_pos:0~dst:content'~dst_pos:2~len;Binary_packing.pack_unsigned_16_big_endian~buf:content'~pos:0code;create~opcode:Close?final(Bytes.to_stringcontent');;letbit_is_setidxv=(vlsridx)land1=1letset_bitvidxb=ifbthenvlor(1lslidx)elsevlandlnot(1lslidx)letint_valueshiftlenv=(vlsrshift)land((1lsllen)-1)letrandom_bytes~len=Bytes.initlen~f:(fun_->Char.of_int_exn(Random.int128))letxor_with_maskmaskmsg=fori=0toBytes.lengthmsg-1doBytes.setmsgi(Char.of_int_exn(Char.to_int(Bytes.getmask(imod4))lxorChar.to_int(Bytes.getmsgi)))done;;letwrite_int16writern=letbuf=Bytes.create2inBinary_packing.pack_unsigned_16_big_endian~buf~pos:0n;Writer.write_bytes~pos:0~len:2writerbuf;;letwrite_int64writern=letbuf=Bytes.create8inBinary_packing.pack_signed_64_big_endian~buf~pos:0n;Writer.write_bytes~pos:0~len:8writerbuf;;letwrite_frame~maskedwriterframe=ifWriter.is_closedwriterthen()else(letcontent=Bytes.of_stringframe.contentinletlen=Bytes.lengthcontentinletopcode=Opcode.to_intframe.opcodeinletpayload_length=matchlenwith|nwhenn<126->len|nwhenn<1lsl16->126|_->127inlethdr=0inlethdr=set_bithdr15frame.finalinlethdr=hdrlor(opcodelsl8)inlethdr=set_bithdr7maskedinlethdr=hdrlorpayload_lengthinletbuf=Bytes.create2inBinary_packing.pack_unsigned_16_big_endian~buf~pos:0hdr;Writer.write_bytes~len:2~pos:0writerbuf;(matchlenwith|nwhenn<126->()|nwhenn<1lsl16->write_int16writern|n->write_int64writer(Int64.of_intn));ifmaskedthen(letmask=random_bytes~len:4inWriter.write_bytes~pos:0~len:4writermask;xor_with_maskmaskcontent);Writer.write_bytes~pos:0~lenwritercontent);;leterror~code~reason=Error{Error.code;message=reason}leterror_deferred~code~reason=Deferred.return(error~code~reason)letread_int64reader=letbuf=Bytes.create8inmatch%mapReader.really_readreader~len:8bufwith|`Ok->Ok(Binary_packing.unpack_signed_64_big_endian~buf~pos:0)|`Eof_->error~code:Connection_close_reason.Protocol_error~reason:"Did not receive correct length";;letread_int16reader=letbuf=Bytes.create2inmatch%mapReader.really_readreader~len:2bufwith|`Ok->Ok(Int64.of_int(Binary_packing.unpack_unsigned_16_big_endian~buf~pos:0))|`Eof_->error~code:Connection_close_reason.Protocol_error~reason:"Did not receive correct length";;letread_this_many_bytesreaderout~len~len_mismatch_error_text:(unit,Error.t)Result.tDeferred.t=ifReader.is_closedreaderthenerror_deferred~code:Connection_close_reason.Closed_abnormally~reason:"pipe was closed"else(match%bindReader.really_readreader~lenoutwith|`Ok->Deferred.return(Ok())|`Eofn->error_deferred~code:Connection_close_reason.Protocol_error~reason:(len_mismatch_error_textn));;letread_framereader=letopenDeferred.Result.Let_syntaxinletbuf=Bytes.create2inletread_this_many_bytes=read_this_many_bytesreaderinlet%bind()=read_this_many_bytesbuf~len_mismatch_error_text:(sprintf"Expected 2 byte header, got %d")~len:2inletheader_part1=Char.to_int(Bytes.getbuf0)inletheader_part2=Char.to_int(Bytes.getbuf1)inletfinal=bit_is_set7header_part1inletopcode=Opcode.of_int(int_value04header_part1)inletmasked=bit_is_set7header_part2inletlength=int_value07header_part2inlet%bindpayload_length=matchlengthwith|126->read_int16reader|127->read_int64reader|iwheni<126->return(Int64.of_inti)|n->error_deferred~code:Connection_close_reason.Protocol_error~reason:(sprintf"Invalid payload length %d"n)inletpayload_length=Int64.to_int_exnpayload_lengthinletmask=Bytes.create4inlet%bind()=ifmaskedthenread_this_many_bytesmask~len_mismatch_error_text:(sprintf"Expected 4 byte mask, got %d")~len:4elsereturn()inletcontent=Bytes.createpayload_lengthinlet%bind()=ifpayload_length=0thenreturn()elseread_this_many_bytescontent~len_mismatch_error_text:(funn->sprintf"Read %d bytes, expected %d bytes"npayload_length)~len:payload_lengthinifmaskedthenxor_with_maskmaskcontent;return(create~opcode~final(Bytes.to_stringcontent));;