BSD-3-Clause licensed by Emily Pillmore
Maintained by [email protected]
This version can be pinned in stack with:base32-1.0@sha256:7f6c04cf8cd6d38e88cfd2bba19c132279e0d4cae7d25328abb0f8517e9612ba,3343

Base32

Build Status Hackage

RFC 4648 standard and extended-hex Base32 encoding, with padded and unpadded variants for strict, lazy, and short ByteString and Text.

import Data.Base32.Types
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Base32 as B32

encoded = B32.encodeBase32' (BS.pack "Sun")
-- encoded :: Base32 'StdPadded ByteString
-- extractBase32 encoded == BS.pack "KN2W4==="
-- B32.decodeBase32 encoded == BS.pack "Sun"

parsed = B32.parseBase32Unpadded (BS.pack "KN2W4")
-- Right "KN2W4"; the original encoded representation is retained

raw = B32.decodeBase32Untyped (BS.pack "KN2W4")
-- Right "Sun"

Use .Hex modules for the extended-hex alphabet. Values carry 'StdPadded, 'StdUnpadded, 'HexPadded, or 'HexUnpadded, preventing accidental mixing of alphabets or padding-specific APIs. See MIGRATION-1.0.md for the changes from 0.4.

Validation

parseBase32Padded and parseBase32Unpadded establish the corresponding padding tag. Both check the alphabet, input length, padding placement, and zero unused trailing bits. They accept upper- and lowercase letters, preserve the original case and representation, and accept empty input. Whitespace is rejected.

Typed decoders use dedicated unchecked loops: provenance establishes validity, so decoding does not repeat alphabet, length, padding, or canonical-bit checks. decodeBase32 uses the StdAlphabet or HexAlphabet constraint to accept both padding forms of its alphabet. decodeBase32Padded and decodeBase32Unpadded require their exact tags; calling them with the wrong padding form is a type error.

The wrapper identifies the alphabet; it does not prove that decoded bytes form UTF-8. Checked Text decoders return Left for invalid UTF-8; decodeBase32With and its variants let you choose a character conversion. For arbitrary binary payloads, use ByteString decoders.

assertBase32, record updates, and the mapping instances can bypass validation. Typed decoding requires a truthful encoded value and, for Text, valid UTF-8 payload bytes. Use parsers and checked decoders for external input.

Lazy encoding and typed decoding preserve streaming through rechunking. Checked lazy decoding and parsing validate a complete finite input.

Optional SIMD

cabal build -fsimd
cabal test -fsimd

The simd flag defaults to off. It enables bundled NEON kernels on AArch64 and runtime-dispatched SSSE3/AVX2 kernels on x86-64, with scalar fallback on other targets. Small inputs and final quanta use Haskell scalar code. No external native library is required.

Strict and short values use SIMD directly. Short codecs use byte arrays without pinning; typed ShortText additionally converts the decoded UTF-8 payload. Lazy variants reach the same kernels through their strict chunks or checked input.

Development

cabal test -f-simd
cabal test -fsimd --ghc-options='-fcheck-prim-bounds -fno-ignore-asserts'
cabal exec -- sh test/typecheck.sh
cabal haddock -fsimd
cabal check

CI tests both flag configurations across GHC 9.6–9.14, builds from the source archive, and runs native sanitizer and guard-page tests on x86-64 and ARM64.

Compare optimized scalar and SIMD benchmark builds separately:

cabal bench -f-simd --builddir=dist-scalar -O2 --benchmark-options='--csv scalar.csv'
cabal bench -fsimd --builddir=dist-simd -O2 --benchmark-options='--csv simd.csv'

Benchmark names identify the selected flag configuration. Both builds measure the same deterministic inputs, with setup excluded from timing. The suite covers strict and short codecs, both alphabets and padding forms, encoding, typed and untyped decoding, parsing, sizes around dispatch thresholds, large buffers, and sliced strict inputs.

The companion base32-lens package needs an update for the 1.0 API before it can be used with this release.

Changes

Revision history for base32

1.0

This release introduces a typed encoding API with breaking signature changes. See MIGRATION-1.0.md for the changes from 0.4.

  • Introduce alphabet- and padding-tagged Base32 values in Data.Base32.Types. Encoders return wrapped values; typed decoders consume them. Checked decoders now use the Untyped suffix for consuming raw Base32 encoded bytes.

    Typed decoding uses unchecked scalar and SIMD loops, with streaming lazy decoding. StdAlphabet and HexAlphabet admit both padding forms in general typed decoders; padding-specific typed decoders require exact tags.

  • Replace isBase32, isValidBase32, isBase32Hex, and isValidBase32Hex with parseBase32Padded and parseBase32Unpadded in all twelve APIs. Parsers preserve input, accept the decoder’s existing case-insensitive alphabets, and check unused pad bits. Noncanonical pad bits are now rejected by decoders too. These were removed because they had no measurable downstream usage, and were of questionable utility.

  • Added an optional, default-off simd flag with NEON, SSSE3, and AVX2 prefixes and a portable scalar fallback, for both standard and extended-hex alphabets.

  • Remove Data.ByteString.Base32.Internal; use Internal.Head for codec entry points and Internal.Utils for validateLastNPads. They were relics of older development.

  • Support GHC 9.6–9.14; update copyright through 2026, CI, bounds, and benchmarks.

0.4

  • Support for GHC 9.8
  • Drop support for GHC <9.x
  • Bump bytestring lower bound to 0.11 (thanks @vlix!)

0.3.1.0

  • Small bug fix
  • Doc updates

0.3

  • Bump lower bounds for bytestring and text to latest significant major/major version
  • Update CI for 8.10.x thru 9.6 coverage
  • Correct the update the latin1 decode default for text modules to utf8 (which is now pure for text-2.0).

0.2.2.0

  • Add support for GHC 9.2.x
  • Drop support for GHC <8.10
  • Drop dependency on ghc-byteorder
  • Bump bounds for deepseq

0.2.1.0

  • Expose Data.ByteString.Base32.Internal API
  • Use closed-form encoding/decoding length calculations, reducing branching and improving performance
  • Update tests:
    • Migrate to using lower heap-footprint concrete dict passing
    • Support for 8.10.5 testing

0.2.0.0

  • Bugfix: fix unpadded base32 encoding (#4)
  • Bugfix: Use decodeLatin1 when decoding, defer to decodeWith* primitives for finer-grained Text to ByteString conversions. (#5)
  • Add short and lazy variants for Text and ByteString (#6)
  • Expanded test coverage to 96% (#7)
  • Improved performance and better error reporting in final quanta (#9)
  • Improved docs + trustworthy/safe annotations (#10)

0.1.1.1 – 2020-02-16

  • Documentation now references correct RFC section

0.1.1 – 2020-02-16

  • Textual interface now uses correct unpadded version

0.1.0.0 – 2020-02-16

  • First version. Released on an unsuspecting world.