Up

module Iobuf

: sig

A non-moving (in the gc sense) contiguous range of bytes, useful for I/O operations.

An iobuf consists of:

All iobuf operations are restricted to operate within the limits. Initially, the window of an iobuf is identical to the limits. A phantom type, "seek" permission, controls whether or not code is allowed to change the limits and window. With seek permission, the limits can be narrowed, but can never be widened, and the window can be set to an arbitrary subrange of the limits.

A phantom type controls whether code can read and write bytes in the bigstring (within the limits) or can only read them.

To present a restricted view of an iobuf to a client, one can create a sub-iobuf or add a type constraint.

include sig
#
type __pa_nonrec_0 = Iobuf_intf.seek
#
type seek = __pa_nonrec_0
end
with type __pa_nonrec_0 := Iobuf_intf.seek
include sig
#
type __pa_nonrec_1 = Iobuf_intf.no_seek
#
type no_seek = __pa_nonrec_1
end
with type __pa_nonrec_1 := Iobuf_intf.no_seek
#
type (+'data_perm_read_write, +'seek_permission) t

The first type parameter controls whether the iobuf can be written to. The second type parameter controls whether the window and limits can be changed.

To allow read_write or read_only access, a function's type uses _ rather than read_only as the type argument to t. Analogously, to allow no_seek or seek access, a function's type uses _ rather than no_seek as the type argument to t. Using _ allows the function to be directly applied to either permission. Using a specific permission would require code to use coercion :>.

include Core_kernel.Std.Invariant.S2 with type ('a, 'b) t := ('a, 'b) t

Creation

#
val create : len:int -> (_, _) t

create ~len creates a new iobuf, backed by a bigstring of length len, with the limits and window set to the entire bigstring.

#
val of_bigstring : ?pos:int -> ?len:int -> Core_kernel.Std.Bigstring.t -> (_, _) t

of_bigstring bigstring ~pos ~len returns an iobuf backed by bigstring, with the window and limits specified starting at pos and of length len.

#
val of_string : string -> (_, _) t

of_string s returns a new iobuf whose contents are s.

#
val sub : ?pos:int -> ?len:int -> ('d, _) t -> ('d, _) t

sub t ~pos ~len returns a new iobuf with limits and window set to the subrange of t specified by pos and len. sub preserves data permissions, but allows arbitrary seek permissions on the resulting iobuf.

#
val set_bounds_and_buffer : src:(Common.read_write, _) t -> dst:(_, seek) t -> unit

set_bounds_and_buffer ~src ~dst copies bounds (ie limits + window) and shallowly copies the buffer from src to dst. read_write access is required on src because the caller might have read_write access to dst, and would after the call then effectively have read_write access to src.

#
val set_bounds_and_buffer_sub : ?pos:int -> ?len:int -> src:(Common.read_write, _) t -> dst:(_, seek) t -> unit -> unit

set_bounds_and_buffer_sub ?pos ?len ~src ~dst () is a more efficient version of: set_bounds_and_buffer ~src:(Iobuf.sub ?pos ?len src) ~dst.

set_bounds_and_buffer ~src ~dst is not the same as set_bounds_and_buffer_sub ~dst ~src (), because the limits are narrowed in the latter case.

#
val read_only : (_, 's) t -> (Common.read_only, 's) t

Generalization

One may wonder why you'd want to call read_only or no_seek, given that the casts are already possible, e.g. t : (read_write, _) t :> (read_only, _) t. It turns out that if you want to define some f : (_, seek) t -> unit of your own, which can be conveniently applied to read_only and read_write iobufs without the user having to cast read_write up, you need this read_only function.

#
val no_seek : ('r, _) t -> ('r, no_seek) t

Accessors

#
val capacity : (_, _) t -> int

capacity t returns the size of t's limits subrange. The capacity of an iobuf can be reduced via narrow.

#
val length : (_, _) t -> int

length t returns the size of t's window.

#
val is_empty : (_, _) t -> bool

is_empty t is length t = 0.

Changing the limits

#
val narrow : (_, seek) t -> unit

narrow t sets t's limits to the current window.

Changing the window

One can call Lo_bound.window t to get a snapshot of the lower bound of the window, and then later restore that snapshot with Lo_bound.restore. This is useful for speculatively parsing, and then rewinding when there isn't enough data to finish.

Similarly for Hi_bound.window and Lo_bound.restore.

Using a snapshot with a different iobuf, even a sub iobuf of the snapshotted one, has unspecified results. An exception may be raised, or a silent error may occur. However, the safety guarantees of the iobuf will not be violated, i.e., the attempt will not enlarge the limits of the subject iobuf.

#
module type Bound = Iobuf_intf.Bound with type ('d, 'w) iobuf := ('d, 'w) t
#
module Lo_bound : Bound
#
module Hi_bound : Bound
#
val advance : (_, seek) t -> int -> unit

advance t amount advances the lower bound of the window by amount. It is an error to advance past the upper bound of the window or the lower limit.

