Compare commits

..

No commits in common. "main" and "gh-pages" have entirely different histories.

49 changed files with 2130 additions and 352 deletions

View file

@ -1,8 +0,0 @@
## v0.2.0 (2024-03-18)
* Rename pp to pp_hexdump
* Add a pp which just prints the hex with some spaces, but no newlines
## v0.1.0 (2024-03-14)
* Initial release

View file

@ -1,23 +0,0 @@
Copyright (c) 2024, 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 Normal file
View file

View file

@ -1,10 +0,0 @@
## oHEX
This package with minimal dependency cone provides functionality to decode and
encode strings into hexadecimal representation.
As example, `Ohex.decode "4142" = "AB"`. And `Ohex.encode "AB" = "4142"`.
There's even the property, for all strings s: `Ohex.(decode (encode s)) = s`.
A pretty-printer is provided as well.

19
doc/index.html Normal file
View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>index</title>
<link rel="stylesheet" href="./odoc.support/odoc.css"/>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
</head>
<body>
<main class="content">
<div class="by-name">
<h2>OCaml package documentation</h2>
<ol>
<li><a href="ohex/index.html">ohex</a> <span class="version">0.2.0</span></li>
</ol>
</div>
</main>
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

1
doc/odoc.support/katex.min.css vendored Normal file

File diff suppressed because one or more lines are too long

1
doc/odoc.support/katex.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1393
doc/odoc.support/odoc.css Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,66 @@
/* The browsers interpretation of the CORS origin policy prevents to run
webworkers from javascript files fetched from the file:// protocol. This hack
is to workaround this restriction. */
function createWebWorker() {
var searchs = search_urls.map((search_url) => {
let parts = document.location.href.split("/");
parts[parts.length - 1] = search_url;
return '"' + parts.join("/") + '"';
});
blobContents = ["importScripts(" + searchs.join(",") + ");"];
var blob = new Blob(blobContents, { type: "application/javascript" });
var blobUrl = URL.createObjectURL(blob);
var worker = new Worker(blobUrl);
URL.revokeObjectURL(blobUrl);
return worker;
}
var worker;
var waiting = 0;
function wait() {
waiting = waiting + 1;
document.querySelector(".search-snake").classList.add("search-busy");
}
function stop_waiting() {
if (waiting > 0) waiting = waiting - 1;
else waiting = 0;
if (waiting == 0) {
document.querySelector(".search-snake").classList.remove("search-busy");
}
}
document.querySelector(".search-bar").addEventListener("focus", (ev) => {
if (typeof worker == "undefined") {
worker = createWebWorker();
worker.onmessage = (e) => {
stop_waiting();
let results = e.data;
let search_results = document.querySelector(".search-result");
search_results.innerHTML = "";
let f = (entry) => {
let search_result = document.createElement("a");
search_result.classList.add("search-entry");
search_result.href = base_url + entry.url;
search_result.innerHTML = entry.html;
search_results.appendChild(search_result);
};
results.forEach(f);
let search_request = document.querySelector(".search-bar").value;
if (results.length == 0 && search_request != "") {
let no_result = document.createElement("div");
no_result.classList.add("search-no-result");
no_result.innerText = "No result...";
search_results.appendChild(no_result);
}
};
}
});
document.querySelector(".search-bar").addEventListener("input", (ev) => {
wait();
worker.postMessage(ev.target.value);
});

