···11+(** Self-Delimiting Numeric Values (RFC 6256).
22+33+ Variable-length integer encoding used by LTP (CCSDS 734.1-B-1), Bundle
44+ Protocol, and other space protocols. *)
55+66+(** {1 Encoding} *)
77+88+let encoded_length v =
99+ if v < 0L then invalid_arg "Sdnv.encoded_length: negative value"
1010+ else if v = 0L then 1
1111+ else
1212+ let rec count v n =
1313+ if v = 0L then n else count (Int64.shift_right_logical v 7) (n + 1)
1414+ in
1515+ count v 0
1616+1717+let encode_to buf off v =
1818+ if v < 0L then invalid_arg "Sdnv.encode_to: negative value"
1919+ else if v = 0L then begin
2020+ Bytes.set_uint8 buf off 0;
2121+ 1
2222+ end
2323+ else begin
2424+ let n = encoded_length v in
2525+ if off + n > Bytes.length buf then
2626+ invalid_arg "Sdnv.encode_to: buffer too small";
2727+ for i = n - 1 downto 0 do
2828+ let shift = i * 7 in
2929+ let byte =
3030+ Int64.to_int (Int64.logand (Int64.shift_right_logical v shift) 0x7FL)
3131+ in
3232+ (* Set continuation bit on all but last byte *)
3333+ let byte = if i > 0 then byte lor 0x80 else byte in
3434+ Bytes.set_uint8 buf (off + (n - 1 - i)) byte
3535+ done;
3636+ n
3737+ end
3838+3939+let encode v =
4040+ let n = encoded_length v in
4141+ let buf = Bytes.create n in
4242+ let _ = encode_to buf 0 v in
4343+ buf
4444+4545+(** {1 Decoding} *)
4646+4747+type error = Truncated | Overflow
4848+4949+let pp_error ppf = function
5050+ | Truncated -> Format.fprintf ppf "truncated SDNV"
5151+ | Overflow -> Format.fprintf ppf "SDNV overflow"
5252+5353+let decode buf off =
5454+ let len = Bytes.length buf in
5555+ let rec loop acc bits_read pos =
5656+ if bits_read >= 64 then Error Overflow
5757+ else if pos >= len then Error Truncated
5858+ else
5959+ let byte = Bytes.get_uint8 buf pos in
6060+ let value = byte land 0x7F in
6161+ let acc = Int64.(logor (shift_left acc 7) (of_int value)) in
6262+ if byte land 0x80 = 0 then Ok (acc, pos - off + 1)
6363+ else loop acc (bits_read + 7) (pos + 1)
6464+ in
6565+ loop 0L 0 off
6666+6767+let decode_exn buf off =
6868+ match decode buf off with
6969+ | Ok v -> v
7070+ | Error Truncated -> invalid_arg "Sdnv.decode_exn: truncated"
7171+ | Error Overflow -> invalid_arg "Sdnv.decode_exn: overflow"
7272+7373+(** {1 Streaming with bytesrw} *)
7474+7575+module Slice = Bytesrw.Bytes.Slice
7676+7777+let read r =
7878+ let rec loop acc bits_read =
7979+ if bits_read >= 64 then Error Overflow
8080+ else
8181+ match Bytesrw.Bytes.Reader.read r with
8282+ | slice when Slice.is_eod slice -> Error Truncated
8383+ | slice ->
8484+ let data = Slice.bytes slice in
8585+ let first = Slice.first slice in
8686+ let length = Slice.length slice in
8787+ let rec process_slice acc bits_read i =
8888+ if i >= first + length then
8989+ (* Need more data from next slice *)
9090+ loop acc bits_read
9191+ else
9292+ let byte = Bytes.get_uint8 data i in
9393+ let value = byte land 0x7F in
9494+ let acc = Int64.(logor (shift_left acc 7) (of_int value)) in
9595+ if byte land 0x80 = 0 then begin
9696+ (* Push back remaining bytes if any *)
9797+ let consumed = i - first + 1 in
9898+ if consumed < length then begin
9999+ let remaining =
100100+ Slice.make data ~first:(i + 1) ~length:(length - consumed)
101101+ in
102102+ Bytesrw.Bytes.Reader.push_back r remaining
103103+ end;
104104+ Ok acc
105105+ end
106106+ else if bits_read + 7 >= 64 then Error Overflow
107107+ else process_slice acc (bits_read + 7) (i + 1)
108108+ in
109109+ process_slice acc bits_read first
110110+ in
111111+ loop 0L 0
112112+113113+let read_exn r =
114114+ match read r with
115115+ | Ok v -> v
116116+ | Error Truncated -> invalid_arg "Sdnv.read_exn: truncated"
117117+ | Error Overflow -> invalid_arg "Sdnv.read_exn: overflow"
118118+119119+let write w v =
120120+ let buf = encode v in
121121+ let slice = Slice.make buf ~first:0 ~length:(Bytes.length buf) in
122122+ Bytesrw.Bytes.Writer.write w slice
+58
lib/sdnv.mli
···11+(** Self-Delimiting Numeric Values (RFC 6256).
22+33+ Variable-length integer encoding used by LTP (CCSDS 734.1-B-1), Bundle
44+ Protocol, and other space protocols. Each byte's high bit indicates
55+ continuation (1=more bytes follow, 0=last byte), with 7 bits of data per
66+ byte in big-endian order.
77+88+ {b Examples:}
99+ - 0 encodes as [0x00] (1 byte)
1010+ - 127 encodes as [0x7F] (1 byte)
1111+ - 128 encodes as [0x81 0x00] (2 bytes)
1212+ - 16383 encodes as [0xFF 0x7F] (2 bytes)
1313+ - 16384 encodes as [0x81 0x80 0x00] (3 bytes)
1414+1515+ {b Reference}: {{:https://datatracker.ietf.org/doc/html/rfc6256}RFC 6256} *)
1616+1717+(** {1 Encoding} *)
1818+1919+val encode : int64 -> bytes
2020+(** [encode v] encodes [v] as SDNV bytes.
2121+ @raise Invalid_argument if [v] is negative. *)
2222+2323+val encode_to : bytes -> int -> int64 -> int
2424+(** [encode_to buf off v] writes SDNV-encoded [v] into [buf] starting at [off].
2525+ Returns the number of bytes written.
2626+ @raise Invalid_argument if [v] is negative or buffer too small. *)
2727+2828+val encoded_length : int64 -> int
2929+(** [encoded_length v] returns the number of bytes needed to encode [v].
3030+ @raise Invalid_argument if [v] is negative. *)
3131+3232+(** {1 Decoding} *)
3333+3434+type error =
3535+ | Truncated (** Not enough bytes to complete decoding *)
3636+ | Overflow (** Value would overflow int64 *)
3737+3838+val pp_error : Format.formatter -> error -> unit
3939+4040+val decode : bytes -> int -> (int64 * int, error) result
4141+(** [decode buf off] decodes an SDNV starting at [off] in [buf]. Returns the
4242+ decoded value and the number of bytes consumed. *)
4343+4444+val decode_exn : bytes -> int -> int64 * int
4545+(** [decode_exn buf off] is like [decode] but raises [Invalid_argument] on
4646+ error. *)
4747+4848+(** {1 Streaming with bytesrw} *)
4949+5050+val read : Bytesrw.Bytes.Reader.t -> (int64, error) result
5151+(** [read r] reads and decodes an SDNV from bytesrw reader [r]. *)
5252+5353+val read_exn : Bytesrw.Bytes.Reader.t -> int64
5454+(** [read_exn r] is like [read] but raises [Invalid_argument] on error. *)
5555+5656+val write : Bytesrw.Bytes.Writer.t -> int64 -> unit
5757+(** [write w v] encodes and writes SDNV [v] to bytesrw writer [w].
5858+ @raise Invalid_argument if [v] is negative. *)
+22
sdnv.opam
···11+opam-version: "2.0"
22+synopsis: "Self-Delimiting Numeric Values (RFC 6256)"
33+description: """
44+Variable-length integer encoding used by LTP (CCSDS 734.1-B-1), Bundle Protocol,
55+and other space protocols. Each byte's high bit indicates continuation, with 7
66+bits of data per byte in big-endian order.
77+"""
88+maintainer: ["Thomas Gazagnaire <thomas@gazagnaire.org>"]
99+authors: ["Thomas Gazagnaire <thomas@gazagnaire.org>"]
1010+license: "ISC"
1111+homepage: "https://github.com/gazagnaire/ocaml-sdnv"
1212+bug-reports: "https://github.com/gazagnaire/ocaml-sdnv/issues"
1313+dev-repo: "git+https://tangled.org/gazagnaire.org/ocaml-sdnv.git"
1414+depends: [
1515+ "ocaml" {>= "4.14"}
1616+ "dune" {>= "3.0"}
1717+ "bytesrw"
1818+]
1919+build: [
2020+ ["dune" "subst"] {dev}
2121+ ["dune" "build" "-p" name "-j" jobs]
2222+]