Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file decoder.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299typedecoder={buffer:Bytes.t;mutablepos:int;mutablemax:int}letio_buffer_size=65536letcreate()={buffer=Bytes.createio_buffer_size;pos=0;max=0}letdecoder_fromx=letmax=String.lengthxinletbuffer=Bytes.of_stringxin{buffer;pos=0;max}typeerror=[`End_of_input|`Expected_charofchar|`Unexpected_charofchar|`Expected_stringofstring|`Expected_eol|`Expected_eol_or_space|`No_enough_space|`Unexpected_end_of_input|`Assert_predicateofchar->bool|`Invalid_pkt_line]letpp_errorppf=function|`End_of_input->Fmt.stringppf"End of input"|`Expected_charchr->Fmt.pfppf"Expected char: %02x"(Char.codechr)|`Unexpected_charchr->Fmt.pfppf"Unexpected char: %02x"(Char.codechr)|`Expected_strings->Fmt.pfppf"Expected_string: %s"s|`Expected_eol->Fmt.stringppf"Expected end-of-line"|`Expected_eol_or_space->Fmt.stringppf"Expected end-of-line or space"|`No_enough_space->Fmt.stringppf"No enough space"|`Unexpected_end_of_input->Fmt.stringppf"Unexpected end of input"|`Assert_predicate_->Fmt.stringppf"Assert predicate"|`Invalid_pkt_line->Fmt.stringppf"Invalid PKT-line"type'errinfo={error:'err;buffer:Bytes.t;committed:int;(** # bytes already processed *)}type('v,'err)state=|Doneof'v|Readof{buffer:Bytes.t;off:int;len:int;continue:int->('v,'err)state;eof:unit->('v,'err)state;}|Errorof'errinfoexceptionLeaveoferrorinfoletreturn(typev)(v:v)_:(v,'err)state=Donevletsafe:(decoder->('v,([>error]as'err))state)->decoder->('v,'err)state=funkdecoder->trykdecoderwithLeave{error=#erroraserror;buffer;committed}->Error{error=(error:>'err);buffer;committed}letend_of_inputdecoder=decoder.maxletpeek_chardecoder=ifdecoder.pos<end_of_inputdecoderthenSome(Bytes.unsafe_getdecoder.bufferdecoder.pos)elseNone(* XXX(dinosaure): in [angstrom] world, [peek_char] should try to read input
again. However, SMTP is a line-directed protocol where we can ensure to
have the full line at the top (with a queue) instead to have a
systematic check (which slow-down the process). *)letleave_with(decoder:decoder)error=raise(Leave{error;buffer=decoder.buffer;committed=decoder.pos})letfail(decoder:decoder)error=Error{error;buffer=decoder.buffer;committed=decoder.pos}letstringstrdecoder=letidx=ref0inletlen=String.lengthstrinwhiledecoder.pos+!idx<end_of_inputdecoder&&!idx<len&&Char.equal(Bytes.unsafe_getdecoder.buffer(decoder.pos+!idx))(String.unsafe_getstr!idx)doincridxdone;if!idx=lenthendecoder.pos<-decoder.pos+lenelseleave_withdecoder(`Expected_stringstr)letjunk_chardecoder=ifdecoder.pos<end_of_inputdecoderthendecoder.pos<-decoder.pos+1elseleave_withdecoder`End_of_inputletwhile1predicatedecoder=letidx=refdecoder.posinwhile!idx<end_of_inputdecoder&&predicate(Bytes.unsafe_getdecoder.buffer!idx)doincridxdone;if!idx-decoder.pos=0thenleave_withdecoder(`Assert_predicatepredicate);letsub=decoder.buffer,decoder.pos,!idx-decoder.posin(* XXX(dinosaure): avoid sub-string operation. *)decoder.pos<-!idx;subletat_least_one_linedecoder=letpos=refdecoder.posinletchr=ref'\000'inlethas_cr=reffalseinwhile!pos<end_of_inputdecoder&&(chr:=Bytes.unsafe_getdecoder.buffer!pos;not(!chr='\n'&&!has_cr))dohas_cr:=!chr='\r';incrposdone;!pos<decoder.max&&!chr='\n'&&!has_cr(** reads off 4 bytes from [decoder.buffer] starting at [decoder.pos] and interprets read
bytes as hex and converts to int.
Why unsafe:
@raise Invalid_argument if there are no 4 bytes to read, i.e.,
[decoder.max - decoder.pos < 4] *)letpkt_len_unsafe(decoder:decoder)=lethex=Bytes.of_string"0x0000"inBytes.blitdecoder.bufferdecoder.poshex24;int_of_string(Bytes.unsafe_to_stringhex)(* no header *)letat_least_one_pktdecoder=letlen=decoder.max-decoder.posiniflen>=4thenletpkt_len=pkt_len_unsafedecoderinlen>=pkt_lenelsefalse(* no header *)letget_pkt_lendecoder=letlen=decoder.max-decoder.posiniflen>=4thenletpkt_len=pkt_len_unsafedecoderinSomepkt_lenelseNone(* XXX(dinosaure): to be able to do a /gentle close/, we do a hack.
It seems that [git] is a bit /obtuse/ when it receives something
which is not expected.
For example:
C> 0009done\n
C> 0000
Where [git] expects only:
C> 0009done\n
seems to cause a drastic connection close by the server when we want to
download the PACK file. In such case, our decoder will be stick on the loop
and waiting more where it received a partial chunk of the current /PKT/.
So we provide an [eof] function which will (depends on [strict]):
- return an error [`End_of_input] as usual
- reformat the current /PKT/ to be able to emit the partial chunk
to another process.
The second case, we are able to unlock the ability to properly close the
connection and to the other process (eg. [carton]) that we can not have more
that what we have (more precisely, from a given /pusher/ to the stream used
by the other process, we are able to do [pusher None]). By this way, we are
able to unlock the /waiting-state/ of the other process. Then, in our side,
we properly call [Flow.close].
However, the error is a protocol error. The second branch [reliable_pkt] should
never appear! It permits for us to gently close the connection and fallback
the protocol error to another layer (eg. [carton] when it received finally a
__not-full__ PACK file). The goal is to be more resilient at this layer. *)letreliable_pktkdecoder()=matchget_pkt_lendecoderwith|Some_len->lethdr=Fmt.str"%04X"(decoder.max-decoder.pos)inBytes.blit_stringhdr0decoder.bufferdecoder.pos4;(* unsafe! *)kdecoder|None->Bytes.blit_string"0000"0decoder.bufferdecoder.pos4;decoder.max<-decoder.pos+4;kdecoderletprompt:?strict:bool->(decoder->('v,([>error]as'err))state)->decoder->('v,'err)state=fun?(strict=true)kdecoder->letcompressdecoder=letrest=decoder.max-decoder.posinBytes.unsafe_blitdecoder.bufferdecoder.posdecoder.buffer0rest;decoder.max<-rest;decoder.pos<-0inifdecoder.pos>0thencompressdecoder;letrecgooff=tryletat_least_one_pkt=at_least_one_pkt{decoderwithmax=off}inifoff=Bytes.lengthdecoder.buffer&&decoder.pos>0&¬at_least_one_pktthenfaildecoder`No_enough_spaceelseifnotat_least_one_pkt(* XXX(dinosaure): we make a new decoder here and we did __not__ set
[decoder.max] owned by end-user, and this is exactly what we want. *)thenleteof=ifstrictthenfun()->faildecoder`End_of_inputelse(decoder.max<-off;reliable_pktkdecoder)inRead{buffer=decoder.buffer;off;len=Bytes.lengthdecoder.buffer-off;continue=(funlen->go(off+len));eof;}else(decoder.max<-off;safekdecoder)with|_exn(* XXX(dinosaure): [at_least_one_pkt] can raise an exception. *)->faildecoder`Invalid_pkt_lineingodecoder.maxletpeek_pktdecoder=letlen=pkt_len_unsafedecoderiniflen>=4thendecoder.buffer,decoder.pos+4,len-4elsedecoder.buffer,decoder.pos+4,0letjunk_pktdecoder=letlen=pkt_len_unsafedecoderiniflen<4thendecoder.pos<-decoder.pos+4elsedecoder.pos<-decoder.pos+lenletpeek_while_eoldecoder=letidx=refdecoder.posinletchr=ref'\000'inlethas_cr=reffalseinwhile!idx<end_of_inputdecoder&&(chr:=Bytes.unsafe_getdecoder.buffer!idx;not(!chr='\n'&&!has_cr))dohas_cr:=!chr='\r';incridxdone;if!idx<end_of_inputdecoder&&!chr='\n'&&!has_crthen(assert(!idx+1-decoder.pos>1);decoder.buffer,decoder.pos,!idx+1-decoder.pos)elseleave_withdecoder`Expected_eolletpeek_while_eol_or_spacedecoder=letidx=refdecoder.posinletchr=ref'\000'inlethas_cr=reffalseinwhile!idx<end_of_inputdecoder&&(chr:=Bytes.unsafe_getdecoder.buffer!idx;(not(!chr='\n'&&!has_cr))&&!chr<>' ')dohas_cr:=!chr='\r';incridxdone;if!idx<end_of_inputdecoder&&((!chr='\n'&&!has_cr)||!chr=' ')thendecoder.buffer,decoder.pos,!idx+1-decoder.poselseleave_withdecoder`Expected_eol_or_space