14
doc/ohex/Ohex/index.html Normal file
View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Ohex (ohex.Ohex)</title><meta charset="utf-8"/><link rel="stylesheet" href="../../odoc.support/odoc.css"/><meta name="generator" content="odoc 2.4.1"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><script src="../../odoc.support/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script></head><body class="odoc"><nav class="odoc-nav"><a href="../index.html">Up</a> <a href="../index.html">ohex</a> &#x00BB; Ohex</nav><header class="odoc-preamble"><h1>Module <code><span>Ohex</span></code></h1><p>Convert from and to hexadecimal representation.</p></header><div class="odoc-content"><div class="odoc-spec"><div class="spec value anchored" id="val-required_length"><a href="#val-required_length" class="anchor"></a><code><span><span class="keyword">val</span> required_length : <span><span class="optlabel">?skip_whitespace</span>:bool <span class="arrow">&#45;&gt;</span></span> <span>string <span class="arrow">&#45;&gt;</span></span> int</span></code></div><div class="spec-doc"><p><code>required_length ~skip_whitespace s</code> returns the length needed when the hex string <code>s</code> would be decoded into a sequence of octets. The argument <code>skip_whitespace</code> defaults to <code>true</code>, and skips any whitespace characters (' ', '\n', '\r', '\t'). This function is useful for estimating the space required for <code>decode_into</code>.</p><ul class="at-tags"><li class="raises"><span class="at-tag">raises</span> <code>Invalid_argument</code> <p>if any character in <code>s</code> is not a hex character, or an odd amount of characters are present.</p></li></ul></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-decode"><a href="#val-decode" class="anchor"></a><code><span><span class="keyword">val</span> decode : <span><span class="optlabel">?skip_whitespace</span>:bool <span class="arrow">&#45;&gt;</span></span> <span>string <span class="arrow">&#45;&gt;</span></span> string</span></code></div><div class="spec-doc"><p><code>decode ~skip_whitespace s</code> decodes a hex string <code>s</code> into a sequence of octets. The argument <code>skip_whitespace</code> defaults to <code>true</code>, and skips any whitespace characters in <code>s</code> (' ', '\n', '\r', '\t'). An example: <code>decode &quot;4142&quot; = &quot;AB&quot;</code>.</p><ul class="at-tags"><li class="raises"><span class="at-tag">raises</span> <code>Invalid_argument</code> <p>if any character in <code>s</code> is not a hex character, or an odd amount of characters are present.</p></li></ul></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-decode_into"><a href="#val-decode_into" class="anchor"></a><code><span><span class="keyword">val</span> decode_into :
<span><span class="optlabel">?skip_whitespace</span>:bool <span class="arrow">&#45;&gt;</span></span>
<span>string <span class="arrow">&#45;&gt;</span></span>
<span>bytes <span class="arrow">&#45;&gt;</span></span>
<span><span class="optlabel">?off</span>:int <span class="arrow">&#45;&gt;</span></span>
<span>unit <span class="arrow">&#45;&gt;</span></span>
unit</span></code></div><div class="spec-doc"><p><code>decode_into ~skip_whitespace s dst ~off ()</code> decodes <code>s</code> into <code>dst</code> starting at <code>off</code> (defaults to 0). The argument <code>skip_whitespace</code> defaults to <code>true</code> and skips any whitespace characters.</p><ul class="at-tags"><li class="raises"><span class="at-tag">raises</span> <code>Invalid_argument</code> <p>if any character in <code>s</code> is not a hex character, an odd amount of characters are present, or <code>dst</code> does not contain enough space.</p></li></ul></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-encode"><a href="#val-encode" class="anchor"></a><code><span><span class="keyword">val</span> encode : <span>string <span class="arrow">&#45;&gt;</span></span> string</span></code></div><div class="spec-doc"><p><code>encode s</code> encodes <code>s</code> into a freshly allocated string of double size, where each character in <code>s</code> is encoded as two hex digits in the returned string. An example: <code>encode &quot;AB&quot; = &quot;4142&quot;</code>.</p></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-encode_into"><a href="#val-encode_into" class="anchor"></a><code><span><span class="keyword">val</span> encode_into : <span>string <span class="arrow">&#45;&gt;</span></span> <span>bytes <span class="arrow">&#45;&gt;</span></span> <span><span class="optlabel">?off</span>:int <span class="arrow">&#45;&gt;</span></span> <span>unit <span class="arrow">&#45;&gt;</span></span> unit</span></code></div><div class="spec-doc"><p><code>encode_into s dst ~off ()</code> encodes <code>s</code> into <code>dst</code> starting at <code>off</code> (defaults to 0). Each character is encoded as two hex digits in <code>dst</code>.</p><ul class="at-tags"><li class="raises"><span class="at-tag">raises</span> <code>Invalid_argument</code> <p>if <code>dst</code> does not contain enough space.</p></li></ul></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-pp"><a href="#val-pp" class="anchor"></a><code><span><span class="keyword">val</span> pp : <span><span class="xref-unresolved">Stdlib</span>.Format.formatter <span class="arrow">&#45;&gt;</span></span> <span>string <span class="arrow">&#45;&gt;</span></span> unit</span></code></div><div class="spec-doc"><p><code>pp ppf s</code> pretty-prints the string <code>s</code> in hexadecimal. Some spaces are emitted for easier readability. No newline is emitted.</p></div></div><div class="odoc-spec"><div class="spec value anchored" id="val-pp_hexdump"><a href="#val-pp_hexdump" class="anchor"></a><code><span><span class="keyword">val</span> pp_hexdump :
<span><span class="optlabel">?row_numbers</span>:bool <span class="arrow">&#45;&gt;</span></span>
<span><span class="optlabel">?chars</span>:bool <span class="arrow">&#45;&gt;</span></span>
<span>unit <span class="arrow">&#45;&gt;</span></span>
<span><span class="xref-unresolved">Stdlib</span>.Format.formatter <span class="arrow">&#45;&gt;</span></span>
<span>string <span class="arrow">&#45;&gt;</span></span>
unit</span></code></div><div class="spec-doc"><p><code>pp_hexdump ~row_numbers ~chars () ppf s</code> pretty-prints the string <code>s</code> in hexadecimal (similar to <code>hexdump -C</code>). If <code>row_numbers</code> is provided (defaults to <code>true</code>), each output line is prefixed with the row number. If <code>chars</code> is provided (defaults to <code>true</code>), in the last column the ASCII string is printed (non-printable characters are printed as '.').</p></div></div></div></body></html>