#
val unsafe_advance : (_, seek) t -> int -> unit

unsafe_advance is like advance but with no bounds checking, so incorrect usage can easily cause segfaults.

#
val resize : (_, seek) t -> len:int -> unit

resize t sets the length of t's window, provided it does not exceed limits.

#
val unsafe_resize : (_, seek) t -> len:int -> unit

unsafe_resize is like resize but with no bounds checking, so incorrect usage can easily cause segfaults.

#
val rewind : (_, seek) t -> unit

rewind t sets the lower bound of the window to the lower limit.

#
val reset : (_, seek) t -> unit

reset t sets the window to the limits.

#
val flip_lo : (_, seek) t -> unit

flip_lo t sets the window to range from the lower limit to the lower bound of the old window. This is typically called after a series of Fills, to reposition the window in preparation to Consume the newly written data.

The bounded version narrows the effective limit. This can preserve some data near the limit, such as an hypothetical packet header, in the case of bounded_flip_lo or unfilled suffix of a buffer, in bounded_flip_hi.

#
val bounded_flip_lo : (_, seek) t -> Lo_bound.t -> unit
#
val compact : (Common.read_write, seek) t -> unit

compact t copies data from the window to the lower limit of the iobuf and sets the window to range from the end of the copied data to the upper limit. This is typically called after a series of Consumes to save unread data and prepare for the next series of Fills and flip_lo.

#
val bounded_compact : (Common.read_write, seek) t -> Lo_bound.t -> Hi_bound.t -> unit
#
val flip_hi : (_, seek) t -> unit

flip_hi t sets the window to range from the the upper bound of the current window to the upper limit. This operation is dual to flip_lo and is typically called when the data in the current (narrowed) window has been processed and the window needs to be positioned over the remaining data in the buffer. For example:

      (* ... determine initial_data_len ... *)
      Iobuf.resize buf ~len:initial_data_len;
      (* ... and process initial data ... *)
      Iobuf.flip_hi buf;

Now the window of buf ranges over the remainder of the data.

#
val bounded_flip_hi : (_, seek) t -> Hi_bound.t -> unit
#
val protect_window_and_bounds : ('rw, no_seek) t -> f:(('rw, seek) t -> 'a) -> 'a

protect_window_and_bounds t ~f applies f to t and restores t's bounds afterwards.

Getting and setting data

"consume" and "fill" functions access data at the lower bound of the window and advance lower bound of the window. "peek" and "poke" functions access data but do not advance the window.

#
val to_string : ?len:int -> (_, _) t -> string

to_string t returns the bytes in t as a string. It does not alter the window.

