base16

Pure Haskell base16 encoding/decoding (docs.ppad.tech/base16).
git clone git://git.ppad.tech/base16.git
Log | Files | Refs | README | LICENSE

commit 11f834993aa8e91ffd6683e85f764e8de7ba306a
parent 195d167a0062eee57596f4b8119c09be3bc46094
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 17 Jan 2025 22:40:59 +0400

meta: readme

Diffstat:
AREADME.md | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbench/Main.hs | 4++--
Mlib/Data/ByteString/Base16.hs | 4++++
3 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md @@ -0,0 +1,80 @@ +# ppad-base16 + +A pure implementation of base16 encoding & decoding on strict +ByteStrings. + +## Usage + +A sample GHCi session: + +``` + > :set -XOverloadedStrings + > + > -- import qualified + > import qualified Data.ByteString.Base16 as B16 + > + > -- simple base16 encoding and decoding + > B16.encode "hello world" + "68656c6c6f20776f726c64" + > + > B16.decode "68656c6c6f20776f726c64" + Just "hello world" +``` + +## Documentation + +Haddocks (API documentation, etc.) are hosted at +[docs.ppad.tech/base16](https://docs.ppad.tech/base16). + +## Performance + +The aim is best-in-class performance for pure, highly-auditable Haskell +code. We could go slightly faster by direct allocation and writes, but +we get pretty close to the best impure versions with only builders. + +Current benchmark figures on 1kb inputs on my mid-2020 MacBook Air look +like (use `cabal bench` to run the benchmark suite): + +``` + benchmarking encode/ppad-base16 + time 5.929 μs (5.847 μs .. 6.013 μs) + 0.999 R² (0.998 R² .. 0.999 R²) + mean 5.975 μs (5.913 μs .. 6.057 μs) + std dev 233.1 ns (172.4 ns .. 310.0 ns) + + benchmarking decode/ppad-base16 + time 4.942 μs (4.884 μs .. 4.995 μs) + 0.999 R² (0.998 R² .. 0.999 R²) + mean 4.908 μs (4.854 μs .. 4.964 μs) + std dev 176.8 ns (150.3 ns .. 214.3 ns) + variance introduced by outliers: 46% (moderately inflated) +``` + +## Security + +This library aims at the maximum security achievable in a +garbage-collected language under an optimizing compiler such as GHC, in +which strict constant-timeness can be challenging to achieve. + +If you discover any vulnerabilities, please disclose them via +security@ppad.tech. + +## Development + +You'll require [Nix][nixos] with [flake][flake] support enabled. Enter a +development shell with: + +``` +$ nix develop +``` + +Then do e.g.: + +``` +$ cabal repl ppad-base16 +``` + +to get a REPL for the main library. + +[nixos]: https://nixos.org/ +[flake]: https://nixos.org/manual/nix/unstable/command-ref/new-cli/nix3-flake.html diff --git a/bench/Main.hs b/bench/Main.hs @@ -12,8 +12,8 @@ import qualified "base16" Data.ByteString.Base16 as R1 main :: IO () main = defaultMain [ - encode - , decode + minimal_encode + , minimal_decode ] minimal_encode :: Benchmark diff --git a/lib/Data/ByteString/Base16.hs b/lib/Data/ByteString/Base16.hs @@ -59,6 +59,8 @@ encode bs@(BI.PS _ _ l) | l < 64 = to_strict_small loop | otherwise = to_strict loop where + -- writing as few words as possible requires performing some length + -- checks up front loop | l `rem` 4 == 0 = go64 bs | (l - 3) `rem` 4 == 0 = case BS.splitAt (l - 3) bs of @@ -113,6 +115,7 @@ encode bs@(BI.PS _ _ l) word4 :: Word8 -> Maybe Word8 word4 w8 = fmap fi (BS.elemIndex w8 hex_charset) +{-# INLINE word4 #-} -- | Decode a base16 'ByteString' to base256. -- @@ -129,6 +132,7 @@ decode bs@(BI.PS _ _ l) | l `quot` 2 < 128 = fmap to_strict_small loop | otherwise = fmap to_strict loop where + -- same story, but we need more checks loop | l `rem` 16 == 0 = go64 mempty bs | (l - 2) `rem` 16 == 0 = case BS.splitAt (l - 2) bs of