2
doc/ohex/index.html Normal file
View file

@ -0,0 +1,2 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>index (ohex.index)</title><meta charset="utf-8"/><link rel="stylesheet" href="../odoc.support/odoc.css"/><meta name="generator" content="odoc 2.4.1"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><script src="../odoc.support/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script></head><body class="odoc"><nav class="odoc-nav"><a href="../index.html">Up</a> ohex</nav><header class="odoc-preamble"><h1 id="ohex-index"><a href="#ohex-index" class="anchor"></a>ohex index</h1></header><nav class="odoc-toc"><ul><li><a href="#library-ohex">Library ohex</a></li></ul></nav><div class="odoc-content"><h2 id="library-ohex"><a href="#library-ohex" class="anchor"></a>Library ohex</h2><p>The entry point of this library is the module: <a href="Ohex/index.html"><code>Ohex</code></a>.</p></div></body></html>

9
dune
View file

@ -1,9 +0,0 @@
(library
(name ohex)
(public_name ohex)
(modules ohex))
(test
(name tests)
(modules tests)
(libraries alcotest ohex))

View file

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

121
ohex.ml
View file

@ -1,121 +0,0 @@
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 digit = function
| '0'..'9' as c -> int_of_char c - 0x30
| 'A'..'F' as c -> int_of_char c - 0x41 + 10
| 'a'..'f' as c -> int_of_char c - 0x61 + 10
| _ -> invalid_arg "bad character"
let required_length ?(skip_whitespace = true) src =
let req =
string_fold (fun r c ->
if skip_whitespace && is_space c then
r
else (
ignore (digit c);
succ r))
0 src
in
if req mod 2 = 0 then
req / 2
else
invalid_arg "leftover byte in hex string"
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
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 = required_length ~skip_whitespace src in
let buf = Bytes.create len in
decode_into ~skip_whitespace src buf ();
Bytes.unsafe_to_string buf
let hex_map = "0123456789abcdef"
let encode_into src tgt ?(off = 0) () =
String.iteri (fun idx c ->
let hi, lo =
let i = int_of_char c in
hex_map.[i lsr 4], hex_map.[i land 0x0F]
in
Bytes.set tgt (idx * 2 + off) hi;
Bytes.set 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 ppf s =
String.iteri (fun idx c ->
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 " ")
s
let pp_hexdump ?(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
if pad > 0 then
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"

View file

@ -1,54 +0,0 @@
(** Convert from and to hexadecimal representation. *)
val required_length : ?skip_whitespace:bool -> string -> int
(** [required_length ~skip_whitespace s] returns the length needed when the
hex string [s] would be decoded into a sequence of octets. 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].
@raise Invalid_argument if any character in [s] is not a hex character, or
an odd amount of characters are present. *)
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.
An example: [encode "AB" = "4142"].
*)
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 : Format.formatter -> string -> unit
(** [pp ppf s] pretty-prints the string [s] in hexadecimal. Some spaces are
emitted for easier readability. No newline is emitted. *)
val pp_hexdump : ?row_numbers:bool -> ?chars:bool -> unit ->
Format.formatter -> string -> unit
(** [pp_hexdump ~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 '.'). *)

