ppad-bech32-0.2.6: lib/Data/ByteString/Base32/Internal.hs
{-# OPTIONS_HADDOCK hide, prune #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module: Data.ByteString.Base32.Internal
-- Copyright: (c) 2024 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Tables and the table-driven encoding loop for the bech32 base32
-- charset, shared by 'Data.ByteString.Base32' and
-- 'Data.ByteString.Bech32.Internal'.
module Data.ByteString.Base32.Internal (
enc_tab
, dec_tab
, w5_tab
, encode_with
) where
import qualified Data.Bits as B
import Data.Bits ((.&.), (.|.))
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BI
import Data.Word (Word8)
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Ptr (Ptr, plusPtr)
import Foreign.Storable (peekElemOff, pokeElemOff)
fi :: (Num a, Integral b) => b -> a
fi = fromIntegral
{-# INLINE fi #-}
-- 32-byte encoding table: the bech32 character set. Maps a 5-bit
-- value (0..31) to its bech32 character. ASCII-only with no embedded
-- NUL, so the bytestring 'IsString' rule rewrites the literal to
-- 'unsafePackAddress' and the bytes live in static rodata.
enc_tab :: BS.ByteString
enc_tab = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
{-# NOINLINE enc_tab #-}
-- 256-byte reverse table. Index by an ASCII byte to obtain its
-- 5-bit value (biased into bit 5); valid bech32 chars map to
-- 0x20..0x3f, every other byte maps to 0x40.
--
-- The encoding is chosen so the literal is strictly ASCII and
-- contains no embedded NUL, which is what the bytestring 'IsString'
-- rule needs to rewrite it into 'unsafePackAddress' (cf. 'enc_tab')
-- - the bytes end up in static rodata, with no CAF allocation.
--
-- The 0x40 sentinel is distinguished by bit 6; no value 0x20..0x3f
-- carries that bit, so callers OR-fold every lookup into an
-- accumulator and test 'acc .&. 0x40 == 0' once at the end. The
-- 5-bit value is extracted as 'b .&. 0x1f'.
dec_tab :: BS.ByteString
dec_tab =
"\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x2f\x40\x2a\x31\x35\x34\x3a\x3e\x27\x25\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x3d\x40\x38\x2d\x39\x29\x28\x37\x40\x32\x36\x3f\x3b\x33\x40\
\\x21\x20\x23\x30\x2b\x3c\x2c\x2e\x26\x24\x22\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\
\\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40\x40"
{-# NOINLINE dec_tab #-}
-- 32-byte identity table: maps a 5-bit value to itself. Encoding
-- through it yields the 5-bit values themselves rather than their
-- bech32 characters.
w5_tab :: BS.ByteString
w5_tab = BS.pack [0 .. 31]
{-# NOINLINE w5_tab #-}
-- | Regroup a base256 'ByteString' into 5-bit values, mapping each
-- through the supplied 32-byte table.
encode_with
:: BS.ByteString -- ^ 32-byte table
-> BS.ByteString -- ^ base256-encoded bytestring
-> BS.ByteString
encode_with (BI.PS tfp toff _) (BI.PS sfp soff l) =
let !outlen = (l * 8 + 4) `quot` 5
in BI.unsafeCreate outlen $ \dst ->
withForeignPtr sfp $ \sp0 ->
withForeignPtr tfp $ \tp0 -> do
let !sp = sp0 `plusPtr` soff :: Ptr Word8
!tp = tp0 `plusPtr` toff :: Ptr Word8
encode_loop sp tp dst l 0 0
encode_loop
:: Ptr Word8 -> Ptr Word8 -> Ptr Word8
-> Int -> Int -> Int -> IO ()
encode_loop !sp !tp !dst !len !i !j
| i + 5 <= len = do
a <- peekElemOff sp i
b <- peekElemOff sp (i + 1)
c <- peekElemOff sp (i + 2)
d <- peekElemOff sp (i + 3)
e <- peekElemOff sp (i + 4)
let !w0 = (a `B.shiftR` 3) .&. 0x1f
!w1 = (a `B.shiftL` 2 .|. b `B.shiftR` 6) .&. 0x1f
!w2 = (b `B.shiftR` 1) .&. 0x1f
!w3 = (b `B.shiftL` 4 .|. c `B.shiftR` 4) .&. 0x1f
!w4 = (c `B.shiftL` 1 .|. d `B.shiftR` 7) .&. 0x1f
!w5 = (d `B.shiftR` 2) .&. 0x1f
!w6 = (d `B.shiftL` 3 .|. e `B.shiftR` 5) .&. 0x1f
!w7 = e .&. 0x1f
peekElemOff tp (fi w0) >>= pokeElemOff dst j
peekElemOff tp (fi w1) >>= pokeElemOff dst (j + 1)
peekElemOff tp (fi w2) >>= pokeElemOff dst (j + 2)
peekElemOff tp (fi w3) >>= pokeElemOff dst (j + 3)
peekElemOff tp (fi w4) >>= pokeElemOff dst (j + 4)
peekElemOff tp (fi w5) >>= pokeElemOff dst (j + 5)
peekElemOff tp (fi w6) >>= pokeElemOff dst (j + 6)
peekElemOff tp (fi w7) >>= pokeElemOff dst (j + 7)
encode_loop sp tp dst len (i + 5) (j + 8)
| otherwise = encode_tail sp tp dst len i j
encode_tail
:: Ptr Word8 -> Ptr Word8 -> Ptr Word8
-> Int -> Int -> Int -> IO ()
encode_tail !sp !tp !dst !len !i !j = case len - i of
0 -> pure ()
1 -> do
a <- peekElemOff sp i
let !w0 = (a `B.shiftR` 3) .&. 0x1f
!w1 = (a `B.shiftL` 2) .&. 0x1f
peekElemOff tp (fi w0) >>= pokeElemOff dst j
peekElemOff tp (fi w1) >>= pokeElemOff dst (j + 1)
2 -> do
a <- peekElemOff sp i
b <- peekElemOff sp (i + 1)
let !w0 = (a `B.shiftR` 3) .&. 0x1f
!w1 = (a `B.shiftL` 2 .|. b `B.shiftR` 6) .&. 0x1f
!w2 = (b `B.shiftR` 1) .&. 0x1f
!w3 = (b `B.shiftL` 4) .&. 0x1f
peekElemOff tp (fi w0) >>= pokeElemOff dst j
peekElemOff tp (fi w1) >>= pokeElemOff dst (j + 1)
peekElemOff tp (fi w2) >>= pokeElemOff dst (j + 2)
peekElemOff tp (fi w3) >>= pokeElemOff dst (j + 3)
3 -> do
a <- peekElemOff sp i
b <- peekElemOff sp (i + 1)
c <- peekElemOff sp (i + 2)
let !w0 = (a `B.shiftR` 3) .&. 0x1f
!w1 = (a `B.shiftL` 2 .|. b `B.shiftR` 6) .&. 0x1f
!w2 = (b `B.shiftR` 1) .&. 0x1f
!w3 = (b `B.shiftL` 4 .|. c `B.shiftR` 4) .&. 0x1f
!w4 = (c `B.shiftL` 1) .&. 0x1f
peekElemOff tp (fi w0) >>= pokeElemOff dst j
peekElemOff tp (fi w1) >>= pokeElemOff dst (j + 1)
peekElemOff tp (fi w2) >>= pokeElemOff dst (j + 2)
peekElemOff tp (fi w3) >>= pokeElemOff dst (j + 3)
peekElemOff tp (fi w4) >>= pokeElemOff dst (j + 4)
4 -> do
a <- peekElemOff sp i
b <- peekElemOff sp (i + 1)
c <- peekElemOff sp (i + 2)
d <- peekElemOff sp (i + 3)
let !w0 = (a `B.shiftR` 3) .&. 0x1f
!w1 = (a `B.shiftL` 2 .|. b `B.shiftR` 6) .&. 0x1f
!w2 = (b `B.shiftR` 1) .&. 0x1f
!w3 = (b `B.shiftL` 4 .|. c `B.shiftR` 4) .&. 0x1f
!w4 = (c `B.shiftL` 1 .|. d `B.shiftR` 7) .&. 0x1f
!w5 = (d `B.shiftR` 2) .&. 0x1f
!w6 = (d `B.shiftL` 3) .&. 0x1f
peekElemOff tp (fi w0) >>= pokeElemOff dst j
peekElemOff tp (fi w1) >>= pokeElemOff dst (j + 1)
peekElemOff tp (fi w2) >>= pokeElemOff dst (j + 2)
peekElemOff tp (fi w3) >>= pokeElemOff dst (j + 3)
peekElemOff tp (fi w4) >>= pokeElemOff dst (j + 4)
peekElemOff tp (fi w5) >>= pokeElemOff dst (j + 5)
peekElemOff tp (fi w6) >>= pokeElemOff dst (j + 6)
_ -> pure () -- impossible: 0 <= len - i < 5