initial commit
This commit is contained in:
commit
25372c91b4
8 changed files with 198 additions and 0 deletions
0
CHANGES.md
Normal file
0
CHANGES.md
Normal file
23
LICENSE.md
Normal file
23
LICENSE.md
Normal 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
0
README.md
Normal file
4
dune
Normal file
4
dune
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
(library
|
||||||
|
(name ohex)
|
||||||
|
(public_name ohex)
|
||||||
|
(modules ohex))
|
3
dune-project
Normal file
3
dune-project
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
(lang dune 2.7)
|
||||||
|
(name ohex)
|
||||||
|
(formatting disabled)
|
102
ohex.ml
Normal file
102
ohex.ml
Normal 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
44
ohex.mli
Normal 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
22
ohex.opam
Normal 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.
|
||||||
|
"""
|
Loading…
Reference in a new issue