View file

@ -1,22 +0,0 @@
opam-version: "2.0"
maintainer: "Hannes Mehnert <hannes@mehnert.org>"
authors: "Hannes Mehnert <hannes@mehnert.org>"
license: "BSD-2-Clause"
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" {>= "2.7"}
"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.
"""

102
tests.ml
View file

@ -1,102 +0,0 @@
let tests = [
"", 0, "";
"41", 1, "A";
"41 41", 2, "AA";
" 41 41 ", 2, "AA";
" 414 1", 2, "AA";
]
let len_dec_tests =
List.mapi (fun i (s, len, v) ->
string_of_int i ^ " is correct", `Quick,
(fun () ->
Alcotest.(check int "required length" len (Ohex.required_length s));
Alcotest.(check string "decode works fine" v (Ohex.decode s))))
tests
let bad_char_input = [ "W" ; "AAWW" ; "WWAA" ]
let leftover_input = [ "AAA" ; "A" ]
let bad_input_ws = [ " "; " AA" ; "AA " ; "A A" ]
let bad_len_dec_tests =
(List.mapi (fun i s ->
string_of_int i ^ " fails (bad character)", `Quick,
(fun () ->
Alcotest.(check_raises "required length raises"
(Invalid_argument "bad character")
(fun () -> ignore (Ohex.required_length s)));
Alcotest.(check_raises "decode raises"
(Invalid_argument "bad character")
(fun () -> ignore (Ohex.decode s)))))
bad_char_input) @
(List.mapi (fun i s ->
string_of_int i ^ " fails (leftover)", `Quick,
(fun () ->
Alcotest.(check_raises "required length raises"
(Invalid_argument "leftover byte in hex string")
(fun () -> ignore (Ohex.required_length ~skip_whitespace:false s)));
Alcotest.(check_raises "decode raises"
(Invalid_argument "leftover byte in hex string")
(fun () -> ignore (Ohex.decode ~skip_whitespace:false s)))))
leftover_input) @
(List.mapi (fun i s ->
string_of_int i ^ " fails (skip_whitespace = false)", `Quick,
(fun () ->
Alcotest.(check_raises "required length raises"
(Invalid_argument "bad character")
(fun () -> ignore (Ohex.required_length ~skip_whitespace:false s)));
Alcotest.(check_raises "decode raises"
(Invalid_argument "bad character")
(fun () -> ignore (Ohex.decode ~skip_whitespace:false s)))))
bad_input_ws)
let enc_tests = [
"A", "41", 2;
"AA", "4141", 4;
"AAA", "414141", 6;
]
let enc_tests =
List.mapi (fun i (v, hex, l) ->
string_of_int i ^ " is correct", `Quick,
(fun () ->
Alcotest.(check string "encode works" hex (Ohex.encode v));
let buf = Bytes.create l in
Ohex.encode_into v buf ~off:0 ();
Alcotest.(check string "encode_into works" hex (Bytes.unsafe_to_string buf))))
enc_tests
let dec_enc () =
let random_string () =
let size = Random.int 128 in
let buf = Bytes.create size in
for i = 0 to size - 1 do
Bytes.set_uint8 buf i (Random.int 256)
done;
Bytes.unsafe_to_string buf
in
for i = 0 to 10_000 do
let input = random_string () in
Alcotest.(check string ("dec (enc s) = s " ^ string_of_int i)
input Ohex.(decode (encode input)));
Alcotest.(check string ("dec ~skip_ws:false (enc s) = s " ^ string_of_int i)
input Ohex.(decode ~skip_whitespace:false (encode input)));
let buf = Bytes.create (String.length input * 2) in
Ohex.encode_into input buf ~off:0 ();
let out = Bytes.create (String.length input) in
Ohex.decode_into (Bytes.unsafe_to_string buf) out ~off:0 ();
Alcotest.(check string ("dec_into (enc_into s) = s " ^ string_of_int i)
input (Bytes.unsafe_to_string out))
done
let suites = [
"length and decode pass", len_dec_tests ;
"bad input", bad_len_dec_tests ;
"encode tests", enc_tests ;
"decode encode", [ "decode (encode s) = s", `Quick, dec_enc ];
]
let () = Alcotest.run "hex tests" suites