#
val to_string_hum : ?bounds:[
| `Window
| `Limits
| `Whole
] -> (_, _) t -> string

to_string_hum t produces a readable, multi-line representation of an iobuf. bounds defaults to `Limits and determines how much of the contents are shown.

#
module Consume : sig

Consume.string t ~len reads len characters (all, by default) from t into a new string and advances the lower bound of the window accordingly.

Consume.bin_prot X.bin_read_t t returns the initial X.t in t, advancing past the bytes read.

#
type src = (Common.read_only, seek) t

To_string.blito ~src ~dst ~dst_pos ~src_len () reads src_len bytes from src, advancing src's window accordingly, and writes them into dst starting at dst_pos. By default dst_pos = 0 and src_len = length src. It is an error if dst_pos and src_len don't specify a valid region of dst or src_len > length src.

#
module To_string : Iobuf_intf.Consuming_blit with type src := src and type dst := string
#
module To_bigstring : Iobuf_intf.Consuming_blit with type src := src and type dst := Core_kernel.Std.Bigstring.t
include Iobuf_intf.Accessors with type ('a, 'r, 's) t = ('r, seek) t -> 'a and type 'a bin_prot := 'a Core_kernel.Std.Bin_prot.Type_class.reader
end
#
module Fill : Iobuf_intf.Accessors with type ('a, 'd, 'w) t = (Common.read_write, seek) t -> 'a -> unit and type 'a bin_prot := 'a Core_kernel.Std.Bin_prot.Type_class.writer

Fill.bin_prot X.bin_write_t t x writes x to t in bin-prot form, advancing past the bytes written.

#
module Peek : sig

Peek and Poke functions access a value at pos from the lower bound of the window and do not advance.

Peek.bin_prot X.bin_read_t t returns the initial X.t in t without advancing.

Following the bin_prot protocol, the representation of x is X.bin_size_t x bytes long. Peek., Poke., Consume., and Fill.bin_prot do not add any size prefix or other framing to the bin_prot representation.

#
type src = (Common.read_only, no_seek) t

Similar to Consume.To_*, but do not advance the buffer.

#
module To_string : Core_kernel.Std.Blit.S_distinct with type src := src and type dst := string
#
module To_bigstring : Core_kernel.Std.Blit.S_distinct with type src := src and type dst := Core_kernel.Std.Bigstring.t
include Iobuf_intf.Accessors with type ('a, 'd, 'w) t = ('d, 'w) t -> pos:int -> 'a and type 'a bin_prot := 'a Core_kernel.Std.Bin_prot.Type_class.reader
end
#
module Poke : Iobuf_intf.Accessors with type ('a, 'd, 'w) t = (Common.read_write, 'w) t -> pos:int -> 'a -> unit and type 'a bin_prot := 'a Core_kernel.Std.Bin_prot.Type_class.writer

Poke.bin_prot X.bin_write_t t x writes x to the beginning of t in binary form without advancing. You can use X.bin_size_t to tell how long it was. X.bin_write_t is only allowed to write that portion of the buffer to which you have access.

#
module Unsafe : sig

Unsafe has submodules that are like their corresponding module, except with no range checks. Hence, mistaken uses can cause segfaults. Be careful!

#
module Consume : module type of Consume
#
module Fill : module type of Fill
#
module Peek : module type of Peek
#
module Poke : module type of Poke
end
#
val fill_bin_prot : (Common.read_write, seek) t -> 'a Core_kernel.Std.Bin_prot.Type_class.writer -> 'a -> unit Core_kernel.Std.Or_error.t

fill_bin_prot writes a bin-prot value to the lower bound of the window, prefixed by its length, and advances by the amount written. fill_bin_prot returns an error if the window is too small to write the value.

consume_bin_prot t reader reads a bin-prot value from the lower bound of the window, which should have been written using fill_bin_prot, and advances the window by the amount read. consume_bin_prot returns an error if there is not a complete message in the window and in that case the window is left unchanged.

Don't use these without a good reason, as they are incompatible with similar functions in Reader and Writer. They use a 4-byte length rather than an 8-byte length.

#
val consume_bin_prot : (_, seek) t -> 'a Core_kernel.Std.Bin_prot.Type_class.reader -> 'a Core_kernel.Std.Or_error.t
#
val transfer : ?len:int -> src:(_, seek) t -> dst:(Common.read_write, seek) t -> unit

transfer blits len bytes from src to dst, advancing the lower bounds of both windows. It is an error if len > length src || len > length dst || phys_equal src dst.

#
val memmove : (Common.read_write, _) t -> src_pos:int -> dst_pos:int -> len:int -> unit

memmove blits len bytes from src_pos to dst_pos in an iobuf, both relative to the lower bound of the window. The window is not advanced.

I/O

#
val read_assume_fd_is_nonblocking : (Common.read_write, seek) t -> Iobuf_intf.Unix.File_descr.t -> unit

Iobuf has analogs of various Bigstring functions. These analogs advance by the amount written/read.

#
val pread_assume_fd_is_nonblocking : (Common.read_write, seek) t -> Iobuf_intf.Unix.File_descr.t -> offset:int -> unit
#
val recvfrom_assume_fd_is_nonblocking : (Common.read_write, seek) t -> Iobuf_intf.Unix.File_descr.t -> Iobuf_intf.Unix.sockaddr
#
val recvmmsg_assume_fd_is_nonblocking : (Iobuf_intf.Unix.File_descr.t -> ?count:int -> ?srcs:Iobuf_intf.Unix.sockaddr array -> (Common.read_write, seek) t array -> int) Core_kernel.Std.Or_error.t

When there are no packets awaiting reception, recvmmsg_assume_fd_is_nonblocking returns <0 (actually, -EWOULDBLOCK/-EAGAIN), rather than raising Unix_error with EAGAIN/EWOULDBLOCK. This saves the allocation of an exception (and backtrace) in common cases, at the cost of callers having to handle but the return code and possible exception.

#
val recvmmsg_assume_fd_is_nonblocking_no_options : (Iobuf_intf.Unix.File_descr.t -> count:int -> (Common.read_write, seek) t array -> int) Core_kernel.Std.Or_error.t
#
val send_nonblocking_no_sigpipe : unit -> ((_, seek) t -> Iobuf_intf.Unix.File_descr.t -> unit) Core_kernel.Std.Or_error.t
#
val sendto_nonblocking_no_sigpipe : unit -> ((_, seek) t -> Iobuf_intf.Unix.File_descr.t -> Iobuf_intf.Unix.sockaddr -> unit) Core_kernel.Std.Or_error.t
#
val write_assume_fd_is_nonblocking : (_, seek) t -> Iobuf_intf.Unix.File_descr.t -> unit
#
val pwrite_assume_fd_is_nonblocking : (_, seek) t -> Iobuf_intf.Unix.File_descr.t -> offset:int -> unit
#
val sexp_of_seek : seek -> Sexplib.Sexp.t
#
val sexp_of_no_seek : no_seek -> Sexplib.Sexp.t
#
val sexp_of_t : ('data_perm_read_write -> Sexplib.Sexp.t) -> ('seek_permission -> Sexplib.Sexp.t) -> ('data_perm_read_write, 'seek_permission) t -> Sexplib.Sexp.t
end