initial commit

This commit is contained in:
Hannes Mehnert 2024-03-14 13:04:14 +01:00
commit 25372c91b4
8 changed files with 198 additions and 0 deletions

0
CHANGES.md Normal file
View file

23
LICENSE.md Normal file
View file

@ -0,0 +1,23 @@
Copyright (c) 2017, 2018, Hannes Mehnert
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

0
README.md Normal file
View file

4
dune Normal file
View file

@ -0,0 +1,4 @@
(library
(name ohex)
(public_name ohex)
(modules ohex))

3
dune-project Normal file
View file

@ -0,0 +1,3 @@
(lang dune 2.7)
(name ohex)
(formatting disabled)

102
ohex.ml Normal file
View file

@ -0,0 +1,102 @@
let string_fold f acc str =
let st = ref acc in
String.iter (fun c -> st := f !st c) str;
!st
let is_space = function
| ' ' | '\n' | '\r' | '\t' -> true
| _ -> false
let count_hex_chars ?(skip_whitespace = true) src =
string_fold (fun r c ->
if skip_whitespace && is_space c then
r
else
succ r)
0 src / 2
let decode_into ?(skip_whitespace = true) src tgt ?(off = 0) () =
let fold f acc str =
let st = ref acc in
String.iter (fun c -> st := f !st c) str;
!st
and digit c =
match c with
| '0'..'9' -> int_of_char c - 0x30
| 'A'..'F' -> int_of_char c - 0x41 + 10
| 'a'..'f' -> int_of_char c - 0x61 + 10
| _ -> invalid_arg "bad character"
in
let chars, leftover =
fold (fun (chars, leftover) c ->
if skip_whitespace && is_space c then
chars, leftover
else
let c = digit c in
match leftover with
| None -> chars, Some (c lsl 4)
| Some c' -> (c' lor c) :: chars, None)
([], None) src
in
let chars = List.rev chars in
if leftover <> None then
invalid_arg "leftover byte in hex string";
List.iteri (fun idx c -> Bytes.set_uint8 tgt (off + idx) c) chars
let decode ?(skip_whitespace = true) src =
let len = count_hex_chars ~skip_whitespace src in
let buf = Bytes.create len in
decode_into ~skip_whitespace src buf ();
Bytes.unsafe_to_string buf
let encode_into src tgt ?(off = 0) () =
String.iteri (fun idx c ->
let hi, lo =
let i = int_of_char c in
i lsr 4, i land 0xFF
in
Bytes.set_uint8 tgt (idx * 2 + off) hi;
Bytes.set_uint8 tgt (idx * 2 + off + 1) lo)
src
let encode src =
let buf = Bytes.create (String.length src * 2) in
encode_into src buf ();
Bytes.unsafe_to_string buf
let printable_ascii c =
let i = int_of_char c in
not (i < 0x20 || i >= 0x7f)
let pp ?(row_numbers = true) ?(chars = true) () ppf s =
String.iteri (fun idx c ->
if idx mod 16 = 0 && row_numbers then
Format.fprintf ppf "%06x " idx;
Format.fprintf ppf "%02x" (int_of_char c);
if idx mod 2 = 1 then
Format.pp_print_string ppf " ";
if idx mod 8 = 7 then
Format.pp_print_string ppf " ";
if idx mod 16 = 15 && chars then
String.iter (fun c ->
Format.pp_print_char ppf (if printable_ascii c then c else '.'))
(String.sub s (idx - 15) 16);
if idx mod 16 = 15 then
Format.pp_print_string ppf "\n")
s;
(if chars then
let last_n, pad =
let l = String.length s in
let pad = 16 - (l mod 16) in
let pad = if pad = 16 then 0 else pad in
String.sub s (l - (l mod 16)) (l mod 16),
pad
in
let pad_chars = pad * 2 + (pad + 1) / 2 + (if pad > 8 then 1 else 0) + 1 in
Format.pp_print_string ppf (String.make pad_chars ' ');
String.iter (fun c ->
Format.pp_print_char ppf (if printable_ascii c then c else '.'))
last_n);
if String.length s mod 16 <> 0 then
Format.pp_print_string ppf "\n"

44
ohex.mli Normal file
View file

@ -0,0 +1,44 @@
(** Convert from and to hex representation. *)
val count_hex_chars : ?skip_whitespace:bool -> string -> int
(** [count_hex_chars ~skip_whitespace s] counts the amount of hex characters in
the string [s]. The argument [skip_whitespace] defaults to [true], and skips
any whitespace characters (' ', '\n', '\r', '\t'). This function is useful
for estimating the space required for [decode_into]. *)
val decode : ?skip_whitespace:bool -> string -> string
(** [decode ~skip_whitespace s] decodes a hex string [s] into a sequence of
octets. The argument [skip_whitespace] defaults to [true], and skips any
whitespace characters in [s] (' ', '\n', '\r', '\t'). An example:
[decode "4142" = "AB"].
@raise Invalid_argument if any character in [s] is not a hex character, or
an odd amount of characters are present. *)
val decode_into : ?skip_whitespace:bool -> string -> bytes -> ?off:int -> unit
-> unit
(** [decode_into ~skip_whitespace s dst ~off ()] decodes [s] into [dst]
starting at [off] (defaults to 0). The argument [skip_whitespace] defaults
to [true] and skips any whitespace characters.
@raise Invalid_argument if any character in [s] is not a hex character, an
odd amount of characters are present, or [dst] does not contain enough
space. *)
val encode : string -> string
(** [encode s] encodes [s] into a freshly allocated string of double size, where
each character in [s] is encoded as two hex digits in the returned string. *)
val encode_into : string -> bytes -> ?off:int -> unit -> unit
(** [encode_into s dst ~off ()] encodes [s] into [dst] starting at [off]
(defaults to 0). Each character is encoded as two hex digits in [dst].
@raise Invalid_argument if [dst] does not contain enough space. *)
val pp : ?row_numbers:bool -> ?chars:bool -> unit ->
Format.formatter -> string -> unit
(** [pp ~row_numbers ~chars () ppf s] pretty-prints the string [s] in
hexadecimal (similar to [hexdump -C]). If [row_numbers] is provided
(defaults to [true]), each output line is prefixed with the row number.
If [chars] is provided (defaults to [true]), in the last column the ASCII
string is printed (non-printable characters are printed as '.'). *)

22
ohex.opam Normal file
View file

@ -0,0 +1,22 @@
opam-version: "2.0"
maintainer: "Hannes Mehnert <hannes@mehnert.org>"
authors: "Hannes Mehnert <hannes@mehnert.org>"
license: "ISC"
homepage: "https://git.robur.coop/robur/ohex"
doc: "https://robur-coop.github.io/ohex/doc"
bug-reports: "https://git.robur.coop/robur/ohex/issues"
depends: [
"ocaml" {>= "4.08.0"}
"dune"
"alcotest" {with-test}
]
build: [
["dune" "subst"] {dev}
["dune" "build" "-p" name "-j" jobs]
["dune" "runtest" "-p" name "-j" jobs] {with-test}
]
dev-repo: "git+https://git.robur.coop/robur/ohex.git"
synopsis: "Hexadecimal encoding and decoding"
description: """
A library to encode and decode hexadecimal byte sequences.
"""