commit 11f834993aa8e91ffd6683e85f764e8de7ba306a
parent 195d167a0062eee57596f4b8119c09be3bc46094
Author: Jared Tobin <jared@jtobin.io>
Date: Fri, 17 Jan 2025 22:40:59 +0400
meta: readme
Diffstat:
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