123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116letsrc=Logs.Src.create"mnet.fragment"moduleLog=(valLogs.src_logsrc:Logs.LOG)typet=Unsized:Ropes.unknownRopes.t->t|Sized:Diet.t*bytes->t(* A payload can be:
- an entire packet (Unfragmented)
- the start of a fragment whose end is unknown (Unsized)
- a packet that has not yet been fully received (but which size is known) (Sized)
In the case of a fragment whose size is unknown, a Rope is used so that
insertion at random locations is not costly. This Rope is in a state where we
do not know the final size ([Ropes.unknown]). We can therefore add fragments
one after the other "ad infinitum". However, we cannot add a fragment on top
of another existing one. If this is the case, the addition raises the
[Overlap] exception.
Then, if we know the last fragment, we know the final size of our packet. We
can therefore [Ropes.fixed] our ropes. This consists of creating a final buffer
in which we will copy all our fragments (a reassembly, in short). However,
this does not mean that we have received the whole of our package; there may
be holes.
It is therefore always necessary to check that the addition of new fragments
to this buffer does not overlap other already existing fragments. For this
reason, we associate with this buffer a Discrete Interval Encoding Tree that
contains all the intervals already "filled" in our buffer. A [Sized] value is
then created.
Thanks to the DIET, we can verify that the addition of all these intervals
forms a continuous final interval of our packet in its entirety. All we have
to do is make a [Diet.diff] between our current DIET and the one that
contains this final interval. If the result is empty, it means that our
current DIET contains all the intervals necessary to complete our buffer. Our
packet is complete!
Finally, there is the special case of an unfragmented packet. This
corresponds to our "happy path" where the bigstring used physically
corresponds to the one used (and filled) by Solo5. That is to say that from
Solo5 up to here, this unfragmented packet **is not** a copy.
*)letsingleton~off?(limit=false)~lenslice:t=match(off,limit)with|_,false->letstr=Slice_bstr.to_stringsliceinletempty=Ropes.(UnknownLimitless)inletropes=Ropes.insert~offstremptyinLog.debug(funm->m"+%d byte(s) %@ %d"(String.lengthstr)off);Unsizedropes|0,true->invalid_arg"Unfragmented packet"|_,true->letstr=Slice_bstr.sub_string~off:0~lensliceinletempty=Ropes.(UnknownLimitless)inletropes=Ropes.insert~offstremptyinletmax=off+String.lengthstrinLog.debug(funm->m"+%d byte(s) %@ %d (max: %d)"(String.lengthstr)offmax);letropes=Ropes.fixed~maxropesinletdiet,buf=Ropes.to_bytesropesinSized(diet,buf)letweight=function|Unsizedropes->Ropes.weightropes|Sized(_,buf)->Bytes.lengthbufexceptionOut_of_boundsexceptionOverlapexceptionToo_biglet()=Printexc.register_printer@@function|Out_of_bounds->Some"Fragment out of bounds"|Overlap->Some"Fragment overlap"|Too_big->Some"Too big"|_->Noneletinsertt~off?(limit=false)str=match(t,limit)with|Sized(diet,buf),false->letlen=String.lengthstrinifoff<0||off>Bytes.lengthbuf-lenthenraise_notraceOut_of_bounds;begintryletdiet=Diet.add~off~lendietinBytes.unsafe_blit_stringstr0bufofflen;Sized(diet,buf)with_->raise_notraceOverlapend|Unsizedropes,false->Log.debug(funm->m"+%d byte(s) %@ %d"(String.lengthstr)off);letropes=Ropes.insert~offstrropesin(* NOTE(dinosaure): actually, we can increase the ropes without limit.
This is also the case with [mirage-tcpip], which has no mechanism to
limit the [payload]. Not sure if you should put a limit or not. *)Unsizedropes|Sized_,true->failwith"Multiple MF:0 fragments"|Unsizedropes,true->letmax=off+String.lengthstrinLog.debug(funm->m"+%d byte(s) %@ %d (max: %d)"(String.lengthstr)offmax);letropes=Ropes.insert~offstrropesinletropes=Ropes.fixed~maxropesinletdiet,buf=Ropes.to_bytesropesinSized(diet,buf)letis_complete:t->bool=function|Unsized_->false|Sized(diet,buf)->letbuf=Diet.add~off:0~len:(Bytes.lengthbuf)Diet.emptyinDiet.(is_empty(diffdietbuf))letreassemble_exn=function|Unsized_->invalid_arg"Fragment.reassemble_exn"|Sized(_,buf)->Bytes.unsafe_to_stringbuf