packages feed

base32 0.1.1.2 → 0.2.0.0

raw patch · 26 files changed

+3916/−533 lines, 26 filesdep +QuickCheckdep +ghc-byteorderdep +tasty-quickcheckdep ~bytestringdep ~deepseqPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, ghc-byteorder, tasty-quickcheck, text-short

Dependency ranges changed: bytestring, deepseq

API changes (from Hackage documentation)

+ Data.ByteString.Base32: decodeBase32Padded :: ByteString -> Either Text ByteString
+ Data.ByteString.Base32.Hex: decodeBase32Padded :: ByteString -> Either Text ByteString
+ Data.ByteString.Lazy.Base32: decodeBase32 :: ByteString -> Either Text ByteString
+ Data.ByteString.Lazy.Base32: decodeBase32Padded :: ByteString -> Either Text ByteString
+ Data.ByteString.Lazy.Base32: decodeBase32Unpadded :: ByteString -> Either Text ByteString
+ Data.ByteString.Lazy.Base32: encodeBase32 :: ByteString -> Text
+ Data.ByteString.Lazy.Base32: encodeBase32' :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base32: encodeBase32Unpadded :: ByteString -> Text
+ Data.ByteString.Lazy.Base32: encodeBase32Unpadded' :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base32: isBase32 :: ByteString -> Bool
+ Data.ByteString.Lazy.Base32: isValidBase32 :: ByteString -> Bool
+ Data.ByteString.Lazy.Base32.Hex: decodeBase32 :: ByteString -> Either Text ByteString
+ Data.ByteString.Lazy.Base32.Hex: decodeBase32Padded :: ByteString -> Either Text ByteString
+ Data.ByteString.Lazy.Base32.Hex: decodeBase32Unpadded :: ByteString -> Either Text ByteString
+ Data.ByteString.Lazy.Base32.Hex: encodeBase32 :: ByteString -> Text
+ Data.ByteString.Lazy.Base32.Hex: encodeBase32' :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base32.Hex: encodeBase32Unpadded :: ByteString -> Text
+ Data.ByteString.Lazy.Base32.Hex: encodeBase32Unpadded' :: ByteString -> ByteString
+ Data.ByteString.Lazy.Base32.Hex: isBase32Hex :: ByteString -> Bool
+ Data.ByteString.Lazy.Base32.Hex: isValidBase32Hex :: ByteString -> Bool
+ Data.ByteString.Short.Base32: decodeBase32 :: ShortByteString -> Either Text ShortByteString
+ Data.ByteString.Short.Base32: decodeBase32Padded :: ShortByteString -> Either Text ShortByteString
+ Data.ByteString.Short.Base32: decodeBase32Unpadded :: ShortByteString -> Either Text ShortByteString
+ Data.ByteString.Short.Base32: encodeBase32 :: ShortByteString -> ShortText
+ Data.ByteString.Short.Base32: encodeBase32' :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base32: encodeBase32Unpadded :: ShortByteString -> ShortText
+ Data.ByteString.Short.Base32: encodeBase32Unpadded' :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base32: isBase32 :: ShortByteString -> Bool
+ Data.ByteString.Short.Base32: isValidBase32 :: ShortByteString -> Bool
+ Data.ByteString.Short.Base32.Hex: decodeBase32 :: ShortByteString -> Either Text ShortByteString
+ Data.ByteString.Short.Base32.Hex: decodeBase32Padded :: ShortByteString -> Either Text ShortByteString
+ Data.ByteString.Short.Base32.Hex: decodeBase32Unpadded :: ShortByteString -> Either Text ShortByteString
+ Data.ByteString.Short.Base32.Hex: encodeBase32 :: ShortByteString -> ShortText
+ Data.ByteString.Short.Base32.Hex: encodeBase32' :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base32.Hex: encodeBase32Unpadded :: ShortByteString -> ShortText
+ Data.ByteString.Short.Base32.Hex: encodeBase32Unpadded' :: ShortByteString -> ShortByteString
+ Data.ByteString.Short.Base32.Hex: isBase32Hex :: ShortByteString -> Bool
+ Data.ByteString.Short.Base32.Hex: isValidBase32Hex :: ShortByteString -> Bool
+ Data.Text.Encoding.Base32: decodeBase32Padded :: Text -> Either Text Text
+ Data.Text.Encoding.Base32: decodeBase32PaddedWith :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Encoding.Base32: decodeBase32UnpaddedWith :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Encoding.Base32: decodeBase32With :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Encoding.Base32.Error: ConversionError :: e -> Base32Error e
+ Data.Text.Encoding.Base32.Error: DecodeError :: Text -> Base32Error e
+ Data.Text.Encoding.Base32.Error: data Base32Error e
+ Data.Text.Encoding.Base32.Error: instance Control.DeepSeq.NFData e => Control.DeepSeq.NFData (Data.Text.Encoding.Base32.Error.Base32Error e)
+ Data.Text.Encoding.Base32.Error: instance GHC.Classes.Eq e => GHC.Classes.Eq (Data.Text.Encoding.Base32.Error.Base32Error e)
+ Data.Text.Encoding.Base32.Error: instance GHC.Exception.Type.Exception e => GHC.Exception.Type.Exception (Data.Text.Encoding.Base32.Error.Base32Error e)
+ Data.Text.Encoding.Base32.Error: instance GHC.Generics.Generic (Data.Text.Encoding.Base32.Error.Base32Error e)
+ Data.Text.Encoding.Base32.Error: instance GHC.Show.Show e => GHC.Show.Show (Data.Text.Encoding.Base32.Error.Base32Error e)
+ Data.Text.Encoding.Base32.Hex: decodeBase32Padded :: Text -> Either Text Text
+ Data.Text.Encoding.Base32.Hex: decodeBase32PaddedWith :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Encoding.Base32.Hex: decodeBase32UnpaddedWith :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Encoding.Base32.Hex: decodeBase32With :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Lazy.Encoding.Base32: decodeBase32 :: Text -> Either Text Text
+ Data.Text.Lazy.Encoding.Base32: decodeBase32Padded :: Text -> Either Text Text
+ Data.Text.Lazy.Encoding.Base32: decodeBase32PaddedWith :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Lazy.Encoding.Base32: decodeBase32Unpadded :: Text -> Either Text Text
+ Data.Text.Lazy.Encoding.Base32: decodeBase32UnpaddedWith :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Lazy.Encoding.Base32: decodeBase32With :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Lazy.Encoding.Base32: encodeBase32 :: Text -> Text
+ Data.Text.Lazy.Encoding.Base32: encodeBase32Unpadded :: Text -> Text
+ Data.Text.Lazy.Encoding.Base32: isBase32 :: Text -> Bool
+ Data.Text.Lazy.Encoding.Base32: isValidBase32 :: Text -> Bool
+ Data.Text.Lazy.Encoding.Base32.Hex: decodeBase32 :: Text -> Either Text Text
+ Data.Text.Lazy.Encoding.Base32.Hex: decodeBase32Padded :: Text -> Either Text Text
+ Data.Text.Lazy.Encoding.Base32.Hex: decodeBase32PaddedWith :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Lazy.Encoding.Base32.Hex: decodeBase32Unpadded :: Text -> Either Text Text
+ Data.Text.Lazy.Encoding.Base32.Hex: decodeBase32UnpaddedWith :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Lazy.Encoding.Base32.Hex: decodeBase32With :: (ByteString -> Either err Text) -> ByteString -> Either (Base32Error err) Text
+ Data.Text.Lazy.Encoding.Base32.Hex: encodeBase32 :: Text -> Text
+ Data.Text.Lazy.Encoding.Base32.Hex: encodeBase32Unpadded :: Text -> Text
+ Data.Text.Lazy.Encoding.Base32.Hex: isBase32Hex :: Text -> Bool
+ Data.Text.Lazy.Encoding.Base32.Hex: isValidBase32Hex :: Text -> Bool
+ Data.Text.Short.Encoding.Base32: decodeBase32 :: ShortText -> Either Text ShortText
+ Data.Text.Short.Encoding.Base32: decodeBase32Padded :: ShortText -> Either Text ShortText
+ Data.Text.Short.Encoding.Base32: decodeBase32PaddedWith :: (ShortByteString -> Either err ShortText) -> ShortByteString -> Either (Base32Error err) ShortText
+ Data.Text.Short.Encoding.Base32: decodeBase32Unpadded :: ShortText -> Either Text ShortText
+ Data.Text.Short.Encoding.Base32: decodeBase32UnpaddedWith :: (ShortByteString -> Either err ShortText) -> ShortByteString -> Either (Base32Error err) ShortText
+ Data.Text.Short.Encoding.Base32: decodeBase32With :: (ShortByteString -> Either err ShortText) -> ShortByteString -> Either (Base32Error err) ShortText
+ Data.Text.Short.Encoding.Base32: encodeBase32 :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base32: encodeBase32Unpadded :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base32: isBase32 :: ShortText -> Bool
+ Data.Text.Short.Encoding.Base32: isValidBase32 :: ShortText -> Bool
+ Data.Text.Short.Encoding.Base32.Hex: decodeBase32 :: ShortText -> Either Text ShortText
+ Data.Text.Short.Encoding.Base32.Hex: decodeBase32Padded :: ShortText -> Either Text ShortText
+ Data.Text.Short.Encoding.Base32.Hex: decodeBase32PaddedWith :: (ShortByteString -> Either err ShortText) -> ShortByteString -> Either (Base32Error err) ShortText
+ Data.Text.Short.Encoding.Base32.Hex: decodeBase32Unpadded :: ShortText -> Either Text ShortText
+ Data.Text.Short.Encoding.Base32.Hex: decodeBase32UnpaddedWith :: (ShortByteString -> Either err ShortText) -> ShortByteString -> Either (Base32Error err) ShortText
+ Data.Text.Short.Encoding.Base32.Hex: decodeBase32With :: (ShortByteString -> Either err ShortText) -> ShortByteString -> Either (Base32Error err) ShortText
+ Data.Text.Short.Encoding.Base32.Hex: encodeBase32 :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base32.Hex: encodeBase32Unpadded :: ShortText -> ShortText
+ Data.Text.Short.Encoding.Base32.Hex: isBase32Hex :: ShortText -> Bool
+ Data.Text.Short.Encoding.Base32.Hex: isValidBase32Hex :: ShortText -> Bool

Files

CHANGELOG.md view
@@ -1,5 +1,15 @@ # Revision history for base32 +## 0.2.0.0++* Bugfix: fix unpadded base32 encoding ([#4](https://github.com/emilypi/Base32/pull/4))+* Bugfix: Use `decodeLatin1` when decoding, defer to `decodeWith*` primitives for finer-grained+          `Text` to `ByteString` conversions. ([#5](https://github.com/emilypi/Base32/pull/5))+* Add short and lazy variants for `Text` and `ByteString` ([#6](https://github.com/emilypi/Base32/pull/6))+* Expanded test coverage to 96% ([#7](https://github.com/emilypi/Base32/pull/7))+* Improved performance and better error reporting in final quanta ([#9](https://github.com/emilypi/Base32/pull/9))+* Improved docs + trustworthy/safe annotations ([#10](https://github.com/emilypi/Base32/pull/10))+ ## 0.1.1.1 -- 2020-02-16  * Documentation now references correct RFC section
README.md view
@@ -7,16 +7,21 @@  For the companion optics and pattern synonyms, see [base32-lens](https://hackage.haskell.org/package/base32-lens). - ### Summary -What does this library provide? Here is the summary:+The following types are supported for both padded and unpadded std and extended hex alphabets: -- Support for padded and unpadded Base32 and Base32hex-- Support for `Text` encodings and decodings-- Optics for handling more complex structures with Base32 representations via the `base32-lens` package-- Checks for both valid Base32 and correct Base32 and Base32hex encodings+- `Data.ByteString`+- `Data.ByteString.Lazy`+- `Data.ByteString.Short`+- `Data.Text`+- `Data.Text.Lazy`+- `Data.Text.Short` -There are no dependencies aside from those bundled with GHC:+Additionally this library has -![base32 dependencies](https://i.imgur.com/8CdVsey.png)+- Better performance than `memory` for encode and decode 3-4x.+- Optics for handling more complex structures with Base32 representations via the `base32-lens` package+- Checks for both validity and correctness of Base32 and Base32hex encodings++There are no dependencies aside from those bundled with GHC, `text-short`, and the `ghc-byteorder` re-export.
base32.cabal view
@@ -1,7 +1,7 @@ cabal-version:      2.0 name:               base32-version:            0.1.1.2-synopsis:           RFC 4648-compliant Base32 encodings/decodings+version:            0.2.0.0+synopsis:           Fast RFC 4648-compliant Base32 encoding description:   RFC 4648-compliant Base32 encodings and decodings.   This library provides performant encoding and decoding primitives, as well as support for textual values.@@ -30,8 +30,17 @@   exposed-modules:     Data.ByteString.Base32     Data.ByteString.Base32.Hex+    Data.ByteString.Lazy.Base32+    Data.ByteString.Lazy.Base32.Hex+    Data.ByteString.Short.Base32+    Data.ByteString.Short.Base32.Hex     Data.Text.Encoding.Base32+    Data.Text.Encoding.Base32.Error     Data.Text.Encoding.Base32.Hex+    Data.Text.Lazy.Encoding.Base32+    Data.Text.Lazy.Encoding.Base32.Hex+    Data.Text.Short.Encoding.Base32+    Data.Text.Short.Encoding.Base32.Hex    other-modules:     Data.ByteString.Base32.Internal@@ -42,9 +51,12 @@     Data.ByteString.Base32.Internal.Utils    build-depends:-      base        >=4.10 && <5-    , bytestring  ^>=0.10-    , text        ^>=1.2+      base           >=4.10     && <5+    , bytestring     ^>=0.10+    , deepseq        >=1.4.3.0  && <1.4.5.0+    , ghc-byteorder  ^>=4.11.0.0+    , text           ^>=1.2+    , text-short     ^>=0.1    hs-source-dirs:   src   default-language: Haskell2010@@ -54,16 +66,20 @@   default-language: Haskell2010   type:             exitcode-stdio-1.0   hs-source-dirs:   test-  main-is:          Base32Tests.hs+  other-modules:    Internal+  main-is:          Main.hs   build-depends:       base               >=4.10 && <5     , base32     , bytestring     , memory+    , QuickCheck     , random-bytestring     , tasty     , tasty-hunit+    , tasty-quickcheck     , text+    , text-short  benchmark bench   default-language: Haskell2010
benchmarks/Base32Bench.hs view
@@ -20,33 +20,62 @@       bgroup "encode"       [ bgroup "25"         [ bench "memory" $ whnf ctob bs25-        , bench "base64" $ whnf B32.encodeBase32' bs25+        , bench "base32" $ whnf B32.encodeBase32' bs25         ]       , bgroup "100"         [ bench "memory" $ whnf ctob bs100-        , bench "base64" $ whnf B32.encodeBase32' bs100+        , bench "base32" $ whnf B32.encodeBase32' bs100         ]       , bgroup "1k"         [ bench "memory" $ whnf ctob bs1k-        , bench "base64" $ whnf B32.encodeBase32' bs1k+        , bench "base32" $ whnf B32.encodeBase32' bs1k         ]       , bgroup "10k"         [ bench "memory" $ whnf ctob bs10k-        , bench "base64" $ whnf B32.encodeBase32' bs10k+        , bench "base32" $ whnf B32.encodeBase32' bs10k         ]       , bgroup "100k"         [ bench "memory" $ whnf ctob bs100k-        , bench "base64" $ whnf B32.encodeBase32' bs100k+        , bench "base32" $ whnf B32.encodeBase32' bs100k         ]       , bgroup "1mm"         [ bench "memory" $ whnf ctob bs1mm-        , bench "base64" $ whnf B32.encodeBase32' bs1mm+        , bench "base32" $ whnf B32.encodeBase32' bs1mm         ]       ]+    , env bs' $ \ ~(bs25,bs100,bs1k,bs10k,bs100k,bs1mm) ->+      bgroup "decode"+      [ bgroup "25"+        [ bench "memory" $ whnf btoc bs25+        , bench "base32" $ whnf B32.decodeBase32 bs25+        ]+      , bgroup "100"+        [ bench "memory" $ whnf btoc bs100+        , bench "base32" $ whnf B32.decodeBase32 bs100+        ]+      , bgroup "1k"+        [ bench "memory" $ whnf btoc bs1k+        , bench "base32" $ whnf B32.decodeBase32 bs1k+        ]+      , bgroup "10k"+        [ bench "memory" $ whnf btoc bs10k+        , bench "base32" $ whnf B32.decodeBase32 bs10k+        ]+      , bgroup "100k"+        [ bench "memory" $ whnf btoc bs100k+        , bench "base32" $ whnf B32.decodeBase32 bs100k+        ]+      , bgroup "1mm"+        [ bench "memory" $ whnf btoc bs1mm+        , bench "base32" $ whnf B32.decodeBase32 bs1mm+        ]+      ]     ]   where     ctob :: ByteString -> ByteString     ctob = Mem.convertToBase Mem.Base32+    btoc :: ByteString -> Either String ByteString+    btoc = Mem.convertFromBase Mem.Base32      bs = do       a <- random 25@@ -55,4 +84,13 @@       d <- random 10000       e <- random 100000       f <- random 1000000+      return (a,b,c,d,e,f)++    bs' = do+      a <- B32.encodeBase32' <$> random 25+      b <- B32.encodeBase32' <$> random 100+      c <- B32.encodeBase32' <$> random 1000+      d <- B32.encodeBase32' <$> random 10000+      e <- B32.encodeBase32' <$> random 100000+      f <- B32.encodeBase32' <$> random 1000000       return (a,b,c,d,e,f)
src/Data/ByteString/Base32.hs view
@@ -1,18 +1,20 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Trustworthy #-} -- | -- Module       : Data.ByteString.Base32--- Copyright 	: (c) 2020 Emily Pillmore--- License	: BSD-style+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style ----- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>--- Stability	: Experimental--- Portability	: portable+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable ----- This module contains the combinators implementing the--- RFC 4648 specification for the Base32 encoding including--- unpadded and lenient variants+-- This module contains 'Data.ByteString.ByteString'-valued combinators for+-- implementing the RFC 4648 specification of the Base32+-- encoding format. This includes padded and unpadded decoding variants, as well as+-- internal and external validation for canonicity. -- module Data.ByteString.Base32 ( encodeBase32@@ -21,13 +23,15 @@ , encodeBase32Unpadded , encodeBase32Unpadded' , decodeBase32Unpadded+, decodeBase32Padded -- , decodeBase32Lenient , isBase32 , isValidBase32 ) where  -import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.ByteString.Internal (ByteString(..)) import Data.ByteString.Base32.Internal import Data.ByteString.Base32.Internal.Head import Data.ByteString.Base32.Internal.Tables@@ -35,59 +39,147 @@ import Data.Text (Text) import qualified Data.Text.Encoding as T +import System.IO.Unsafe (unsafeDupablePerformIO) --- | Encode a 'ByteString' value as Base32 'Text' with padding.++-- | Encode a 'ByteString' value as a Base32 'Text' value with padding. -- -- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "KN2W4==="+-- encodeBase32 :: ByteString -> Text encodeBase32 = T.decodeUtf8 . encodeBase32' {-# INLINE encodeBase32 #-} --- | Encode a 'ByteString' value as a Base32 'ByteString'  value with padding.+-- | Encode a 'ByteString' value as a Base32 'ByteString' value with padding. -- -- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> encodeBase32' "Sun"+-- "KN2W4==="+-- encodeBase32' :: ByteString -> ByteString encodeBase32' = encodeBase32_ "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"# {-# INLINE encodeBase32' #-} --- | Decode a padded Base32-encoded 'ByteString' value.+-- | Decode an arbitrarily padded Base32-encoded 'ByteString' value. If its length+-- is not a multiple of 8, then padding characters will be added to fill out the+-- input to a multiple of 8 for safe decoding, as Base32-encoded values are+-- optionally padded. -- -- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> decodeBase32 "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W==="+-- Left "Base32-encoded bytestring has invalid padding"+-- decodeBase32 :: ByteString -> Either Text ByteString-decodeBase32 = decodeBase32_ False stdDecodeTable+decodeBase32 bs@(PS _ _ !l)+    | l == 0 = Right bs+    | r == 0 = unsafeDupablePerformIO $ decodeBase32_ dlen stdDecodeTable bs+    | r == 2 = unsafeDupablePerformIO $ decodeBase32_ dlen stdDecodeTable (BS.append bs "======")+    | r == 4 = validateLastNPads 2 bs $ decodeBase32_ dlen stdDecodeTable (BS.append bs "====")+    | r == 5 = validateLastNPads 3 bs $ decodeBase32_ dlen stdDecodeTable (BS.append bs "===")+    | r == 7 = validateLastNPads 5 bs $ decodeBase32_ dlen stdDecodeTable (BS.append bs "=")+    | otherwise = Left "Base32-encoded bytestring has invalid size"+  where+    !r = l `rem` 8+    !q = l `quot` 8+    !dlen = q * 5 {-# INLINE decodeBase32 #-}  -- | Encode a 'ByteString' value as a Base32 'Text' value without padding. ----- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__: --+-- >>> encodeBase32Unpadded "Sun"+-- "KN2W4"+-- encodeBase32Unpadded :: ByteString -> Text encodeBase32Unpadded = T.decodeUtf8 . encodeBase32Unpadded' {-# INLINE encodeBase32Unpadded #-} --- | Encode a 'ByteString' value as a Base32 'ByteString'  value with padding.+-- | Encode a 'ByteString' value as a Base32 'ByteString' value without padding. ----- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> encodeBase32Unpadded' "Sun"+-- "KN2W4"+-- encodeBase32Unpadded' :: ByteString -> ByteString encodeBase32Unpadded' = encodeBase32NoPad_ "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"# {-# INLINE encodeBase32Unpadded' #-} --- | Decode an arbitarily padded Base32-encoded 'ByteString' value.+-- | Decode an unpadded Base32-encoded 'ByteString' value. ----- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "KN2W4==="+-- Left "Base32-encoded bytestring has invalid padding"+-- decodeBase32Unpadded :: ByteString -> Either Text ByteString-decodeBase32Unpadded = decodeBase32_ True stdDecodeTable+decodeBase32Unpadded bs@(PS _ _ !l)+    | l == 0 = Right bs+    | r == 0 = validateLastNPads 1 bs $ decodeBase32_ dlen stdDecodeTable bs+    | r == 2 = unsafeDupablePerformIO $ decodeBase32_ dlen stdDecodeTable (BS.append bs "======")+    | r == 4 = validateLastNPads 1 bs $ decodeBase32_ dlen stdDecodeTable (BS.append bs "====")+    | r == 5 = validateLastNPads 1 bs $ decodeBase32_ dlen stdDecodeTable (BS.append bs "===")+    | r == 7 = validateLastNPads 1 bs $ decodeBase32_ dlen stdDecodeTable (BS.append bs "=")+    | otherwise = Left "Base32-encoded bytestring has invalid size"+  where+    !q = l `quot` 8+    !r = l `rem` 8+    !dlen = q * 5 {-# INLINE decodeBase32Unpadded #-} +-- | Decode a padded Base32-encoded 'ByteString' value.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "KN2W4"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: ByteString -> Either Text ByteString+decodeBase32Padded bs@(PS _ _ !l)+    | l == 0 = Right bs+    | r == 1 = Left "Base32-encoded bytestring has invalid size"+    | r == 3 = Left "Base32-encoded bytestring has invalid size"+    | r == 6 = Left "Base32-encoded bytestring has invalid size"+    | r /= 0 = Left "Base32-encoded bytestring requires padding"+    | otherwise = unsafeDupablePerformIO $ decodeBase32_ dlen stdDecodeTable bs+  where+    !q = l `quot` 8+    !r = l `rem` 8+    !dlen = q * 5+{-# INLINE decodeBase32Padded #-}+ -- -- | Leniently decode an unpadded Base32-encoded 'ByteString' value. This function -- -- will not generate parse errors. If input data contains padding chars, -- -- then the input will be parsed up until the first pad character.@@ -98,8 +190,19 @@ -- decodeBase32Lenient = decodeBase32Lenient_ decodeB32Table -- {-# INLINE decodeBase32Lenient #-} --- | Tell whether a 'ByteString' value is base32 encoded.+-- | Tell whether a 'ByteString' value is encoded in padded or unpadded Base32 format --+-- === __Examples__:+--+-- >>> isBase32 "KN2W4"+-- True+--+-- >>> isBase32 "KN2W4==="+-- True+--+-- >>> isBase32 "KN2W4=="+-- False+-- isBase32 :: ByteString -> Bool isBase32 bs = isValidBase32 bs && isRight (decodeBase32 bs) {-# INLINE isBase32 #-}@@ -107,8 +210,19 @@ -- | Tell whether a 'ByteString' value is a valid Base32 format. -- -- This will not tell you whether or not this is a correct Base32 representation,--- only that it conforms to the correct shape. To check whether it is a true--- Base32 encoded 'ByteString' value, use 'isBase32'.+-- only that it conforms to the correct shape (including padding/size etc.).+-- To check whether it is a true Base32 encoded 'ByteString' value, use 'isBase32'.+--+-- === __Examples__:+--+-- >>> isValidBase32 "KN2W4"+-- True+--+-- >>> isValidBase32 "KN2W4="+-- False+--+-- >>> isValidBase32 "KN2W4%"+-- False -- isValidBase32 :: ByteString -> Bool isValidBase32 = validateBase32 "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
src/Data/ByteString/Base32/Hex.hs view
@@ -1,18 +1,20 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Trustworthy #-} -- | -- Module       : Data.ByteString.Base32.Hex--- Copyright 	: (c) 2020 Emily Pillmore--- License	: BSD-style+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style ----- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>--- Stability	: Experimental--- Portability	: portable+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable ----- This module contains the combinators implementing the--- RFC 4648 specification for the Base32-Hex encoding including--- unpadded and lenient variants+-- This module contains 'Data.ByteString.ByteString'-valued combinators for+-- implementing the RFC 4648 specification of the Base32hex+-- encoding format. This includes padded and unpadded decoding variants, as well as+-- internal and external validation for canonicity. -- module Data.ByteString.Base32.Hex ( encodeBase32@@ -21,12 +23,15 @@ , encodeBase32Unpadded , encodeBase32Unpadded' , decodeBase32Unpadded+, decodeBase32Padded -- , decodeBase32Lenient , isBase32Hex , isValidBase32Hex ) where -import Data.ByteString (ByteString)++import qualified Data.ByteString as BS+import Data.ByteString.Internal (ByteString(..)) import Data.ByteString.Base32.Internal import Data.ByteString.Base32.Internal.Head import Data.ByteString.Base32.Internal.Tables@@ -34,79 +39,180 @@ import Data.Text (Text) import qualified Data.Text.Encoding as T +import System.IO.Unsafe (unsafeDupablePerformIO) + -- | Encode a 'ByteString' value as a Base32hex 'Text' value with padding. -- -- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7> --+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "ADQMS==="+-- encodeBase32 :: ByteString -> Text encodeBase32 = T.decodeUtf8 . encodeBase32' {-# INLINE encodeBase32 #-} --- | Encode a 'ByteString' as a Base32hex 'ByteString' value with padding.+-- | Encode a 'ByteString' value as a Base32hex 'ByteString' value with padding. -- -- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7> --+-- === __Examples__:+--+-- >>> encodeBase32' "Sun"+-- "ADQMS==="+-- encodeBase32' :: ByteString -> ByteString encodeBase32' = encodeBase32_ "0123456789ABCDEFGHIJKLMNOPQRSTUV"# {-# INLINE encodeBase32' #-} --- | Decode a padded Base32hex encoded 'ByteString' value.+-- | Decode an arbitrarily padded Base32hex-encoded 'ByteString' value. If its length+-- is not a multiple of 8, then padding characters will be added to fill out the+-- input to a multiple of 8 for safe decoding, as Base32hex-encoded values are+-- optionally padded. -- -- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7> --+-- === __Examples__:+--+-- >>> decodeBase32 "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32 "ADQM==="+-- Left "Base32-encoded bytestring has invalid padding"+-- decodeBase32 :: ByteString -> Either Text ByteString-decodeBase32 = decodeBase32_ False hexDecodeTable+decodeBase32 bs@(PS _ _ !l)+    | l == 0 = Right bs+    | r == 0 = unsafeDupablePerformIO $ decodeBase32_ dlen hexDecodeTable bs+    | r == 2 = unsafeDupablePerformIO $ decodeBase32_ dlen hexDecodeTable (BS.append bs "======")+    | r == 4 = validateLastNPads 2 bs $ decodeBase32_ dlen hexDecodeTable (BS.append bs "====")+    | r == 5 = validateLastNPads 3 bs $ decodeBase32_ dlen hexDecodeTable (BS.append bs "===")+    | r == 7 = validateLastNPads 5 bs $ decodeBase32_ dlen hexDecodeTable (BS.append bs "=")+    | otherwise = Left "Base32-encoded bytestring has invalid size"+  where+    !r = l `rem` 8+    !q = l `quot` 8+    !dlen = q * 8 {-# INLINE decodeBase32 #-} --- | Encode a 'ByteString' as a Base32hex 'Text' value without padding.+-- | Encode a 'ByteString' value as a Base32hex 'Text' value without padding. ----- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7> --+-- === __Examples__:+--+-- >>> encodeBase32Unpadded' "Sun"+-- "ADQMS"+-- encodeBase32Unpadded :: ByteString -> Text encodeBase32Unpadded = T.decodeUtf8 . encodeBase32Unpadded' {-# INLINE encodeBase32Unpadded #-} --- | Encode a 'ByteString' as a Base32hex 'ByteString' value without padding.+-- | Encode a 'ByteString' value as a Base32hex 'ByteString' value without padding. ----- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7> --+-- === __Examples__:+--+-- >>> encodeBase32Unpadded' "Sun"+-- "ADQMS"+-- encodeBase32Unpadded' :: ByteString -> ByteString encodeBase32Unpadded' = encodeBase32NoPad_ "0123456789ABCDEFGHIJKLMNOPQRSTUV"# {-# INLINE encodeBase32Unpadded' #-} --- | Decode an arbitrarily padded Base32hex encoded 'ByteString' value.+-- | Decode an unpadded Base32hex-encoded 'ByteString' value. ----- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7> --+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+-- decodeBase32Unpadded :: ByteString -> Either Text ByteString-decodeBase32Unpadded = decodeBase32_ True hexDecodeTable+decodeBase32Unpadded bs@(PS _ _ !l)+    | l == 0 = Right bs+    | r == 0 = validateLastNPads 1 bs $ decodeBase32_ dlen hexDecodeTable bs+    | r == 2 = unsafeDupablePerformIO $ decodeBase32_ dlen hexDecodeTable (BS.append bs "======")+    | r == 4 = validateLastNPads 1 bs $ decodeBase32_ dlen hexDecodeTable (BS.append bs "====")+    | r == 5 = validateLastNPads 1 bs $ decodeBase32_ dlen hexDecodeTable (BS.append bs "===")+    | r == 7 = validateLastNPads 1 bs $ decodeBase32_ dlen hexDecodeTable (BS.append bs "=")+    | otherwise = Left "Base32-encoded bytestring has invalid size"+  where+    !q = l `quot` 8+    !r = l `rem` 8+    !dlen = q * 5 {-# INLINE decodeBase32Unpadded #-} --- -- | Leniently decode an unpadded Base32hex-encoded 'ByteString'. This function--- -- will not generate parse errors. If input data contains padding chars,--- -- then the input will be parsed up until the first pad character.--- ----- -- __Note:__ This is not RFC 4648-compliant.--- ----- decodeBase32Lenient :: ByteString -> ByteString--- decodeBase32Lenient = decodeBase32Lenient_ decodeB32HexTable--- {-# INLINE decodeBase32Lenient #-}+-- | Decode a padded Base32hex-encoded 'ByteString' value.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "ADQMS"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: ByteString -> Either Text ByteString+decodeBase32Padded bs@(PS _ _ !l)+    | l == 0 = Right bs+    | r == 1 = Left "Base32-encoded bytestring has invalid size"+    | r == 3 = Left "Base32-encoded bytestring has invalid size"+    | r == 6 = Left "Base32-encoded bytestring has invalid size"+    | r /= 0 = Left "Base32-encoded bytestring requires padding"+    | otherwise = unsafeDupablePerformIO $ decodeBase32_ dlen hexDecodeTable bs+  where+    !q = l `quot` 8+    !r = l `rem` 8+    !dlen = q * 5+{-# INLINE decodeBase32Padded #-} --- | Tell whether a 'ByteString' is Base32hex-encoded.+-- | Tell whether a 'ByteString' value is encoded in padded or unpadded Base32hex format --+-- === __Examples__:+--+-- >>> isBase32Hex "ADQMS"+-- True+--+-- >>> isBase32Hex "ADQMS==="+-- True+--+-- >>> isBase32Hex "ADQMS=="+-- False+-- isBase32Hex :: ByteString -> Bool isBase32Hex bs = isValidBase32Hex bs && isRight (decodeBase32 bs) {-# INLINE isBase32Hex #-} --- | Tell whether a 'ByteString' is a valid Base32hex format.+-- | Tell whether a 'ByteString' value is a valid Base32hex format. -- -- This will not tell you whether or not this is a correct Base32hex representation,--- only that it conforms to the correct shape. To check whether it is a true--- Base32 encoded 'ByteString' value, use 'isBase32Hex'.+-- only that it conforms to the correct shape (including padding/size etc.).+-- To check whether it is a true Base32hex encoded 'ByteString' value, use 'isBase32'.+--+-- === __Examples__:+--+-- >>> isValidBase32Hex "ADQMS"+-- True+--+-- >>> isValidBase32Hex "ADQMS="+-- False+--+-- >>> isValidBase32Hex "ADQMS%"+-- False -- isValidBase32Hex :: ByteString -> Bool isValidBase32Hex = validateBase32 "0123456789ABCDEFGHIJKLMNOPQRSTUV"
src/Data/ByteString/Base32/Internal.hs view
@@ -18,30 +18,46 @@ -- module Data.ByteString.Base32.Internal ( validateBase32+, validateLastNPads ) where   import qualified Data.ByteString as BS import Data.ByteString.Internal+import Data.Text (Text)  import Foreign.ForeignPtr import Foreign.Ptr import Foreign.Storable +import GHC.Word++import System.IO.Unsafe+ -- -------------------------------------------------------------------------- ----- Validating Base64+-- Validating Base32  validateBase32 :: ByteString -> ByteString -> Bool-validateBase32 !alphabet (PS fp off l) =-    accursedUnutterablePerformIO $ withForeignPtr fp $ \p ->-      go (plusPtr p off) (plusPtr p (l + off))+validateBase32 !alphabet bs@(PS _ _ l)+    | l == 0 = True+    | r == 0 = f bs+    | r == 2 = f (BS.append bs "======")+    | r == 4 = f (BS.append bs "====")+    | r == 5 = f (BS.append bs "===")+    | r == 7 = f (BS.append bs "=")+    | otherwise = False   where+    r = l `rem` 8++    f (PS fp o l') = accursedUnutterablePerformIO $ withForeignPtr fp $ \p ->+      go (plusPtr p o) (plusPtr p (l' + o))+     go !p !end       | p == end = return True       | otherwise = do         w <- peek p -        let f a+        let check a               | a == 0x3d, plusPtr p 1 == end = True               | a == 0x3d, plusPtr p 2 == end = True               | a == 0x3d, plusPtr p 3 == end = True@@ -51,5 +67,53 @@               | a == 0x3d = False               | otherwise = BS.elem a alphabet -        if f w then go (plusPtr p 1) end else return False+        if check w then go (plusPtr p 1) end else return False {-# INLINE validateBase32 #-}++-- | This function checks that the last N-chars of a bytestring are '='+-- and, if true, fails with a message or completes some io action.+--+-- This is necessary to check when decoding permissively (i.e. filling in padding chars).+-- Consider the following 8 cases of a string of length l:+--+-- l = 0 mod 8: No pad chars are added, since the input is assumed to be good.+-- l = 1 mod 8: Never an admissible length in base32+-- l = 2 mod 8: 6 padding chars are added. If padding chars are present in the string, they will fail as to decode as final quanta+-- l = 3 mod 8: Never an admissible length in base32+-- l = 4 mod 8: 4 padding chars are added. If 2 padding chars are present in the string this can be "completed" in the sense that+--              it now acts like a string `l == 2 mod 8` with 6 padding chars, and could potentially form corrupted data.+-- l = 5 mod 8: 3 padding chars are added. If 3 padding chars are present in the string, this could form corrupted data like in the+--              previous case.+-- l = 6 mod 8: Never an admissible length in base32+-- l = 7 mod 8: 1 padding char is added. If 5 padding chars are present in the string, this could form corrupted data like the+--              previous cases.+--+-- Hence, permissive decodes should only fill in padding chars when it makes sense to add them. That is,+-- if an input is degenerate, it should never succeed when we add padding chars. We need the following invariant to hold:+--+-- @+--   B32.decodeUnpadded <|> B32.decodePadded ~ B32.decode+-- @+--+validateLastNPads+    :: Int+    -> ByteString+    -> IO (Either Text ByteString)+    -> Either Text ByteString+validateLastNPads !n (PS !fp !o !l) io+    | not valid = Left "Base32-encoded bytestring has invalid padding"+    | otherwise = unsafeDupablePerformIO io+  where+    !lo = l + o+    valid = accursedUnutterablePerformIO $ withForeignPtr fp $ \p -> do+      let end = plusPtr p lo++      let go :: Ptr Word8 -> IO Bool+          go !q+            | q == end = return True+            | otherwise = do+              a <- peek q+              if a == 0x3d then return False else go (plusPtr q 1)++      go (plusPtr p (lo - n))+{-# INLINE validateLastNPads #-}
src/Data/ByteString/Base32/Internal/Head.hs view
@@ -9,10 +9,10 @@ ) where  -import qualified Data.ByteString as BS import Data.ByteString.Internal import Data.ByteString.Base32.Internal.Loop import Data.ByteString.Base32.Internal.Tail+import Data.ByteString.Base32.Internal.Utils import Data.Text (Text)  import Foreign.Ptr@@ -28,64 +28,49 @@ -- | Head of the base32 encoding loop - marshal data, assemble loops -- encodeBase32_ :: Addr# -> ByteString -> ByteString-encodeBase32_ !lut (PS !sfp !soff !slen) =-    unsafeCreate dlen $ \dptr ->-      withForeignPtr sfp $ \sptr -> do-        let !end = plusPtr sptr (soff + slen)-        innerLoop-          lut-          (castPtr dptr)-          (plusPtr sptr soff)-          end-          (loopTail lut end)+encodeBase32_ !lut (PS !sfp !o !l) =+    unsafeDupablePerformIO $ do+      dfp <- mallocPlainForeignPtrBytes dlen+      withForeignPtr dfp $ \dptr ->+        withForeignPtr sfp $ \sptr -> do+          let !end = plusPtr sptr (l + o)+          innerLoop+            lut+            (castPtr dptr)+            (plusPtr sptr o)+            end+            (loopTail lut dfp dptr end)   where-    !dlen = 8 * ((slen + 4) `div` 5)+    !q = (l * 8) `quot` 5+    !r = (l * 8) `rem` 5+    !dlen = padCeilN 8 (q + if r == 0 then 0 else 1)  -- | Head of the unpadded base32 encoding loop - marshal data, assemble loops -- encodeBase32NoPad_ :: Addr# -> ByteString -> ByteString-encodeBase32NoPad_ !lut (PS !sfp !soff !slen)-    = unsafeDupablePerformIO $ do+encodeBase32NoPad_ !lut (PS !sfp !o !l) =+    unsafeDupablePerformIO $ do       !dfp <- mallocPlainForeignPtrBytes dlen       withForeignPtr dfp $ \dptr ->         withForeignPtr sfp $ \sptr -> do-          let !end = plusPtr sptr (soff + slen)-          innerLoopNoPad-            lut+          let !end = plusPtr sptr (l + o)+          innerLoop lut             (castPtr dptr)-            (plusPtr sptr soff)+            (plusPtr sptr o)             end-            (loopTailNoPad lut dfp end)+            (loopTailNoPad lut dfp dptr end)   where-    !dlen = 8 * ((slen + 4) `div` 5)+    !q = (l * 8) `quot` 5+    !r = (l * 8) `rem` 5+    !dlen = padCeilN 8 (q + if r == 0 then 0 else 1)  -- | Head of the base32 decoding loop - marshal data, assemble loops ---decodeBase32_ :: Bool -> ForeignPtr Word8 -> ByteString -> Either Text ByteString-decodeBase32_ !pad !alphabet bs@(PS _ _ !l)-    | l == 0 = Right ""-    | r /= 0, pad =-      if-        | r == 2 -> go (BS.append bs (BS.replicate 6 0x3d))-        | r == 4 -> go (BS.append bs (BS.replicate 4 0x3d))-        | r == 5 -> go (BS.append bs (BS.replicate 3 0x3d))-        | r == 7 -> go (BS.append bs (BS.replicate 1 0x3d))-        | otherwise -> Left "invalid bytestring size"-    | r /= 0, not pad = Left "invalid padding"-    | otherwise = go bs-  where-    (!q, !r) = l `divMod` 8-    !dlen = q * 8--    go (PS !sfp !soff !slen) = unsafeDupablePerformIO $ do-      !dfp <- mallocPlainForeignPtrBytes dlen-      withForeignPtr dfp $ \dptr ->-        withForeignPtr alphabet $ \(Ptr lut) ->-        withForeignPtr sfp $ \sptr -> do-          let !end = plusPtr sptr (soff + slen)-          decodeLoop-            lut-            (castPtr dptr)-            (plusPtr sptr soff)-            end-            (decodeTail lut dfp end)+decodeBase32_ :: Int -> ForeignPtr Word8 -> ByteString -> IO (Either Text ByteString)+decodeBase32_ !dlen !dtfp (PS !sfp !soff !slen) =+    withForeignPtr dtfp $ \(Ptr dtable) ->+    withForeignPtr sfp $ \sptr -> do+      dfp <- mallocPlainForeignPtrBytes dlen+      withForeignPtr dfp $ \dptr -> do+        let !end = plusPtr sptr (soff + slen)+        decodeLoop dtable dfp dptr (plusPtr sptr soff) end
src/Data/ByteString/Base32/Internal/Loop.hs view
@@ -1,20 +1,21 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE MagicHash #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE TypeApplications #-} module Data.ByteString.Base32.Internal.Loop ( innerLoop-, innerLoopNoPad , decodeLoop ) where  import Data.Bits-import Data.ByteString (ByteString)+import Data.ByteString.Internal (ByteString(..)) import Data.ByteString.Base32.Internal.Utils import Data.Text (Text) import qualified Data.Text as T  import Foreign.Ptr+import Foreign.ForeignPtr import Foreign.Storable  import GHC.Exts@@ -29,132 +30,179 @@     -> Ptr Word64     -> Ptr Word8     -> Ptr Word8-    -> (Ptr Word8 -> Ptr Word8 -> IO ())-    -> IO ()+    -> (Ptr Word8 -> Ptr Word8 -> IO ByteString)+    -> IO ByteString innerLoop !lut !dptr !sptr !end finish = go dptr sptr   where-    lix a = return $ w64 (aix (fromIntegral a .&. 0x1f) lut)+    lix a = w64 (aix (fromIntegral a .&. 0x1f) lut)     {-# INLINE lix #-}      go !dst !src       | plusPtr src 4 >= end = finish (castPtr dst) src       | otherwise = do-#ifdef WORDS_BIGENDIAN-        !t <- peek @Word32 (castPtr src)-#else-        !t <- byteSwap32 <$> peek @Word32 (castPtr src)-#endif+        !t <- peekWord32BE (castPtr src)         !u <- w32 <$> peek (plusPtr src 4) -        !a <- lix (unsafeShiftR t 27)-        !b <- lix (unsafeShiftR t 22)-        !c <- lix (unsafeShiftR t 17)-        !d <- lix (unsafeShiftR t 12)-        !e <- lix (unsafeShiftR t 7)-        !f <- lix (unsafeShiftR t 2)-        !g <- lix ((unsafeShiftL t 3) .|. (unsafeShiftR u 5))-        !h <- lix u+        let !a = lix (unsafeShiftR t 27)+            !b = lix (unsafeShiftR t 22)+            !c = lix (unsafeShiftR t 17)+            !d = lix (unsafeShiftR t 12)+            !e = lix (unsafeShiftR t 7)+            !f = lix (unsafeShiftR t 2)+            !g = lix ((unsafeShiftL t 3) .|. (unsafeShiftR u 5))+            !h = lix u -        w <- return $ a-          .|. (unsafeShiftL b 8)-          .|. (unsafeShiftL c 16)-          .|. (unsafeShiftL d 24)-          .|. (unsafeShiftL e 32)-          .|. (unsafeShiftL f 40)-          .|. (unsafeShiftL g 48)-          .|. (unsafeShiftL h 56)+        let !w = a+             .|. (unsafeShiftL b 8)+             .|. (unsafeShiftL c 16)+             .|. (unsafeShiftL d 24)+             .|. (unsafeShiftL e 32)+             .|. (unsafeShiftL f 40)+             .|. (unsafeShiftL g 48)+             .|. (unsafeShiftL h 56)          poke dst w-         go (plusPtr dst 8) (plusPtr src 5) {-# INLINE innerLoop #-} -innerLoopNoPad-    :: Addr#-    -> Ptr Word64-    -> Ptr Word8-    -> Ptr Word8-    -> (Ptr Word8 -> Ptr Word8 -> Int -> IO ByteString)-    -> IO ByteString-innerLoopNoPad !lut !dptr !sptr !end finish = go dptr sptr 0-  where-    lix a = return $ w64 (aix (fromIntegral a .&. 0x1f) lut)--    go !dst !src !n-      | plusPtr src 4 >= end = finish (castPtr dptr) src n-      | otherwise = do-#ifdef WORDS_BIGENDIAN-        !t <- peek @Word32 (castPtr src)-#else-        !t <- byteSwap32 <$> peek @Word32 (castPtr src)-#endif-        !u <- w32 <$> peek (plusPtr src 4)--        !a <- lix (unsafeShiftR t 27)-        !b <- lix (unsafeShiftR t 22)-        !c <- lix (unsafeShiftR t 17)-        !d <- lix (unsafeShiftR t 12)-        !e <- lix (unsafeShiftR t 7)-        !f <- lix (unsafeShiftR t 2)-        !g <- lix ((unsafeShiftL t 3) .|. (unsafeShiftR u 5))-        !h <- lix u--        !w <- return $ a-          .|. (unsafeShiftL b 8)-          .|. (unsafeShiftL c 16)-          .|. (unsafeShiftL d 24)-          .|. (unsafeShiftL e 32)-          .|. (unsafeShiftL f 40)-          .|. (unsafeShiftL g 48)-          .|. (unsafeShiftL h 56)--        poke dst w--        go (plusPtr dst 8) (plusPtr src 5) (n + 8)-{-# INLINE innerLoopNoPad #-}- -- ------------------------------------------------------------------------ -- -- Decoding loops  decodeLoop     :: Addr#+    -> ForeignPtr Word8     -> Ptr Word8     -> Ptr Word64     -> Ptr Word8-    -> (Ptr Word8 -> Ptr Word8 -> Int -> IO (Either Text ByteString))     -> IO (Either Text ByteString)-decodeLoop !lut !dptr !sptr !end finish = go dptr sptr 0+decodeLoop !lut !dfp !dptr !sptr !end = go dptr sptr   where     lix a = w64 (aix (fromIntegral a) lut) -    roll !w !acc = (acc `unsafeShiftL` 5) .|. lix w+    err :: Ptr Word64 -> IO (Either Text ByteString)+    err p = return . Left . T.pack+      $ "invalid character at offset: "+      ++ show (p `minusPtr` sptr) -    err = return . Left . T.pack+    padErr :: Ptr Word64 -> IO (Either Text ByteString)+    padErr p =  return . Left . T.pack+      $ "invalid padding at offset: "+      ++ show (p `minusPtr` sptr) -    go !dst !src !n-      | plusPtr src 8 == end = finish dst (castPtr src) n+    look :: Ptr Word8 -> IO Word64+    look !p = lix . w64 <$> peek @Word8 p++    go !dst !src+      | plusPtr src 8 >= end = do++        let src' = castPtr src++        a <- look src'+        b <- look (plusPtr src' 1)+        c <- look (plusPtr src' 2)+        d <- look (plusPtr src' 3)+        e <- look (plusPtr src' 4)+        f <- look (plusPtr src' 5)+        g <- look (plusPtr src' 6)+        h <- look (plusPtr src' 7)+        finalChunk dst src a b c d e f g h+       | otherwise = do-#ifdef WORDS_BIGENDIAN-        !t <- peek src-#else-        !t <- byteSwap64 <$> peek src-#endif-        !w <- return-          $ roll (unsafeShiftR t 0)-          $ roll (unsafeShiftR t 8)-          $ roll (unsafeShiftR t 16)-          $ roll (unsafeShiftR t 24)-          $ roll (unsafeShiftR t 32)-          $ roll (unsafeShiftR t 40)-          $ roll (unsafeShiftR t 48)-          $ roll (unsafeShiftR t 56)-          0+        !t <- peekWord64BE src -        if w /= 0xff-        then do-          poke @Word8 dst (fromIntegral (w `unsafeShiftR` 32))-          poke @Word32 (castPtr (plusPtr dst 1)) (byteSwap32 (fromIntegral w))-          go (plusPtr dst 5) (plusPtr src 8) (n + 5)-        else err-          $ "invalid character at offset: "-          ++ show (src `minusPtr` sptr)+        let a = lix (unsafeShiftR t 56)+            b = lix (unsafeShiftR t 48)+            c = lix (unsafeShiftR t 40)+            d = lix (unsafeShiftR t 32)+            e = lix (unsafeShiftR t 24)+            f = lix (unsafeShiftR t 16)+            g = lix (unsafeShiftR t 8)+            h = lix t++        decodeChunk dst src a b c d e f g h++    finalChunk !dst !src !a !b !c !d !e !f !g !h+      | a == 0x63 = padErr src+      | b == 0x63 = padErr (plusPtr src 1)+      | a == 0xff = err src+      | b == 0xff = err (plusPtr src 1)+      | c == 0xff = err (plusPtr src 2)+      | d == 0xff = err (plusPtr src 3)+      | e == 0xff = err (plusPtr src 4)+      | f == 0xff = err (plusPtr src 5)+      | g == 0xff = err (plusPtr src 6)+      | h == 0xff = err (plusPtr src 7)+      | otherwise = do++        let !o1 = (fromIntegral a `unsafeShiftL` 3) .|. (fromIntegral b `unsafeShiftR` 2)+            !o2 = (fromIntegral b `unsafeShiftL` 6)+              .|. (fromIntegral c `unsafeShiftL` 1)+              .|. (fromIntegral d `unsafeShiftR` 4)+            !o3 = (fromIntegral d `unsafeShiftL` 4) .|. (fromIntegral e `unsafeShiftR` 1)+            !o4 = (fromIntegral e `unsafeShiftL` 7)+              .|. (fromIntegral f `unsafeShiftL` 2)+              .|. (fromIntegral g `unsafeShiftR` 3)+            !o5 = (fromIntegral g `unsafeShiftL` 5) .|. fromIntegral h++        poke @Word8 dst o1+        poke @Word8 (plusPtr dst 1) o2++        case (c,d,e,f,g,h) of+          (0x63,0x63,0x63,0x63,0x63,0x63) ->+            return (Right (PS dfp 0 (1 + minusPtr dst dptr)))+          (0x63,_,_,_,_,_) -> padErr (plusPtr src 3)+          (_,0x63,0x63,0x63,0x63,0x63) -> padErr (plusPtr src 3)+          (_,0x63,_,_,_,_) -> padErr (plusPtr src 4)+          (_,_,0x63,0x63,0x63,0x63) -> do+            poke @Word8 (plusPtr dst 2) o3+            return (Right (PS dfp 0 (2 + minusPtr dst dptr)))+          (_,_,0x63,_,_,_) -> padErr (plusPtr src 5)+          (_,_,_,0x63,0x63,0x63) -> do+            poke @Word8 (plusPtr dst 2) o3+            poke @Word8 (plusPtr dst 3) o4+            return (Right (PS dfp 0 (3 + minusPtr dst dptr)))+          (_,_,_,0x63,_,_) -> padErr (plusPtr src 6)+          (_,_,_,_,0x63,0x63) -> padErr (plusPtr src 6)+          (_,_,_,_,0x63,_) -> padErr (plusPtr src 7)+          (_,_,_,_,_,0x63) -> do+            poke @Word8 (plusPtr dst 2) o3+            poke @Word8 (plusPtr dst 3) o4+            poke @Word8 (plusPtr dst 4) o5+            return (Right (PS dfp 0 (4 + minusPtr dst dptr)))+          (_,_,_,_,_,_) -> do+            poke @Word8 (plusPtr dst 2) o3+            poke @Word8 (plusPtr dst 3) o4+            poke @Word8 (plusPtr dst 4) o5+            return (Right (PS dfp 0 (5 + minusPtr dst dptr)))++    decodeChunk !dst !src !a !b !c !d !e !f !g !h+      | a == 0x63 = padErr src+      | b == 0x63 = padErr (plusPtr src 1)+      | c == 0x63 = padErr (plusPtr src 2)+      | d == 0x63 = padErr (plusPtr src 3)+      | e == 0x63 = padErr (plusPtr src 4)+      | f == 0x63 = padErr (plusPtr src 5)+      | g == 0x63 = padErr (plusPtr src 6)+      | h == 0x63 = padErr (plusPtr src 7)+      | a == 0xff = err src+      | b == 0xff = err (plusPtr src 1)+      | c == 0xff = err (plusPtr src 2)+      | d == 0xff = err (plusPtr src 3)+      | e == 0xff = err (plusPtr src 4)+      | f == 0xff = err (plusPtr src 5)+      | g == 0xff = err (plusPtr src 6)+      | h == 0xff = err (plusPtr src 7)+      | otherwise = do++        let !w = ((unsafeShiftL a 35)+              .|. (unsafeShiftL b 30)+              .|. (unsafeShiftL c 25)+              .|. (unsafeShiftL d 20)+              .|. (unsafeShiftL e 15)+              .|. (unsafeShiftL f 10)+              .|. (unsafeShiftL g 5)+              .|. h) :: Word64++        poke @Word32 (castPtr dst) (byteSwap32 (fromIntegral (unsafeShiftR w 8)))+        poke @Word8 (plusPtr dst 4) (fromIntegral w)+        go (plusPtr dst 5) (plusPtr src 8)
src/Data/ByteString/Base32/Internal/Tables.hs view
@@ -18,7 +18,7 @@     [ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff     , 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff     , 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff-    , 0xff,0xff,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff+    , 0xff,0xff,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0xff,0xff,0xff,0xff,0xff,0x63,0xff,0xff     , 0xff,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e     , 0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xff,0xff,0xff,0xff,0xff     , 0xff,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e@@ -39,7 +39,7 @@     [ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff     , 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff     , 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff-    , 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xff,0xff,0xff,0xff,0xff,0xff+    , 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xff,0xff,0xff,0x63,0xff,0xff     , 0xff,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18     , 0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff     , 0xff,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18
src/Data/ByteString/Base32/Internal/Tail.hs view
@@ -7,15 +7,12 @@ module Data.ByteString.Base32.Internal.Tail ( loopTail , loopTailNoPad-, decodeTail ) where   import Data.Bits import Data.ByteString.Internal import Data.ByteString.Base32.Internal.Utils-import Data.Text (Text)-import qualified Data.Text as T  import Foreign.Ptr import Foreign.ForeignPtr@@ -29,11 +26,14 @@ -- loopTail     :: Addr#+    -> ForeignPtr Word8     -> Ptr Word8     -> Ptr Word8     -> Ptr Word8-    -> IO ()-loopTail !lut !end !dst !src+    -> Ptr Word8+    -> IO ByteString+loopTail !lut !dfp !dptr !end !dst !src+    | src == end = return (PS dfp 0 (minusPtr dst dptr))     | plusPtr src 1 == end = do -- 2 6       !a <- peek src @@ -44,6 +44,7 @@       poke (plusPtr dst 1) u       padN (plusPtr dst 2) 6 +      return (PS dfp 0 (8 + minusPtr dst dptr))     | plusPtr src 2 == end = do -- 4 4       !a <- peek src       !b <- peek (plusPtr src 1)@@ -59,6 +60,7 @@       poke (plusPtr dst 3) w       padN (plusPtr dst 4) 4 +      return (PS dfp 0 (8 + minusPtr dst dptr))     | plusPtr src 3 == end = do -- 5 3       !a <- peek src       !b <- peek (plusPtr src 1)@@ -76,8 +78,9 @@       poke (plusPtr dst 3) w       poke (plusPtr dst 4) x       padN (plusPtr dst 5) 3+      return (PS dfp 0 (8 + minusPtr dst dptr)) -    | plusPtr src 4 == end = do -- 7 1+    | otherwise = do -- 7 1       !a <- peek src       !b <- peek (plusPtr src 1)       !c <- peek (plusPtr src 2)@@ -99,8 +102,7 @@       poke (plusPtr dst 5) y       poke (plusPtr dst 6) z       padN (plusPtr dst 7) 1--    | otherwise = return ()+      return (PS dfp 0 (8 + minusPtr dst dptr))   where     look !n = aix n lut @@ -117,9 +119,10 @@     -> Ptr Word8     -> Ptr Word8     -> Ptr Word8-    -> Int+    -> Ptr Word8     -> IO ByteString-loopTailNoPad !lut !dfp !end !dst !src !n+loopTailNoPad !lut !dfp !dptr !end !dst !src+  | src == end = return (PS dfp 0 (minusPtr dst dptr))   | plusPtr src 1 == end = do -- 2 6       !a <- peek src @@ -129,7 +132,7 @@       poke dst t       poke (plusPtr dst 1) u -      return (PS dfp 0 (n + 2))+      return (PS dfp 0 (2 + minusPtr dst dptr))      | plusPtr src 2 == end = do -- 4 4       !a <- peek src@@ -145,7 +148,7 @@       poke (plusPtr dst 2) v       poke (plusPtr dst 3) w -      return (PS dfp 0 (n + 4))+      return (PS dfp 0 (4 + minusPtr dst dptr))      | plusPtr src 3 == end = do -- 5 3       !a <- peek src@@ -163,9 +166,9 @@       poke (plusPtr dst 2) v       poke (plusPtr dst 3) w       poke (plusPtr dst 4) x-      return (PS dfp 0 (n + 5))+      return (PS dfp 0 (5 + minusPtr dst dptr)) -    | plusPtr src 4 == end = do -- 7 1+    | otherwise = do -- 7 1       !a <- peek src       !b <- peek (plusPtr src 1)       !c <- peek (plusPtr src 2)@@ -186,96 +189,7 @@       poke (plusPtr dst 4) x       poke (plusPtr dst 5) y       poke (plusPtr dst 6) z-      return (PS dfp 0 (n + 7))--    | otherwise = return (PS dfp 0 n)+      return (PS dfp 0 (7 + minusPtr dst dptr))   where     look !i = aix i lut {-# INLINE loopTailNoPad #-}---- ------------------------------------------------------------------------ ----- Decoding loops--decodeTail-    :: Addr#-    -> ForeignPtr Word8-    -> Ptr Word8-    -> Ptr Word8-    -> Ptr Word8-    -> Int-    -> IO (Either Text ByteString)-decodeTail !lut !dfp !end !dptr !sptr !n = go dptr sptr-  where-    lix a = aix a lut-    {-# INLINE lix #-}--    ps !m = return (Right (PS dfp 0 m))-    {-# INLINE ps #-}--    err = return . Left . T.pack-    {-# INLINE err #-}--    decodeOctet (!a,!b,!c,!d,!e,!f,!g,!h) =-      case (lix a, lix b, lix c, lix d, lix e, lix f, lix g, lix h) of-        (0xff,_,_,_,_,_,_,_) -> Left (0 :: Int)-        (_,0xff,_,_,_,_,_,_) -> Left 1-        (_,_,0xff,_,_,_,_,_) -> Left 2-        (_,_,_,0xff,_,_,_,_) -> Left 3-        (_,_,_,_,0xff,_,_,_) -> Left 4-        (_,_,_,_,_,0xff,_,_) -> Left 5-        (_,_,_,_,_,_,0xff,_) -> Left 6-        (_,_,_,_,_,_,_,0xff) -> Left 7-        (ri1,ri2,ri3,ri4,ri5,ri6,ri7,ri8) ->-            let !o1 = (ri1 `unsafeShiftL` 3) .|. (ri2 `unsafeShiftR` 2)-                !o2 = (ri2 `unsafeShiftL` 6) .|. (ri3 `unsafeShiftL` 1) .|. (ri4 `unsafeShiftR` 4)-                !o3 = (ri4 `unsafeShiftL` 4) .|. (ri5 `unsafeShiftR` 1)-                !o4 = (ri5 `unsafeShiftL` 7) .|. (ri6 `unsafeShiftL` 2) .|. (ri7 `unsafeShiftR` 3)-                !o5 = (ri7 `unsafeShiftL` 5) .|. ri8-             in Right (o1, o2, o3, o4, o5)--    go !dst !src-      | src == end = ps n-      | otherwise = do-        !a <- peek @Word8 src-        !b <- peek @Word8 (plusPtr src 1)-        !c <- peek @Word8 (plusPtr src 2)-        !d <- peek @Word8 (plusPtr src 3)-        !e <- peek @Word8 (plusPtr src 4)-        !f <- peek @Word8 (plusPtr src 5)-        !g <- peek @Word8 (plusPtr src 6)-        !h <- peek @Word8 (plusPtr src 7)--        let (!m, !c', !d', !e', !f', !g', !h') = case (c,d,e,f,g,h) of-              (0x3d,0x3d,0x3d,0x3d,0x3d,0x3d) -> (6,0x41,0x41,0x41,0x41,0x41,0x41)-              (_,0x3d,0x3d,0x3d,0x3d,0x3d) -> (5,c,0x41,0x41,0x41,0x41,0x41)-              (_,_,0x3d,0x3d,0x3d,0x3d) -> (4,c,d,0x41,0x41,0x41,0x41)-              (_,_,_,0x3d,0x3d,0x3d) -> (3,c,d,e,0x41,0x41,0x41)-              (_,_,_,_,0x3d,0x3d) -> (2,c,d,e,f,0x41,0x41)-              (_,_,_,_,_,0x3d) -> (1,c,d,e,f,g,0x41)-              _ -> (0 :: Int,c,d,e,f,g,h)--        case decodeOctet (a,b,c',d',e',f',g',h') of-          Left ofs -> err $ "invalid character at offset: " ++ show (n + ofs)-          Right (!v,!w,!x,!y,!z) -> do-            poke dst v-            poke (plusPtr dst 1) w--            if-              | m == 0 -> do-                poke (plusPtr dst 2) x-                poke (plusPtr dst 3) y-                poke (plusPtr dst 4) z-                ps (n + 5)-              | m == 1 -> do-                poke (plusPtr dst 2) x-                poke (plusPtr dst 3) y-                poke (plusPtr dst 4) z-                ps (n + 4)-              | m < 4 -> do-                poke (plusPtr dst 2) x-                poke (plusPtr dst 3) y-                ps (n + 3)-              | m < 5 -> do-                poke (plusPtr dst 2) x-                ps (n + 2)-              | otherwise -> ps (n + 1)
src/Data/ByteString/Base32/Internal/Utils.hs view
@@ -2,16 +2,26 @@ {-# LANGUAGE MagicHash #-} module Data.ByteString.Base32.Internal.Utils ( aix+, padCeilN+, peekWord32BE+, peekWord64BE+, reChunkN , w32 , w64 , w64_32 , writeNPlainForeignPtrBytes ) where ++import Data.Bits+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+ import Foreign.Ptr import Foreign.ForeignPtr import Foreign.Storable +import GHC.ByteOrder import GHC.Exts import GHC.ForeignPtr import GHC.Word@@ -37,6 +47,14 @@ w64 = fromIntegral {-# INLINE w64 #-} +padCeilN :: Int -> Int -> Int+padCeilN !n !x+    | r == 0 = x+    | otherwise = (x - r) + n+  where+    r = x .&. (n - 1)+{-# INLINE padCeilN #-}+ -- | Allocate and fill @n@ bytes with some data -- writeNPlainForeignPtrBytes@@ -54,3 +72,38 @@     go !_ [] = return ()     go !p (x:xs) = poke p x >> go (plusPtr p 1) xs {-# INLINE writeNPlainForeignPtrBytes #-}++peekWord32BE :: Ptr Word32 -> IO Word32+peekWord32BE p = case targetByteOrder of+  LittleEndian -> byteSwap32 <$> peek p+  BigEndian    -> peek p+{-# inline peekWord32BE #-}++peekWord64BE :: Ptr Word64 -> IO Word64+peekWord64BE p = case targetByteOrder of+  LittleEndian -> byteSwap64 <$> peek p+  BigEndian    -> peek p+{-# inline peekWord64BE #-}++-- | Rechunk a list of bytestrings in multiples of @n@+--+reChunkN :: Int -> [ByteString] -> [ByteString]+reChunkN n = go+  where+    go [] = []+    go (b:bs) = case divMod (BS.length b) n of+      (_, 0) -> b : go bs+      (d, _) -> case BS.splitAt (d * n) b of+        ~(h, t) -> h : accum t bs++    accum acc [] = [acc]+    accum acc (c:cs) =+      case BS.splitAt (n - BS.length acc) c of+        ~(h, t) ->+          let acc' = BS.append acc h+          in if BS.length acc' == n+             then+               let cs' = if BS.null t then cs else t : cs+               in acc' : go cs'+             else accum acc' cs+{-# INLINE reChunkN #-}
+ src/Data/ByteString/Lazy/Base32.hs view
@@ -0,0 +1,244 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Trustworthy #-}+-- |+-- Module       : Data.ByteString.Lazy.Base32+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains 'Data.ByteString.Lazy.ByteString'-valued combinators for+-- implementing the RFC 4648 specification of the Base32+-- encoding format. This includes strictly padded/unpadded+-- decoding variants, as well as internal and external validation for canonicity.+--+module Data.ByteString.Lazy.Base32+( -- * Encoding+  encodeBase32+, encodeBase32'+, encodeBase32Unpadded+, encodeBase32Unpadded'+  -- * Decoding+, decodeBase32+, decodeBase32Unpadded+, decodeBase32Padded+--, decodeBase32Lenient+  -- * Validation+, isBase32+, isValidBase32+) where+++import Prelude hiding (all, elem)++import qualified Data.ByteString as BS+import qualified Data.ByteString.Base32 as B32+import Data.ByteString.Base32.Internal.Utils (reChunkN)+import Data.ByteString.Lazy (elem, fromChunks, toChunks)+import Data.ByteString.Lazy.Internal (ByteString(..))+import Data.Either (isRight)+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TL+++-- | Encode a 'ByteString' value as a Base32 'Text' value with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "KN2W4==="+--+encodeBase32 :: ByteString -> TL.Text+encodeBase32 = TL.decodeUtf8 . encodeBase32'+{-# INLINE encodeBase32 #-}++-- | Encode a 'ByteString' as a Base32 'ByteString' value with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "KN2W4==="+--+encodeBase32' :: ByteString -> ByteString+encodeBase32' = fromChunks+  . fmap B32.encodeBase32'+  . reChunkN 5+  . toChunks++-- | Decode an arbitrarily padded Base32 encoded 'ByteString' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as Base32-encoded values are optionally padded.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32 "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32 :: ByteString -> Either T.Text ByteString+decodeBase32 = fmap (fromChunks . (:[]))+  . B32.decodeBase32+  . BS.concat+  . toChunks+{-# INLINE decodeBase32 #-}++-- | Encode a 'ByteString' value as Base32 'Text' without padding. Note that for Base32,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32 and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "KN2W4"+--+encodeBase32Unpadded :: ByteString -> TL.Text+encodeBase32Unpadded = TL.decodeUtf8 . encodeBase32Unpadded'+{-# INLINE encodeBase32Unpadded #-}++-- | Encode a 'ByteString' value as Base32 without padding. Note that for Base32,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32 and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded' "Sun"+-- "KN2W4"+--+encodeBase32Unpadded' :: ByteString -> ByteString+encodeBase32Unpadded' = fromChunks+  . fmap B32.encodeBase32Unpadded'+  . reChunkN 5+  . toChunks++-- | Decode an unpadded Base32-encoded 'ByteString' value. Input strings are+-- required to be unpadded, and will undergo validation prior to decoding to+-- confirm.+--+-- In general, unless unpadded Base32 is explicitly required, it is+-- safer to call 'decodeBase32'.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "KN2W4==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32Unpadded :: ByteString -> Either T.Text ByteString+decodeBase32Unpadded = fmap (fromChunks . (:[]))+  . B32.decodeBase32Unpadded+  . BS.concat+  . toChunks+{-# INLINE decodeBase32Unpadded #-}++-- | Decode a padded Base32-encoded 'ByteString' value. Input strings are+-- required to be correctly padded, and will be validated prior to decoding+-- to confirm.+--+-- In general, unless padded Base32 is explicitly required, it is+-- safer to call 'decodeBase32'.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "KN2W4"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: ByteString -> Either T.Text ByteString+decodeBase32Padded = fmap (fromChunks . (:[]))+  . B32.decodeBase32Padded+  . BS.concat+  . toChunks+{-# INLINE decodeBase32Padded #-}++-- -- | Leniently decode an unpadded Base32-encoded 'ByteString'. This function+-- -- will not generate parse errors. If input data contains padding chars,+-- -- then the input will be parsed up until the first pad character.+-- --+-- -- __Note:__ This is not RFC 4648-compliant.+-- --+-- -- === __Examples__:+-- --+-- -- >>> decodeBase32Lenient "PDw_Pj4="+-- -- "<<?>>"+-- --+-- -- >>> decodeBase32Lenient "PDw_%%%$}Pj4"+-- -- "<<?>>"+-- --+-- decodeBase32Lenient :: ByteString -> ByteString+-- decodeBase32Lenient = fromChunks+--     . fmap B32.decodeBase32Lenient+--     . reChunkN 8+--     . fmap (BS.filter (flip elem "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="))+--     . toChunks+-- {-# INLINE decodeBase32Lenient #-}++-- | Tell whether a 'ByteString' is Base32-encoded.+--+-- === __Examples__:+--+-- >>> isBase32 "KN2W4"+-- True+--+-- >>> isBase32 "KN2W4==="+-- True+--+-- >>> isBase32 "KN2W4=="+-- False+--+isBase32 :: ByteString -> Bool+isBase32 bs = isValidBase32 bs && isRight (decodeBase32 bs)+{-# INLINE isBase32 #-}++-- | Tell whether a 'ByteString' is a valid Base32 format.+--+-- This will not tell you whether or not this is a correct Base32 representation,+-- only that it conforms to the correct shape. To check whether it is a true+-- Base32 encoded 'ByteString' value, use 'isBase32'.+--+-- === __Examples__:+--+-- >>> isValidBase32 "KN2W4"+-- True+--+-- >>> isValidBase32 "KN2W4="+-- False+--+-- >>> isValidBase32 "KN2W4%"+-- False+--+isValidBase32 :: ByteString -> Bool+isValidBase32 = go . toChunks+  where+    go [] = True+    go [c] = B32.isValidBase32 c+    go (c:cs) = -- note the lack of padding char+      BS.all (flip elem "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567") c+      && go cs+{-# INLINE isValidBase32 #-}
+ src/Data/ByteString/Lazy/Base32/Hex.hs view
@@ -0,0 +1,245 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Trustworthy #-}+-- |+-- Module       : Data.ByteString.Lazy.Base32.Hex+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains 'Data.ByteString.Lazy.ByteString'-valued combinators for+-- implementing the RFC 4648 specification of the Base32hex+-- encoding format. This includes strictly padded/unpadded+-- decoding variants, as well as internal and external validation for canonicity.+--+module Data.ByteString.Lazy.Base32.Hex+( -- * Encoding+  encodeBase32+, encodeBase32'+, encodeBase32Unpadded+, encodeBase32Unpadded'+  -- * Decoding+, decodeBase32+, decodeBase32Unpadded+, decodeBase32Padded+--, decodeBase32Lenient+  -- * Validation+, isBase32Hex+, isValidBase32Hex+) where+++import Prelude hiding (all, elem)++import qualified Data.ByteString as BS+import qualified Data.ByteString.Base32.Hex as B32H+import Data.ByteString.Base32.Internal.Utils (reChunkN)+import Data.ByteString.Lazy (elem, fromChunks, toChunks)+import Data.ByteString.Lazy.Internal (ByteString(..))+import Data.Either (isRight)+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TL+++-- | Encode a 'ByteString' value as a Base32hex 'Text' value with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "ADQMS==="+--+encodeBase32 :: ByteString -> TL.Text+encodeBase32 = TL.decodeUtf8 . encodeBase32'+{-# INLINE encodeBase32 #-}++-- | Encode a 'ByteString' as a Base32hex 'ByteString' value with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32' "Sun"+-- "ADQMS==="+--+encodeBase32' :: ByteString -> ByteString+encodeBase32' = fromChunks+    . fmap B32H.encodeBase32'+    . reChunkN 5+    . toChunks++-- | Decode an arbitrarily padded Base32hex encoded 'ByteString' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as Base32hex-encoded values are optionally padded.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32 "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32 :: ByteString -> Either T.Text ByteString+decodeBase32 = fmap (fromChunks . (:[]))+    . B32H.decodeBase32+    . BS.concat+    . toChunks+{-# INLINE decodeBase32 #-}++-- | Encode a 'ByteString' value as Base32hex 'Text' without padding. Note that for Base32hex,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32hex and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "ADQMS"+--+encodeBase32Unpadded :: ByteString -> TL.Text+encodeBase32Unpadded = TL.decodeUtf8 . encodeBase32Unpadded'+{-# INLINE encodeBase32Unpadded #-}++-- | Encode a 'ByteString' value as Base32hex without padding. Note that for Base32hex,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32hex and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded' "Sun"+-- "ADQMS"+--+encodeBase32Unpadded' :: ByteString -> ByteString+encodeBase32Unpadded' = fromChunks+    . fmap B32H.encodeBase32Unpadded'+    . reChunkN 5+    . toChunks++-- | Decode an unpadded Base32hex-encoded 'ByteString' value. Input strings are+-- required to be unpadded, and will undergo validation prior to decoding to+-- confirm.+--+-- In general, unless unpadded Base32hex is explicitly required, it is+-- safer to call 'decodeBase32'.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32Unpadded :: ByteString -> Either T.Text ByteString+decodeBase32Unpadded = fmap (fromChunks . (:[]))+    . B32H.decodeBase32Unpadded+    . BS.concat+    . toChunks+{-# INLINE decodeBase32Unpadded #-}++-- | Decode a padded Base32hex-encoded 'ByteString' value. Input strings are+-- required to be correctly padded, and will be validated prior to decoding+-- to confirm.+--+-- In general, unless padded Base32hex is explicitly required, it is+-- safer to call 'decodeBase32'.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "ADQMS"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: ByteString -> Either T.Text ByteString+decodeBase32Padded = fmap (fromChunks . (:[]))+    . B32H.decodeBase32Padded+    . BS.concat+    . toChunks+{-# INLINE decodeBase32Padded #-}++-- -- | Leniently decode an unpadded Base32hex-encoded 'ByteString'. This function+-- -- will not generate parse errors. If input data contains padding chars,+-- -- then the input will be parsed up until the first pad character.+-- --+-- -- __Note:__ This is not RFC 4648-compliant.+-- --+-- -- === __Examples__:+-- --+-- -- >>> decodeBase32Lenient "PDw_Pj4="+-- -- "<<?>>"+-- --+-- -- >>> decodeBase32Lenient "PDw_%%%$}Pj4"+-- -- "<<?>>"+-- --+-- decodeBase32Lenient :: ByteString -> ByteString+-- decodeBase32Lenient = fromChunks+--     . fmap B32H.decodeBase32Lenient+--     . reChunkN 8+--     . fmap (BS.filter (flip elem "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="))+--     . toChunks+-- {-# INLINE decodeBase32Lenient #-}++-- | Tell whether a 'ByteString' is Base32hex-encoded.+--+-- === __Examples__:+--+-- >>> isBase32Hex "ADQMS"+-- True+--+-- >>> isBase32Hex "ADQMS==="+-- True+--+-- >>> isBase32Hex "ADQMS=="+-- False+--+isBase32Hex :: ByteString -> Bool+isBase32Hex bs = isValidBase32Hex bs && isRight (decodeBase32 bs)+{-# INLINE isBase32Hex #-}++-- | Tell whether a 'ByteString' is a valid Base32hex format.+--+-- This will not tell you whether or not this is a correct Base32hex representation,+-- only that it conforms to the correct shape. To check whether it is a true+-- Base32hex encoded 'ByteString' value, use 'isBase32Hex'.+--+-- === __Examples__:+--+--+-- >>> isValidBase32Hex "ADQMS"+-- True+--+-- >>> isValidBase32Hex "ADQMS="+-- False+--+-- >>> isValidBase32Hex "ADQMS%"+-- False+--+isValidBase32Hex :: ByteString -> Bool+isValidBase32Hex = go . toChunks+  where+    go [] = True+    go [c] = B32H.isValidBase32Hex c+    go (c:cs) = -- note the lack of padding char+      BS.all (flip elem "0123456789ABCDEFGHIJKLMNOPQRSTUV") c+      && go cs+{-# INLINE isValidBase32Hex #-}
+ src/Data/ByteString/Short/Base32.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE Trustworthy #-}+-- |+-- Module       : Data.ByteString.Short.Base32+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains 'Data.ByteString.Short.ShortByteString'-valued combinators for+-- implementing the RFC 4648 specification of the Base32+-- encoding format. This includes strictly padded/unpadded decoding+-- variants, as well as internal and external validation for canonicity.+--+module Data.ByteString.Short.Base32+( -- * Encoding+  encodeBase32+, encodeBase32'+, encodeBase32Unpadded+, encodeBase32Unpadded'+  -- * Decoding+, decodeBase32+, decodeBase32Unpadded+, decodeBase32Padded+--, decodeBase32Lenient+  -- * Validation+, isBase32+, isValidBase32+) where+++import qualified Data.ByteString.Base32 as B32+import Data.ByteString.Short (ShortByteString, fromShort, toShort)+import Data.Text (Text)+import Data.Text.Short (ShortText)+import Data.Text.Short.Unsafe (fromShortByteStringUnsafe)+++-- | Encode a 'ShortByteString' value as a Base32 'Text' value with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "KN2W4==="+--+encodeBase32 :: ShortByteString -> ShortText+encodeBase32 = fromShortByteStringUnsafe . encodeBase32'+{-# INLINE encodeBase32 #-}++-- | Encode a 'ShortByteString' as a Base32 'ShortByteString' value with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32' "Sun"+-- "KN2W4==="+--+encodeBase32' :: ShortByteString -> ShortByteString+encodeBase32' = toShort . B32.encodeBase32' . fromShort++-- | Decode an arbitrarily padded Base32 encoded 'ShortByteString' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as Base32-encoded values are optionally padded.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32 "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32 :: ShortByteString -> Either Text ShortByteString+decodeBase32 = fmap toShort . B32.decodeBase32 . fromShort++{-# INLINE decodeBase32 #-}++-- | Encode a 'ShortByteString' value as Base32 'Text' without padding. Note that for Base32,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32 and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "KN2W4"+--+encodeBase32Unpadded :: ShortByteString -> ShortText+encodeBase32Unpadded = fromShortByteStringUnsafe . encodeBase32Unpadded'+{-# INLINE encodeBase32Unpadded #-}++-- | Encode a 'ShortByteString' value as Base32 without padding. Note that for Base32,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32 and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded' "Sun"+-- "KN2W4"+--+encodeBase32Unpadded' :: ShortByteString -> ShortByteString+encodeBase32Unpadded' = toShort . B32.encodeBase32Unpadded' . fromShort++-- | Decode an unpadded Base32-encoded 'ShortByteString' value. Input strings are+-- required to be unpadded, and will undergo validation prior to decoding to+-- confirm.+--+-- In general, unless unpadded Base32 is explicitly required, it is+-- safer to call 'decodeBase32'.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "KN2W4==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32Unpadded :: ShortByteString -> Either Text ShortByteString+decodeBase32Unpadded = fmap toShort . B32.decodeBase32Unpadded . fromShort+{-# INLINE decodeBase32Unpadded #-}++-- | Decode a padded Base32-encoded 'ShortByteString' value. Input strings are+-- required to be correctly padded, and will be validated prior to decoding+-- to confirm.+--+-- In general, unless padded Base32 is explicitly required, it is+-- safer to call 'decodeBase32'.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "KN2W4"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: ShortByteString -> Either Text ShortByteString+decodeBase32Padded = fmap toShort . B32.decodeBase32Padded . fromShort+{-# INLINE decodeBase32Padded #-}++-- -- | Leniently decode an unpadded Base32-encoded 'ShortByteString'. This function+-- -- will not generate parse errors. If input data contains padding chars,+-- -- then the input will be parsed up until the first pad character.+-- --+-- -- __Note:__ This is not RFC 4648-compliant.+-- --+-- -- === __Examples__:+-- --+-- -- >>> decodeBase32Lenient "PDw_Pj4="+-- -- "<<?>>"+-- --+-- -- >>> decodeBase32Lenient "PDw_%%%$}Pj4"+-- -- "<<?>>"+-- --+-- decodeBase32Lenient :: ShortByteString -> ShortByteString+-- decodeBase32Lenient = toShort . B32.decodeBase32Lenient . fromShort+-- {-# INLINE decodeBase32Lenient #-}++-- | Tell whether a 'ShortByteString' is Base32-encoded.+--+-- === __Examples__:+--+-- >>> isBase32 "KN2W4"+-- True+--+-- >>> isBase32 "KN2W4==="+-- True+--+-- >>> isBase32 "KN2W4=="+-- False+--+isBase32 :: ShortByteString -> Bool+isBase32 = B32.isBase32 . fromShort+{-# INLINE isBase32 #-}++-- | Tell whether a 'ShortByteString' is a valid Base32 format.+--+-- This will not tell you whether or not this is a correct Base32 representation,+-- only that it conforms to the correct shape. To check whether it is a true+-- Base32 encoded 'ShortByteString' value, use 'isBase32'.+--+-- === __Examples__:+--+-- >>> isValidBase32 "KN2W4"+-- True+--+-- >>> isValidBase32 "KN2W4="+-- False+--+-- >>> isValidBase32 "KN2W4%"+-- False+--+isValidBase32 :: ShortByteString -> Bool+isValidBase32 = B32.isValidBase32 . fromShort+{-# INLINE isValidBase32 #-}
+ src/Data/ByteString/Short/Base32/Hex.hs view
@@ -0,0 +1,210 @@+{-# LANGUAGE Trustworthy #-}+-- |+-- Module       : Data.ByteString.Short.Base32.Hex+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains 'Data.ByteString.Short.ShortByteString'-valued combinators for+-- implementing the RFC 4648 specification of the Base32hex+-- encoding format. This includes strictly padded/unpadded and decoding+-- variants, as well as internal and external validation for canonicity.+--+module Data.ByteString.Short.Base32.Hex+( -- * Encoding+  encodeBase32+, encodeBase32'+, encodeBase32Unpadded+, encodeBase32Unpadded'+  -- * Decoding+, decodeBase32+, decodeBase32Unpadded+, decodeBase32Padded+--, decodeBase32Lenient+  -- * Validation+, isBase32Hex+, isValidBase32Hex+) where+++import qualified Data.ByteString.Base32.Hex as B32H+import Data.ByteString.Short (ShortByteString, fromShort, toShort)+import Data.Text (Text)+import Data.Text.Short (ShortText)+import Data.Text.Short.Unsafe (fromShortByteStringUnsafe)++-- | Encode a 'ShortByteString' value as a Base32hex 'Text' value with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "ADQMS==="+--+encodeBase32 :: ShortByteString -> ShortText+encodeBase32 = fromShortByteStringUnsafe . encodeBase32'+{-# INLINE encodeBase32 #-}++-- | Encode a 'ShortByteString' as a Base32hex 'ShortByteString' value with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32' "Sun"+-- "ADQMS==="+--+encodeBase32' :: ShortByteString -> ShortByteString+encodeBase32' = toShort . B32H.encodeBase32' . fromShort++-- | Decode an arbitrarily padded Base32hex encoded 'ShortByteString' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as Base32hex-encoded values are optionally padded.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32 "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32 "ADQM==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32 :: ShortByteString -> Either Text ShortByteString+decodeBase32 = fmap toShort . B32H.decodeBase32 . fromShort+{-# INLINE decodeBase32 #-}++-- | Encode a 'ShortByteString' value as Base32hex 'Text' without padding. Note that for Base32hex,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32hex and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded' "Sun"+-- "ADQMS"+--+encodeBase32Unpadded :: ShortByteString -> ShortText+encodeBase32Unpadded = fromShortByteStringUnsafe . encodeBase32Unpadded'+{-# INLINE encodeBase32Unpadded #-}++-- | Encode a 'ShortByteString' value as Base32hex without padding. Note that for Base32hex,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32hex and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded' "Sun"+-- "ADQMS"+--+encodeBase32Unpadded' :: ShortByteString -> ShortByteString+encodeBase32Unpadded' = toShort . B32H.encodeBase32Unpadded' . fromShort++-- | Decode an unpadded Base32hex-encoded 'ShortByteString' value. Input strings are+-- required to be unpadded, and will undergo validation prior to decoding to+-- confirm.+--+-- In general, unless unpadded Base32hex is explicitly required, it is+-- safer to call 'decodeBase32'.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32Unpadded :: ShortByteString -> Either Text ShortByteString+decodeBase32Unpadded = fmap toShort . B32H.decodeBase32Unpadded . fromShort+{-# INLINE decodeBase32Unpadded #-}++-- | Decode a padded Base32hex-encoded 'ShortByteString' value. Input strings are+-- required to be correctly padded, and will be validated prior to decoding+-- to confirm.+--+-- In general, unless padded Base32hex is explicitly required, it is+-- safer to call 'decodeBase32'.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "ADQMS"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: ShortByteString -> Either Text ShortByteString+decodeBase32Padded = fmap toShort . B32H.decodeBase32Padded . fromShort+{-# INLINE decodeBase32Padded #-}++-- -- | Leniently decode an unpadded Base32hex-encoded 'ShortByteString'. This function+-- -- will not generate parse errors. If input data contains padding chars,+-- -- then the input will be parsed up until the first pad character.+-- --+-- -- __Note:__ This is not RFC 4648-compliant.+-- --+-- -- === __Examples__:+-- --+-- -- >>> decodeBase32Lenient "PDw_Pj4="+-- -- "<<?>>"+-- --+-- -- >>> decodeBase32Lenient "PDw_%%%$}Pj4"+-- -- "<<?>>"+-- --+-- decodeBase32Lenient :: ShortByteString -> ShortByteString+-- decodeBase32Lenient = toShort . B32H.decodeBase32Lenient . fromShort+-- {-# INLINE decodeBase32Lenient #-}++-- | Tell whether a 'ShortByteString' is Base32hex-encoded.+--+-- === __Examples__:+--+-- >>> isBase32Hex "ADQMS"+-- True+--+-- >>> isBase32Hex "ADQMS==="+-- True+--+-- >>> isBase32Hex "ADQMS=="+-- False+--+isBase32Hex :: ShortByteString -> Bool+isBase32Hex = B32H.isBase32Hex . fromShort+{-# INLINE isBase32Hex #-}++-- | Tell whether a 'ShortByteString' is a valid Base32hex format.+--+-- This will not tell you whether or not this is a correct Base32hex representation,+-- only that it conforms to the correct shape. To check whether it is a true+-- Base32 encoded 'ShortByteString' value, use 'isBase32Hex'.+--+-- === __Examples__:+--+-- >>> isValidBase32Hex "ADQMS"+-- True+--+-- >>> isValidBase32Hex "ADQMS="+-- False+--+-- >>> isValidBase32Hex "ADQMS%"+-- False+--+isValidBase32Hex :: ShortByteString -> Bool+isValidBase32Hex = B32H.isValidBase32Hex . fromShort+{-# INLINE isValidBase32Hex #-}
src/Data/Text/Encoding/Base32.hs view
@@ -1,69 +1,217 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Safe #-} -- | -- Module       : Data.Text.Encoding.Base32--- Copyright 	: (c) 2019 Emily Pillmore--- License	: BSD-style+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style ----- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>--- Stability	: Experimental--- Portability	: portable+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable ----- This module contains the combinators implementing the--- RFC 4648 specification for the Base32 encoding including--- unpadded and lenient variants+-- This module contains 'Data.Text.Text'-valued combinators for+-- implementing the RFC 4648 specification of the Base32+-- encoding format. This includes strictly padded/unpadded decoding variants, as well as+-- internal and external validation for canonicity. -- module Data.Text.Encoding.Base32 ( encodeBase32 , decodeBase32+, decodeBase32With , encodeBase32Unpadded , decodeBase32Unpadded+, decodeBase32UnpaddedWith+, decodeBase32Padded+, decodeBase32PaddedWith -- , decodeBase32Lenient , isBase32 , isValidBase32 ) where  +import Data.Bifunctor (first)+import Data.ByteString (ByteString) import qualified Data.ByteString.Base32 as B32  import Data.Text (Text) import qualified Data.Text.Encoding as T+import Data.Text.Encoding.Base32.Error (Base32Error(..))  -- | Encode a 'Text' value in Base32 with padding. -- -- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "KN2W4==="+-- encodeBase32 :: Text -> Text encodeBase32 = B32.encodeBase32 . T.encodeUtf8 {-# INLINE encodeBase32 #-} --- | Decode a padded Base32-encoded 'Text' value+-- | Decode an arbitrarily padded Base32-encoded 'Text' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as base32 encodings are optionally padded. --+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'T.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32With`+-- and pass in a custom decode function.+-- -- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> decodeBase32 "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W==="+-- Left "Base32-encoded bytestring has invalid padding"+-- decodeBase32 :: Text -> Either Text Text-decodeBase32 = fmap T.decodeUtf8 . B32.decodeBase32 . T.encodeUtf8+decodeBase32 = fmap T.decodeLatin1+    . B32.decodeBase32+    . T.encodeUtf8 {-# INLINE decodeBase32 #-} +-- | Attempt to decode a 'Text' value as Base32, converting from+-- 'ByteString' to 'Text' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Example__:+--+-- @+-- 'decodeBase32With' 'T.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'Text'+-- @+--+decodeBase32With+    :: (ByteString -> Either err Text)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) Text+decodeBase32With f t = case B32.decodeBase32 t of+    Left de -> Left $ DecodeError de+    Right a -> first ConversionError (f a)+{-# INLINE decodeBase32With #-}+ -- | Encode a 'Text' value in Base32 without padding. ----- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "KN2W4"+-- encodeBase32Unpadded :: Text -> Text encodeBase32Unpadded = B32.encodeBase32Unpadded . T.encodeUtf8 {-# INLINE encodeBase32Unpadded #-} --- | Decode an arbitrarily padded Base32-encoded 'Text'+-- | Decode an unpadded Base32 encoded 'Text' value. ----- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'T.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32WUnpaddedWith`+-- and pass in a custom decode function. --+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "KN2W4==="+-- Left "Base32-encoded bytestring has invalid padding"+-- decodeBase32Unpadded :: Text -> Either Text Text-decodeBase32Unpadded = fmap T.decodeUtf8+decodeBase32Unpadded = fmap T.decodeLatin1     . B32.decodeBase32Unpadded     . T.encodeUtf8 {-# INLINE decodeBase32Unpadded #-} +-- | Attempt to decode an unpadded 'ByteString' value as Base32, converting from+-- 'ByteString' to 'Text' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Example__:+--+-- @+-- 'decodeBase32UnpaddedWith' 'T.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'Text'+-- @+--+decodeBase32UnpaddedWith+    :: (ByteString -> Either err Text)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) Text+decodeBase32UnpaddedWith f t = case B32.decodeBase32Unpadded t of+    Left de -> Left $ DecodeError de+    Right a -> first ConversionError (f a)+{-# INLINE decodeBase32UnpaddedWith #-}+++-- | Decode an padded Base32 encoded 'Text' value+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'T.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32PaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "KN2W4"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: Text -> Either Text Text+decodeBase32Padded = fmap T.decodeLatin1+    . B32.decodeBase32Padded+    . T.encodeUtf8+{-# INLINE decodeBase32Padded #-}++-- | Attempt to decode a padded 'ByteString' value as Base32, converting from+-- 'ByteString' to 'Text' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Example__:+--+-- @+-- 'decodeBase32PaddedWith' 'T.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'Text'+-- @+--+decodeBase32PaddedWith+    :: (ByteString -> Either err Text)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) Text+decodeBase32PaddedWith f t = case B32.decodeBase32Padded t of+    Left de -> Left $ DecodeError de+    Right a -> first ConversionError (f a)+{-# INLINE decodeBase32PaddedWith #-}+ -- -- | Leniently decode a Base32-encoded 'Text' value. This function -- -- will not generate parse errors. If input data contains padding chars, -- -- then the input will be parsed up until the first pad character.@@ -76,8 +224,19 @@ --     . T.encodeUtf8 -- {-# INLINE decodeBase32Lenient #-} --- | Tell whether a 'Text' value is Base32-encoded.+-- | Tell whether a 'Text' value is Base32-encoded --+-- === __Examples__:+--+-- >>> isBase32 "KN2W4"+-- True+--+-- >>> isBase32 "KN2W4==="+-- True+--+-- >>> isBase32 "KN2W4=="+-- False+-- isBase32 :: Text -> Bool isBase32 = B32.isBase32 . T.encodeUtf8 {-# INLINE isBase32 #-}@@ -87,6 +246,17 @@ -- This will not tell you whether or not this is a correct Base32 representation, -- only that it conforms to the correct shape. To check whether it is a true -- Base32 encoded 'Text' value, use 'isBase32'.+--+-- === __Examples__:+--+-- >>> isValidBase32 "KN2W4"+-- True+--+-- >>> isValidBase32 "KN2W4="+-- False+--+-- >>> isValidBase32 "KN2W4%"+-- False -- isValidBase32 :: Text -> Bool isValidBase32 = B32.isValidBase32 . T.encodeUtf8
+ src/Data/Text/Encoding/Base32/Error.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE Safe #-}+-- |+-- Module       : Data.Text.Encoding.Base32.Error+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains the error types raised (not as exceptions!)+-- in the decoding process.+--+module Data.Text.Encoding.Base32.Error+( Base32Error(..)+) where+++import Control.DeepSeq (NFData(..))+import Control.Exception (Exception(..))++import Data.Text (Text)++import GHC.Generics++-- | This data type represents the type of decoding errors of+-- various kinds as they pertain to decoding 'Text' values.+-- Namely, to distinguish between decoding errors from opaque+-- unicode exceptions caught in the unicode decoding process.+--+data Base32Error e+  = DecodeError Text+    -- ^ The error associated with decoding failure+    -- as a result of the Base32 decoding process+  | ConversionError e+    -- ^ The error associated with the decoding failure+    -- as a result of the conversion process+  deriving+    ( Eq, Show+    , Generic+      -- ^ @since 0.2.0.0+    )++-- |+--+-- @since 0.2.0.0+--+instance Exception e => Exception (Base32Error e)+++-- |+--+-- @since 0.2.0.0+--+instance NFData e => NFData (Base32Error e)
src/Data/Text/Encoding/Base32/Hex.hs view
@@ -1,68 +1,220 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Safe #-} -- | -- Module       : Data.Text.Encoding.Base32.Hex--- Copyright 	: (c) 2019 Emily Pillmore--- License	: BSD-style+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style ----- Maintainer	: Emily Pillmore <emilypi@cohomolo.gy>--- Stability	: Experimental--- Portability	: portable+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable ----- This module contains the combinators implementing the--- RFC 4648 specification for the Base32-Hex encoding including--- unpadded and lenient variants+-- This module contains 'Data.Text.Text'-valued combinators for+-- implementing the RFC 4648 specification of the Base32hex+-- encoding format. This includes strictly padded and unpadded+-- decoding variants, as well as internal and external validation+-- for canonicity.+-- module Data.Text.Encoding.Base32.Hex ( encodeBase32 , decodeBase32+, decodeBase32With , encodeBase32Unpadded , decodeBase32Unpadded+, decodeBase32UnpaddedWith+, decodeBase32Padded+, decodeBase32PaddedWith -- , decodeBase32Lenient , isBase32Hex , isValidBase32Hex ) where  -import qualified Data.ByteString.Base32.Hex as B32U+import Data.Bifunctor (first)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Base32.Hex as B32H  import Data.Text (Text) import qualified Data.Text.Encoding as T+import Data.Text.Encoding.Base32.Error (Base32Error(..))  -- | Encode a 'Text' value in Base32hex with padding. ----- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "ADQMS==="+-- encodeBase32 :: Text -> Text-encodeBase32 = B32U.encodeBase32 . T.encodeUtf8+encodeBase32 = B32H.encodeBase32 . T.encodeUtf8 {-# INLINE encodeBase32 #-} --- | Decode a padded Base32hex-encoded 'Text' value.+-- | Decode a padded Base32hex-encoded 'Text' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as base32hex encodings are optionally padded. ----- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'T.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32With`+-- and pass in a custom decode function. --+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32 "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+-- decodeBase32 :: Text -> Either Text Text-decodeBase32 = fmap T.decodeUtf8 . B32U.decodeBase32 . T.encodeUtf8+decodeBase32 = fmap T.decodeLatin1 . B32H.decodeBase32 . T.encodeUtf8 {-# INLINE decodeBase32 #-} --- | Encode a 'Text' value in Base32hex without padding.+-- | Attempt to decode a 'ByteString' value as Base32hex, converting from+-- 'ByteString' to 'Text' according to some encoding function. In practice,+-- This is something like 'T.decodeUtf8'', which may produce an error. ----- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6> --+-- === __Examples__:+--+-- @+-- 'decodeBase32With' 'T.decodeUtf8''+--   :: 'Text' -> 'Either' ('Base32Error' 'UnicodeException') 'Text'+-- @+--+decodeBase32With+    :: (ByteString -> Either err Text)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) Text+decodeBase32With f t = case B32H.decodeBase32 t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32With #-}++-- | Encode a 'Text' value in Base32hex without padding. Note that for Base32hex,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32hex and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "ADQMS"+-- encodeBase32Unpadded :: Text -> Text-encodeBase32Unpadded = B32U.encodeBase32Unpadded . T.encodeUtf8+encodeBase32Unpadded = B32H.encodeBase32Unpadded . T.encodeUtf8 {-# INLINE encodeBase32Unpadded #-} --- | Decode an arbitrarily padded Base32hex encoded 'Text' value++-- | Decode an unpadded Base32hex encoded 'Text' value. ----- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>,---      <https://tools.ietf.org/html/rfc4648#section-3.2 RFC-4648 section 3.2>+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'T.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32WUnpaddedWith`+-- and pass in a custom decode function. --+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+-- decodeBase32Unpadded :: Text -> Either Text Text-decodeBase32Unpadded = fmap T.decodeUtf8-    . B32U.decodeBase32Unpadded+decodeBase32Unpadded = fmap T.decodeLatin1+    . B32H.decodeBase32Unpadded     . T.encodeUtf8 {-# INLINE decodeBase32Unpadded #-} ++-- | Attempt to decode an unpadded 'ByteString' value as Base32hex, converting from+-- 'ByteString' to 'Text' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Example__:+--+-- @+-- 'decodeBase32UnpaddedWith' 'T.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'Text'+-- @+--+decodeBase32UnpaddedWith+    :: (ByteString -> Either err Text)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) Text+decodeBase32UnpaddedWith f t = case B32H.decodeBase32Unpadded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32UnpaddedWith #-}+++-- | Decode an padded Base32hex encoded 'Text' value+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'T.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32PaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "ADQMS"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: Text -> Either Text Text+decodeBase32Padded = fmap T.decodeLatin1+    . B32H.decodeBase32Padded+    . T.encodeUtf8+{-# INLINE decodeBase32Padded #-}++-- | Attempt to decode a padded 'ByteString' value as Base32hex, converting from+-- 'ByteString' to 'Text' according to some encoding function. In practice,+-- This is something like 'T.decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Example__:+--+-- @+-- 'decodeBase32PaddedWith' 'T.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'Text'+-- @+--+decodeBase32PaddedWith+    :: (ByteString -> Either err Text)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) Text+decodeBase32PaddedWith f t = case B32H.decodeBase32Padded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32PaddedWith #-}+ -- -- -- | Leniently decode an unpadded Base32hex-encoded 'Text'. This function -- -- -- will not generate parse errors. If input data contains padding chars, -- -- -- then the input will be parsed up until the first pad character.@@ -71,14 +223,25 @@ -- -- -- -- -- decodeBase32Lenient :: Text -> Text -- -- decodeBase32Lenient = T.decodeUtf8--- --     . B32U.decodeBase32Lenient+-- --     . B32H.decodeBase32Lenient -- --     . T.encodeUtf8 -- -- {-# INLINE decodeBase32Lenient #-}  -- | Tell whether a 'Text' value is Base32hex-encoded. --+-- === __Examples__:+--+-- >>> isBase32Hex "ADQMS"+-- True+--+-- >>> isBase32Hex "ADQMS==="+-- True+--+-- >>> isBase32Hex "ADQMS=="+-- False+-- isBase32Hex :: Text -> Bool-isBase32Hex = B32U.isBase32Hex . T.encodeUtf8+isBase32Hex = B32H.isBase32Hex . T.encodeUtf8 {-# INLINE isBase32Hex #-}  -- | Tell whether a 'Text' value is a valid Base32hex format.@@ -87,6 +250,17 @@ -- only that it conforms to the correct shape. To check whether it is a true -- Base32 encoded 'Text' value, use 'isBase32Hex'. --+-- === __Examples__:+--+-- >>> isValidBase32Hex "ADQMS"+-- True+--+-- >>> isValidBase32Hex "ADQMS="+-- False+--+-- >>> isValidBase32Hex "ADQMS%"+-- False+-- isValidBase32Hex :: Text -> Bool-isValidBase32Hex = B32U.isValidBase32Hex . T.encodeUtf8+isValidBase32Hex = B32H.isValidBase32Hex . T.encodeUtf8 {-# INLINE isValidBase32Hex #-}
+ src/Data/Text/Lazy/Encoding/Base32.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE Safe #-}+-- |+-- Module       : Data.Text.Lazy.Encoding.Base32+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains 'Data.Text.Lazy.Text'-valued combinators for+-- implementing the RFC 4648 specification of the Base32+-- encoding format. This includes strictly padded/unpadded decoding+-- variants, as well as internal and external validation for canonicity.+--+module Data.Text.Lazy.Encoding.Base32+( -- * Encoding+  encodeBase32+, encodeBase32Unpadded+  -- * Decoding+, decodeBase32+, decodeBase32With+, decodeBase32Unpadded+, decodeBase32UnpaddedWith+, decodeBase32Padded+, decodeBase32PaddedWith+--, decodeBase32Lenient+  -- * Validation+, isBase32+, isValidBase32+) where+++import Data.Bifunctor (first)+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy.Base32 as BL32++import qualified Data.Text as T+import Data.Text.Encoding.Base32.Error+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TL++-- | Encode a 'TL.Text' value in Base32 with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "KN2W4==="+--+encodeBase32 :: TL.Text -> TL.Text+encodeBase32 = BL32.encodeBase32 . TL.encodeUtf8+{-# INLINE encodeBase32 #-}++-- | Decode an arbitrarily padded Base32-encoded 'TL.Text' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as base32 encodings are optionally padded.+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'TL.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32With`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32 "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32 :: TL.Text -> Either T.Text TL.Text+decodeBase32 = fmap TL.decodeLatin1 . BL32.decodeBase32 . TL.encodeUtf8+{-# INLINE decodeBase32 #-}++-- | Attempt to decode a lazy 'ByteString' value as Base32, converting from+-- 'ByteString' to 'TL.Text' according to some encoding function. In practice,+-- This is something like 'TL.decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32With' 'TL.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'TL.Text'+-- @+--+decodeBase32With+    :: (ByteString -> Either err TL.Text)+      -- ^ convert a bytestring to text (e.g. 'TL.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) TL.Text+decodeBase32With f t = case BL32.decodeBase32 t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32With #-}++-- | Encode a 'TL.Text' value in Base32 without padding. Note that for Base32,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32 and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "KN2W4"+--+encodeBase32Unpadded :: TL.Text -> TL.Text+encodeBase32Unpadded = BL32.encodeBase32Unpadded . TL.encodeUtf8+{-# INLINE encodeBase32Unpadded #-}++-- | Decode an unpadded Base32 encoded 'TL.Text' value.+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'TL.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32WUnpaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "KN2W4==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32Unpadded :: TL.Text -> Either T.Text TL.Text+decodeBase32Unpadded = fmap TL.decodeLatin1+    . BL32.decodeBase32Unpadded+    . TL.encodeUtf8+{-# INLINE decodeBase32Unpadded #-}++-- | Attempt to decode an unpadded lazy 'ByteString' value as Base32, converting from+-- 'ByteString' to 'TL.Text' according to some encoding function. In practice,+-- This is something like 'TL.decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32UnpaddedWith' 'TL.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'TL.Text'+-- @+--+decodeBase32UnpaddedWith+    :: (ByteString -> Either err TL.Text)+      -- ^ convert a bytestring to text (e.g. 'TL.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) TL.Text+decodeBase32UnpaddedWith f t = case BL32.decodeBase32Unpadded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32UnpaddedWith #-}++-- | Decode an padded Base32 encoded 'TL.Text' value+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'TL.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32PaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "KN2W4"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: TL.Text -> Either T.Text TL.Text+decodeBase32Padded = fmap TL.decodeLatin1+    . BL32.decodeBase32Padded+    . TL.encodeUtf8+{-# INLINE decodeBase32Padded #-}++-- | Attempt to decode a padded lazy 'ByteString' value as Base32, converting from+-- 'ByteString' to 'TL.Text' according to some encoding function. In practice,+-- This is something like 'TL.decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-6 RFC-4648 section 6>+--+-- === __Example__:+--+-- @+-- 'decodeBase32PaddedWith' 'TL.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'Text'+-- @+--+decodeBase32PaddedWith+    :: (ByteString -> Either err TL.Text)+      -- ^ convert a bytestring to text (e.g. 'TL.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) TL.Text+decodeBase32PaddedWith f t = case BL32.decodeBase32Padded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32PaddedWith #-}++-- -- | Leniently decode an unpadded Base32-encoded 'TL.Text'. This function+-- -- will not generate parse errors. If input data contains padding chars,+-- -- then the input will be parsed up until the first pad character.+-- --+-- -- __Note:__ This is not RFC 4648-compliant.+-- --+-- -- === __Examples__:+-- --+-- -- >>> decodeBase32Lenient "PDw_Pj4="+-- -- "<<?>>"+-- --+-- -- >>> decodeBase32Lenient "PDw_%%%$}Pj4"+-- -- "<<?>>"+-- --+-- decodeBase32Lenient :: TL.Text -> TL.Text+-- decodeBase32Lenient = TL.decodeLatin1+--     . BL32.decodeBase32Lenient+--     . TL.encodeUtf8+-- {-# INLINE decodeBase32Lenient #-}++-- | Tell whether a 'TL.Text' value is Base32-encoded+--+-- === __Examples__:+--+-- >>> isBase32 "KN2W4"+-- True+--+-- >>> isBase32 "KN2W4==="+-- True+--+-- >>> isBase32 "KN2W4=="+-- False+--+isBase32 :: TL.Text -> Bool+isBase32 = BL32.isBase32 . TL.encodeUtf8+{-# INLINE isBase32 #-}++-- | Tell whether a 'TL.Text' value is a valid Base32 format.+--+-- This will not tell you whether or not this is a correct Base32 representation,+-- only that it conforms to the correct shape. To check whether it is a true+-- Base32 encoded 'TL.Text' value, use 'isBase32'.+--+-- === __Examples__:+--+-- >>> isValidBase32 "KN2W4"+-- True+--+-- >>> isValidBase32 "KN2W4="+-- False+--+-- >>> isValidBase32 "KN2W4%"+-- False+--+isValidBase32 :: TL.Text -> Bool+isValidBase32 = BL32.isValidBase32 . TL.encodeUtf8+{-# INLINE isValidBase32 #-}
+ src/Data/Text/Lazy/Encoding/Base32/Hex.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE Safe #-}+-- |+-- Module       : Data.Text.Lazy.Encoding.Base32.Hex+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains 'Data.Text.Lazy.Text'-valued combinators for+-- implementing the RFC 4648 specification of the Base32hex+-- encoding format. This includes strictly padded/unpadded decoding+-- variants, as well as internal and external validation for canonicity.+--+module Data.Text.Lazy.Encoding.Base32.Hex+( -- * Encoding+  encodeBase32+, encodeBase32Unpadded+  -- * Decoding+, decodeBase32+, decodeBase32With+, decodeBase32Unpadded+, decodeBase32UnpaddedWith+, decodeBase32Padded+, decodeBase32PaddedWith+--, decodeBase32Lenient+  -- * Validation+, isBase32Hex+, isValidBase32Hex+) where+++import Data.Bifunctor (first)+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy.Base32.Hex as BL32H++import qualified Data.Text as T+import Data.Text.Encoding.Base32.Error+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TL++-- | Encode a 'TL.Text' value in Base32hex with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7  RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "ADQMS==="+--+encodeBase32 :: TL.Text -> TL.Text+encodeBase32 = BL32H.encodeBase32 . TL.encodeUtf8+{-# INLINE encodeBase32 #-}++-- | Decode a padded Base32hex-encoded 'TL.Text' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as base32hex encodings are optionally padded.+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'TL.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32With`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32 "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32 :: TL.Text -> Either T.Text TL.Text+decodeBase32 = fmap TL.decodeLatin1 . BL32H.decodeBase32 . TL.encodeUtf8+{-# INLINE decodeBase32 #-}++-- | Attempt to decode a lazy 'ByteString' value as Base32hex, converting from+-- 'ByteString' to 'TL.Text' according to some encoding function. In practice,+-- This is something like 'TL.decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32With' 'TL.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'TL.Text'+-- @+--+decodeBase32With+    :: (ByteString -> Either err TL.Text)+      -- ^ convert a bytestring to text (e.g. 'TL.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) TL.Text+decodeBase32With f t = case BL32H.decodeBase32 t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32With #-}++-- | Encode a 'TL.Text' value in Base32hex without padding. Note that for Base32hex,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32hex and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "ADQMS"+--+encodeBase32Unpadded :: TL.Text -> TL.Text+encodeBase32Unpadded = BL32H.encodeBase32Unpadded . TL.encodeUtf8+{-# INLINE encodeBase32Unpadded #-}++-- | Decode an unpadded Base32hex encoded 'TL.Text' value.+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'TL.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32WUnpaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32Unpadded :: TL.Text -> Either T.Text TL.Text+decodeBase32Unpadded = fmap TL.decodeLatin1+    . BL32H.decodeBase32Unpadded+    . TL.encodeUtf8+{-# INLINE decodeBase32Unpadded #-}++-- | Attempt to decode an unpadded lazy 'ByteString' value as Base32hex, converting from+-- 'ByteString' to 'TL.Text' according to some encoding function. In practice,+-- This is something like 'TL.decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32UnpaddedWith' 'TL.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'TL.Text'+-- @+--+decodeBase32UnpaddedWith+    :: (ByteString -> Either err TL.Text)+      -- ^ convert a bytestring to text (e.g. 'TL.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) TL.Text+decodeBase32UnpaddedWith f t = case BL32H.decodeBase32Unpadded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32UnpaddedWith #-}++-- | Decode an padded Base32hex encoded 'TL.Text' value+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'TL.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32PaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "ADQMS"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: TL.Text -> Either T.Text TL.Text+decodeBase32Padded = fmap TL.decodeLatin1+    . BL32H.decodeBase32Padded+    . TL.encodeUtf8+{-# INLINE decodeBase32Padded #-}++-- | Attempt to decode a padded lazy 'ByteString' value as Base32hex, converting from+-- 'ByteString' to 'TL.Text' according to some encoding function. In practice,+-- This is something like 'TL.decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Example__:+--+-- @+-- 'decodeBase32PaddedWith' 'TL.decodeUtf8''+--   :: 'ByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'Text'+-- @+--+decodeBase32PaddedWith+    :: (ByteString -> Either err TL.Text)+      -- ^ convert a bytestring to text (e.g. 'TL.decodeUtf8'')+    -> ByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) TL.Text+decodeBase32PaddedWith f t = case BL32H.decodeBase32Padded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32PaddedWith #-}++-- -- | Leniently decode an unpadded Base32hex-encoded 'TL.Text'. This function+-- -- will not generate parse errors. If input data contains padding chars,+-- -- then the input will be parsed up until the first pad character.+-- --+-- -- __Note:__ This is not RFC 4648-compliant.+-- --+-- -- === __Examples__:+-- --+-- -- >>> decodeBase32Lenient "PDw_Pj4="+-- -- "<<?>>"+-- --+-- -- >>> decodeBase32Lenient "PDw_%%%$}Pj4"+-- -- "<<?>>"+-- --+-- decodeBase32Lenient :: TL.Text -> TL.Text+-- decodeBase32Lenient = TL.decodeLatin1+--     . BL32H.decodeBase32Lenient+--     . TL.encodeUtf8+-- {-# INLINE decodeBase32Lenient #-}++-- | Tell whether a 'TL.Text' value is Base32hex-encoded+--+-- === __Examples__:+--+-- >>> isBase32Hex "ADQMS"+-- True+--+-- >>> isBase32Hex "ADQMS==="+-- True+--+-- >>> isBase32Hex "ADQMS=="+-- False+--+isBase32Hex :: TL.Text -> Bool+isBase32Hex = BL32H.isBase32Hex . TL.encodeUtf8+{-# INLINE isBase32Hex #-}++-- | Tell whether a 'TL.Text' value is a valid Base32hex format.+--+-- This will not tell you whether or not this is a correct Base32hex representation,+-- only that it conforms to the correct shape. To check whether it is a true+-- Base32 encoded 'TL.Text' value, use 'isBase32Hex'.+--+-- === __Examples__:+--+-- >>> isValidBase32Hex "ADQMS"+-- True+--+-- >>> isValidBase32Hex "ADQMS="+-- False+--+-- >>> isValidBase32Hex "ADQMS%"+-- False+--+isValidBase32Hex :: TL.Text -> Bool+isValidBase32Hex = BL32H.isValidBase32Hex . TL.encodeUtf8+{-# INLINE isValidBase32Hex #-}
+ src/Data/Text/Short/Encoding/Base32.hs view
@@ -0,0 +1,273 @@+{-# LANGUAGE Trustworthy #-}+-- |+-- Module       : Data.Text.Short.Encoding.Base32+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains 'Data.Text.Short.ShortText'-valued combinators+-- implementing the RFC 4648 specification for the Base32+-- encoding format. This includes strictly padded/unpadded decoding+-- variants, and external + internal validations for canonicity.+--+module Data.Text.Short.Encoding.Base32+( -- * Encoding+  encodeBase32+, encodeBase32Unpadded+  -- * Decoding+, decodeBase32+, decodeBase32With+, decodeBase32Unpadded+, decodeBase32UnpaddedWith+, decodeBase32Padded+, decodeBase32PaddedWith+--, decodeBase32Lenient+  -- * Validation+, isBase32+, isValidBase32+) where+++import Data.Bifunctor (first)+import qualified Data.ByteString.Base32 as B32+import Data.ByteString.Short (ShortByteString)+import qualified Data.ByteString.Short.Base32 as BS32+import Data.Text (Text)+import qualified Data.Text.Encoding.Base32 as B32T+import Data.Text.Encoding.Base32.Error+import Data.Text.Short+import Data.Text.Short.Unsafe++-- | Encode a 'ShortText' value in Base32 with padding.+--+-- See: <https://tools.ietf.org/html/rfc4328#section-6 RFC-4328 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "KN2W4==="+--+encodeBase32 :: ShortText -> ShortText+encodeBase32 = fromByteStringUnsafe+  . B32.encodeBase32'+  . toByteString+{-# INLINE encodeBase32 #-}++-- | Decode an arbitrarily padded Base32-encoded 'ShortText' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as base32 encodings are optionally padded.+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'Data.Text.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32With`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4328#section-6 RFC-4328 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32 "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32 "KN2W==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32 :: ShortText -> Either Text ShortText+decodeBase32 = fmap fromText . B32T.decodeBase32 . toText+{-# INLINE decodeBase32 #-}++-- | Attempt to decode a 'ShortByteString' value as Base32, converting from+-- 'ByteString' to 'ShortText' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4328#section-6 RFC-4328 section 6>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32With' '(fmap fromText . T.decodeUtf8' . toText)'+--   :: 'ShortByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'ShortText'+-- @+--+decodeBase32With+    :: (ShortByteString -> Either err ShortText)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ShortByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) ShortText+decodeBase32With f t = case BS32.decodeBase32 t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32With #-}++-- | Encode a 'ShortText' value in Base32 without padding. Note that for Base32,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32 and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4328#section-6 RFC-4328 section 6>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "KN2W4"+--+encodeBase32Unpadded :: ShortText -> ShortText+encodeBase32Unpadded = fromByteStringUnsafe+  . B32.encodeBase32Unpadded'+  . toByteString+{-# INLINE encodeBase32Unpadded #-}++-- | Decode an unpadded Base32 encoded 'ShortText' value.+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'T.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32UnpaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4328#section-6 RFC-4328 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "KN2W4"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "KN2W4==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32Unpadded :: ShortText -> Either Text ShortText+decodeBase32Unpadded = fmap fromText . B32T.decodeBase32Unpadded . toText+{-# INLINE decodeBase32Unpadded #-}++-- | Attempt to decode an unpadded 'ShortByteString' value as Base32, converting from+-- 'ShortByteString' to 'ShortText' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4328#section-6 RFC-4328 section 6>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32UnpaddedWith' '(fmap fromText . T.decodeUtf8' . toText)'+--   :: 'ShortByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'ShortText'+-- @+--+decodeBase32UnpaddedWith+    :: (ShortByteString -> Either err ShortText)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ShortByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) ShortText+decodeBase32UnpaddedWith f t = case BS32.decodeBase32Unpadded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32UnpaddedWith #-}++-- | Decode an padded Base32 encoded 'ShortText' value+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'T.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32PaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4328#section-6 RFC-4328 section 6>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "KN2W4==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "KN2W4"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: ShortText -> Either Text ShortText+decodeBase32Padded = fmap fromText . B32T.decodeBase32Padded . toText+{-# INLINE decodeBase32Padded #-}++-- | Attempt to decode a padded 'ShortByteString' value as Base32, converting from+-- 'ByteString' to 'ShortText' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4328#section-6 RFC-4328 section 6>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32With' '(fmap fromText . T.decodeUtf8' . toText)'+--   :: 'ShortByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'ShortText'+-- @+--+decodeBase32PaddedWith+    :: (ShortByteString -> Either err ShortText)+      -- ^ convert a bytestring to text (e.g. 'T.decodeUtf8'')+    -> ShortByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) ShortText+decodeBase32PaddedWith f t = case BS32.decodeBase32Padded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32PaddedWith #-}++-- -- | Leniently decode an unpadded Base32-encoded 'ShortText'. This function+-- -- will not generate parse errors. If input data contains padding chars,+-- -- then the input will be parsed up until the first pad character.+-- --+-- -- __Note:__ This is not RFC 4328-compliant.+-- --+-- -- === __Examples__:+-- --+-- -- >>> decodeBase32Lenient "PDw_Pj4="+-- -- "<<?>>"+-- --+-- -- >>> decodeBase32Lenient "PDw_%%%$}Pj4"+-- -- "<<?>>"+-- --+-- decodeBase32Lenient :: ShortText -> ShortText+-- decodeBase32Lenient = fromText . B32T.decodeBase32Lenient . toText+-- {-# INLINE decodeBase32Lenient #-}++-- | Tell whether a 'ShortText' value is Base32-encoded.+--+-- === __Examples__:+--+-- >>> isBase32 "KN2W4"+-- True+--+-- >>> isBase32 "KN2W4==="+-- True+--+-- >>> isBase32 "KN2W4=="+-- False+--+isBase32 :: ShortText -> Bool+isBase32 = B32.isBase32 . toByteString+{-# INLINE isBase32 #-}++-- | Tell whether a 'ShortText' value is a valid Base32 format.+--+-- This will not tell you whether or not this is a correct Base32 representation,+-- only that it conforms to the correct shape. To check whether it is a true+-- Base32 encoded 'ShortText' value, use 'isBase32'.+--+-- === __Examples__:+--+-- >>> isValidBase32 "KN2W4"+-- True+--+-- >>> isValidBase32 "KN2W4="+-- False+--+-- >>> isValidBase32 "KN2W4%"+-- False+--+isValidBase32 :: ShortText -> Bool+isValidBase32 = B32.isValidBase32 . toByteString+{-# INLINE isValidBase32 #-}
+ src/Data/Text/Short/Encoding/Base32/Hex.hs view
@@ -0,0 +1,273 @@+{-# LANGUAGE Trustworthy #-}+-- |+-- Module       : Data.Text.Short.Encoding.Base32.Hex+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : stable+-- Portability  : non-portable+--+-- This module contains 'Data.Text.Short.ShortText'-valued combinators+-- implementing the RFC 4648 specification for the Base32hex+-- encoding format. This includes strictly padded/unpadded+-- decoding variants, and external + internal validations for canonicity.+--+module Data.Text.Short.Encoding.Base32.Hex+( -- * Encoding+  encodeBase32+, encodeBase32Unpadded+  -- * Decoding+, decodeBase32+, decodeBase32With+, decodeBase32Unpadded+, decodeBase32UnpaddedWith+, decodeBase32Padded+, decodeBase32PaddedWith+--, decodeBase32Lenient+  -- * Validation+, isBase32Hex+, isValidBase32Hex+) where+++import Data.Bifunctor (first)+import qualified Data.ByteString.Base32.Hex as B32H+import Data.ByteString.Short (ShortByteString)+import qualified Data.ByteString.Short.Base32.Hex as BS32H+import Data.Text (Text)+import qualified Data.Text.Encoding.Base32.Hex as B32TH+import Data.Text.Encoding.Base32.Error+import Data.Text.Short+import Data.Text.Short.Unsafe++-- | Encode a 'ShortText' value in Base32hex with padding.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32 "Sun"+-- "ADQMS==="+--+encodeBase32 :: ShortText -> ShortText+encodeBase32 = fromByteStringUnsafe+  . B32H.encodeBase32'+  . toByteString+{-# INLINE encodeBase32 #-}++-- | Decode an arbitrarily padded Base32hex-encoded 'ShortText' value. If its length is not a multiple+-- of 4, then padding chars will be added to fill out the input to a multiple of+-- 4 for safe decoding as base32hex encodings are optionally padded.+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'Data.Text.Encoding.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32With`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32 "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32 "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32 :: ShortText -> Either Text ShortText+decodeBase32 = fmap fromText . B32TH.decodeBase32 . toText+{-# INLINE decodeBase32 #-}++-- | Attempt to decode a 'ShortByteString' value as Base32hex, converting from+-- 'ByteString' to 'ShortText' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32With' '(fmap fromText . Data.Text.Encoding.decodeUtf8' . toText)'+--   :: 'ShortByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'ShortText'+-- @+--+decodeBase32With+    :: (ShortByteString -> Either err ShortText)+      -- ^ convert a bytestring to text (e.g. '(fmap fromText . Data.Text.Encoding.decodeUtf8' . toText)')+    -> ShortByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) ShortText+decodeBase32With f t = case BS32H.decodeBase32 t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32With #-}++-- | Encode a 'ShortText' value in Base32hex without padding. Note that for Base32hex,+-- padding is optional. If you call this function, you will simply be encoding+-- as Base32hex and stripping padding chars from the output.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> encodeBase32Unpadded "Sun"+-- "ADQMS"+--+encodeBase32Unpadded :: ShortText -> ShortText+encodeBase32Unpadded = fromByteStringUnsafe+  . B32H.encodeBase32Unpadded'+  . toByteString+{-# INLINE encodeBase32Unpadded #-}++-- | Decode an unpadded Base32hex encoded 'ShortText' value.+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'Data.Text.Encoding.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32UnpaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Unpadded "ADQMS"+-- Right "Sun"+--+-- >>> decodeBase32Unpadded "ADQMS==="+-- Left "Base32-encoded bytestring has invalid padding"+--+decodeBase32Unpadded :: ShortText -> Either Text ShortText+decodeBase32Unpadded = fmap fromText . B32TH.decodeBase32Unpadded . toText+{-# INLINE decodeBase32Unpadded #-}++-- | Attempt to decode an unpadded 'ShortByteString' value as Base32hex, converting from+-- 'ShortByteString' to 'ShortText' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32UnpaddedWith' '(fmap fromText . Data.Text.Encoding.decodeUtf8' . toText)'+--   :: 'ShortByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'ShortText'+-- @+--+decodeBase32UnpaddedWith+    :: (ShortByteString -> Either err ShortText)+      -- ^ convert a bytestring to text (e.g. '(fmap fromText . Data.Text.Encoding.decodeUtf8' . toText)')+    -> ShortByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) ShortText+decodeBase32UnpaddedWith f t = case BS32H.decodeBase32Unpadded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32UnpaddedWith #-}++-- | Decode an padded Base32hex encoded 'ShortText' value+--+-- /Note:/ This function makes sure that decoding is total by deferring to+-- 'Data.Text.Encoding.decodeLatin1'. This will always round trip for any valid Base32-encoded+-- text value, but it may not round trip for bad inputs. The onus is on the+-- caller to make sure inputs are valid. If unsure, defer to `decodeBase32PaddedWith`+-- and pass in a custom decode function.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- >>> decodeBase32Padded "ADQMS==="+-- Right "Sun"+--+-- >>> decodeBase32Padded "ADQMS"+-- Left "Base32-encoded bytestring requires padding"+--+decodeBase32Padded :: ShortText -> Either Text ShortText+decodeBase32Padded = fmap fromText . B32TH.decodeBase32Padded . toText+{-# INLINE decodeBase32Padded #-}++-- | Attempt to decode a padded 'ShortByteString' value as Base32hex, converting from+-- 'ByteString' to 'ShortText' according to some encoding function. In practice,+-- This is something like 'decodeUtf8'', which may produce an error.+--+-- See: <https://tools.ietf.org/html/rfc4648#section-7 RFC-4648 section 7>+--+-- === __Examples__:+--+-- @+-- 'decodeBase32With' '(fmap fromText . Data.Text.Encoding.decodeUtf8' . toText)'+--   :: 'ShortByteString' -> 'Either' ('Base32Error' 'UnicodeException') 'ShortText'+-- @+--+decodeBase32PaddedWith+    :: (ShortByteString -> Either err ShortText)+      -- ^ convert a bytestring to text (e.g. 'Data.Text.Encoding.decodeUtf8'')+    -> ShortByteString+      -- ^ Input text to decode+    -> Either (Base32Error err) ShortText+decodeBase32PaddedWith f t = case BS32H.decodeBase32Padded t of+  Left de -> Left $ DecodeError de+  Right a -> first ConversionError (f a)+{-# INLINE decodeBase32PaddedWith #-}++-- -- | Leniently decode an unpadded Base32hex-encoded 'ShortText'. This function+-- -- will not generate parse errors. If input data contains padding chars,+-- -- then the input will be parsed up until the first pad character.+-- --+-- -- __Note:__ This is not RFC 4648-compliant.+-- --+-- -- === __Examples__:+-- --+-- -- >>> decodeBase32Lenient "PDw_Pj4="+-- -- "<<?>>"+-- --+-- -- >>> decodeBase32Lenient "PDw_%%%$}Pj4"+-- -- "<<?>>"+-- --+-- decodeBase32Lenient :: ShortText -> ShortText+-- decodeBase32Lenient = fromText . B32TH.decodeBase32Lenient . toText+-- {-# INLINE decodeBase32Lenient #-}++-- | Tell whether a 'ShortText' value is Base32hex-encoded.+--+-- === __Examples__:+--+-- >>> isBase32Hex "ADQMS"+-- True+--+-- >>> isBase32Hex "ADQMS==="+-- True+--+-- >>> isBase32Hex "ADQMS=="+-- False+--+isBase32Hex :: ShortText -> Bool+isBase32Hex = B32H.isBase32Hex . toByteString+{-# INLINE isBase32Hex #-}++-- | Tell whether a 'ShortText' value is a valid Base32hex format.+--+-- This will not tell you whether or not this is a correct Base32hex representation,+-- only that it conforms to the correct shape. To check whether it is a true+-- Base32 encoded 'ShortText' value, use 'isBase32Hex'.+--+-- === __Examples__:+--+-- >>> isValidBase32Hex "ADQMS"+-- True+--+-- >>> isValidBase32Hex "ADQMS="+-- False+--+-- >>> isValidBase32Hex "ADQMS%"+-- False+--+isValidBase32Hex :: ShortText -> Bool+isValidBase32Hex = B32H.isValidBase32Hex . toByteString+{-# INLINE isValidBase32Hex #-}
− test/Base32Tests.hs
@@ -1,148 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PackageImports #-}-{-# LANGUAGE TypeApplications #-}-module Main-( main-, tests-) where---import Data.Bifunctor-import Data.ByteString (ByteString)-import "base32" Data.ByteString.Base32 as B32-import "base32" Data.ByteString.Base32.Hex as B32H-import "memory" Data.ByteArray.Encoding as Mem-import Data.ByteString.Random (random)-import Data.Functor (void)-import Data.Text (pack)--import Test.Tasty-import Test.Tasty.HUnit---main :: IO ()-main = defaultMain tests---tests :: TestTree-tests = testGroup "Base32 Tests"-    [ testVectors-    , sanityTests-    , alphabetTests-    ]--testVectors :: TestTree-testVectors = testGroup "RFC 4648 Test Vectors"-    [ testGroup "encode/decode"-      [ testCaseB32 "" ""-      , testCaseB32 "f" "MY======"-      , testCaseB32 "fo" "MZXQ===="-      , testCaseB32 "foo" "MZXW6==="-      , testCaseB32 "foob" "MZXW6YQ="-      , testCaseB32 "fooba" "MZXW6YTB"-      , testCaseB32 "foobar" "MZXW6YTBOI======"-      ]-    , testGroup "encode/decode hex"-      [ testCaseB32' "" ""-      , testCaseB32' "f" "CO======"-      , testCaseB32' "fo" "CPNG===="-      , testCaseB32' "foo" "CPNMU==="-      , testCaseB32' "foob" "CPNMUOG="-      , testCaseB32' "fooba" "CPNMUOJ1"-      , testCaseB32' "foobar" "CPNMUOJ1E8======"-      ]-    ]-  where-    testCaseB32 s t =-      testCaseSteps (show $ if s == "" then "empty" else s) $ \step -> do-        let t' = B32.encodeBase32' s-            s' = B32.decodeBase32 t'--        step "compare encoding + decoding w/ padding"-        t @=? t'--        step "compare decoding w/ padding"-        Right s @=? s'--    testCaseB32' s t =-      testCaseSteps (show $ if s == "" then "empty" else s) $ \step -> do-        let t' = B32H.encodeBase32' s-            s' = B32H.decodeBase32 t'-            u = B32H.encodeBase32' s-            v = B32H.decodeBase32 u--        step "compare hex encoding w/ padding"-        t @=? t'--        step "compare hex decoding w/ padding"-        Right s @=? s'--        step "compare hex encoding w/o padding"-        t @=? t'--        step "compare hex decoding w/o padding"-        Right s @=? v--sanityTests :: TestTree-sanityTests = testGroup "Sanity tests"-    [ testGroup "very large bytestrings don't segfault"-        [ chonk-        ]-    , testGroup "`memory` sanity checks"-        [ compare32 3-        , compare32 4-        , compare32 5-        , compare32 6-        , compare32 1000-        , compare32 100000-        ]-    , testGroup "roundtrip encode/decode"-        [ roundtrip 3-        , roundtrip 4-        , roundtrip 5-        , roundtrip 1000-        , roundtrip 100000-        ]-    ]-  where-    chonk = testCase ("Encoding huge bytestrings doesn't result in OOM or segfault") $ do-      bs <- random 1000000-      void $ return $ B32.encodeBase32' bs-      void $ return $ B32H.encodeBase32' bs--    compare32 n = testCase ("Testing " ++ show n ++ "-sized bytestrings") $ do-      bs <- random n-      B32.encodeBase32' bs @=? Mem.convertToBase Mem.Base32 bs-      B32.decodeBase32 (B32.encodeBase32' bs) @=?-        first pack (Mem.convertFromBase @ByteString Mem.Base32 (Mem.convertToBase Mem.Base32 bs))--    roundtrip n = testCase ("Roundtrip encode/decode for " ++ show n ++ "-sized bytestrings") $ do-      bs <- random n-      B32.decodeBase32 (B32.encodeBase32' bs) @=? Right bs-      B32H.decodeBase32 (B32H.encodeBase32' bs) @=? Right bs--alphabetTests :: TestTree-alphabetTests = testGroup "Alphabet tests"-    [ base32Tests 0-    , base32Tests 4-    , base32Tests 5-    , base32Tests 6-    , base32Tests 100-    , base32HexTests 0-    , base32HexTests 4-    , base32HexTests 5-    , base32HexTests 6-    , base32HexTests 100-    ]-  where-    base32Tests n = testCase ("Conforms to Base32 alphabet: " ++ show n) $ do-      bs <- random n-      let b = B32.encodeBase32' bs-      assertBool ("failed validity: " ++ show b) $ B32.isValidBase32 b-      assertBool ("failed correctness: " ++ show b) $ B32.isBase32 b--    base32HexTests n = testCase ("Conforms to Base32hex alphabet: " ++ show n) $ do-      bs <- random n-      let b = B32H.encodeBase32' bs-      assertBool ("failed validity: " ++ show b) $ B32H.isValidBase32Hex b-      assertBool ("failed correctness: " ++ show b) $ B32H.isBase32Hex b
+ test/Internal.hs view
@@ -0,0 +1,308 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE PackageImports #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- |+-- Module       : Main+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : Experimental+-- Portability  : portable+--+-- This module contains internal test harnesses for `base32`+--+module Internal where+++import qualified Data.ByteString as BS+import "base32" Data.ByteString.Base32 as B32+import "base32" Data.ByteString.Base32.Hex as B32H+import qualified Data.ByteString.Lazy as LBS+import "base32" Data.ByteString.Lazy.Base32 as BL32+import "base32" Data.ByteString.Lazy.Base32.Hex as BL32H+import qualified Data.ByteString.Short as SBS+import "base32" Data.ByteString.Short.Base32 as BS32+import "base32" Data.ByteString.Short.Base32.Hex as BS32H+import Data.Proxy+import Data.String+import Data.Text (Text)+import qualified Data.Text as T+import "base32" Data.Text.Encoding.Base32 as T32+import "base32" Data.Text.Encoding.Base32.Error (Base32Error)+import "base32" Data.Text.Encoding.Base32.Hex as T32H+import qualified Data.Text.Lazy as TL+import "base32" Data.Text.Lazy.Encoding.Base32 as TL32+import "base32" Data.Text.Lazy.Encoding.Base32.Hex as TL32H+import qualified Data.Text.Short as TS+import "base32" Data.Text.Short.Encoding.Base32 as TS32+import "base32" Data.Text.Short.Encoding.Base32.Hex as TS32H++import Test.QuickCheck hiding (label)++-- ------------------------------------------------------------------ --+-- Test Harnesses++data Impl+  = B32+  | BL32+  | BS32+  | T32+  | TL32+  | TS32++b32 :: Proxy 'B32+b32 = Proxy++bl32 :: Proxy 'BL32+bl32 = Proxy++bs32 :: Proxy 'BS32+bs32 = Proxy++t32 :: Proxy 'T32+t32 = Proxy++tl32 :: Proxy 'TL32+tl32 = Proxy++ts32 :: Proxy 'TS32+ts32 = Proxy++-- | This class provides the generic API definition for+-- the base32 std alphabet+--+class+  ( Eq bs+  , Show bs+  , Arbitrary bs+  , CoArbitrary bs+  , IsString bs+  ) => Harness (a :: Impl) bs | a -> bs, bs -> a+  where++  label :: String++  encode :: bs -> bs+  encodeNopad :: bs -> bs+  encodeHex :: bs -> bs+  encodeHexNopad :: bs -> bs++  decode :: bs -> Either Text bs+  decodeHex :: bs -> Either Text bs+  decodePad :: bs -> Either Text bs+  decodeHexPad :: bs -> Either Text bs+  decodeNopad :: bs -> Either Text bs+  decodeHexNopad :: bs -> Either Text bs++  correct :: bs -> Bool+  correctHex :: bs -> Bool++  validate :: bs -> Bool+  validateHex :: bs -> Bool+++instance Harness 'B32 BS.ByteString where+  label = "ByteString"++  encode = B32.encodeBase32'+  encodeNopad = B32.encodeBase32Unpadded'++  decode = B32.decodeBase32+  decodePad = B32.decodeBase32Padded+  decodeNopad = B32.decodeBase32Unpadded+  correct = B32.isBase32+  validate = B32.isValidBase32++  encodeHex = B32H.encodeBase32'+  encodeHexNopad = B32H.encodeBase32Unpadded'+  decodeHex = B32H.decodeBase32+  decodeHexPad = B32H.decodeBase32Padded+  decodeHexNopad = B32H.decodeBase32Unpadded+  correctHex = B32H.isBase32Hex+  validateHex = B32H.isValidBase32Hex++instance Harness 'BL32 LBS.ByteString where+  label = "Lazy ByteString"++  encode = BL32.encodeBase32'+  encodeNopad = BL32.encodeBase32Unpadded'++  decode = BL32.decodeBase32+  decodePad = BL32.decodeBase32Padded+  decodeNopad = BL32.decodeBase32Unpadded+  correct = BL32.isBase32+  validate = BL32.isValidBase32++  encodeHex = BL32H.encodeBase32'+  encodeHexNopad = BL32H.encodeBase32Unpadded'+  decodeHex = BL32H.decodeBase32+  decodeHexPad = BL32H.decodeBase32Padded+  decodeHexNopad = BL32H.decodeBase32Unpadded+  correctHex = BL32H.isBase32Hex+  validateHex = BL32H.isValidBase32Hex++instance Harness 'BS32 SBS.ShortByteString where+  label = "Short ByteString"++  encode = BS32.encodeBase32'+  encodeNopad = BS32.encodeBase32Unpadded'++  decode = BS32.decodeBase32+  decodePad = BS32.decodeBase32Padded+  decodeNopad = BS32.decodeBase32Unpadded+  correct = BS32.isBase32+  validate = BS32.isValidBase32++  encodeHex = BS32H.encodeBase32'+  encodeHexNopad = BS32H.encodeBase32Unpadded'+  decodeHex = BS32H.decodeBase32+  decodeHexPad = BS32H.decodeBase32Padded+  decodeHexNopad = BS32H.decodeBase32Unpadded+  correctHex = BS32H.isBase32Hex+  validateHex = BS32H.isValidBase32Hex++instance Harness 'T32 T.Text where+  label = "Text"++  encode = T32.encodeBase32+  encodeNopad = T32.encodeBase32Unpadded+  decode = T32.decodeBase32+  decodeNopad = T32.decodeBase32Unpadded+  decodePad = T32.decodeBase32Padded+  correct = T32.isBase32++  encodeHex = T32H.encodeBase32+  encodeHexNopad = T32H.encodeBase32Unpadded+  decodeHex = T32H.decodeBase32+  decodeHexPad = T32H.decodeBase32Padded+  decodeHexNopad = T32H.decodeBase32Unpadded++  correctHex = T32H.isBase32Hex+  validateHex = T32H.isValidBase32Hex+  validate = T32.isValidBase32++instance Harness 'TL32 TL.Text where+  label = "Lazy Text"++  encode = TL32.encodeBase32+  encodeNopad = TL32.encodeBase32Unpadded+  decode = TL32.decodeBase32+  decodeNopad = TL32.decodeBase32Unpadded+  decodePad = TL32.decodeBase32Padded+  correct = TL32.isBase32++  encodeHex = TL32H.encodeBase32+  encodeHexNopad = TL32H.encodeBase32Unpadded+  decodeHex = TL32H.decodeBase32+  decodeHexPad = TL32H.decodeBase32Padded+  decodeHexNopad = TL32H.decodeBase32Unpadded++  correctHex = TL32H.isBase32Hex+  validateHex = TL32H.isValidBase32Hex+  validate = TL32.isValidBase32++instance Harness 'TS32 TS.ShortText where+  label = "Short Text"++  encode = TS32.encodeBase32+  encodeNopad = TS32.encodeBase32Unpadded+  decode = TS32.decodeBase32+  decodeNopad = TS32.decodeBase32Unpadded+  decodePad = TS32.decodeBase32Padded+  correct = TS32.isBase32++  encodeHex = TS32H.encodeBase32+  encodeHexNopad = TS32H.encodeBase32Unpadded+  decodeHex = TS32H.decodeBase32+  decodeHexPad = TS32H.decodeBase32Padded+  decodeHexNopad = TS32H.decodeBase32Unpadded++  correctHex = TS32H.isBase32Hex+  validateHex = TS32H.isValidBase32Hex+  validate = TS32.isValidBase32++class Harness a cs+  => TextHarness (a :: Impl) cs bs+  | a -> cs, bs -> cs, cs -> a, cs -> bs where+  decodeWith_ :: (bs -> Either err cs) -> bs -> Either (Base32Error err) cs+  decodePaddedWith_ :: (bs -> Either err cs) -> bs -> Either (Base32Error err) cs+  decodeUnpaddedWith_ :: (bs -> Either err cs) -> bs -> Either (Base32Error err) cs+  decodeHexWith_ :: (bs -> Either err cs) -> bs -> Either (Base32Error err) cs+  decodeHexPaddedWith_ :: (bs -> Either err cs) -> bs -> Either (Base32Error err) cs+  decodeHexUnpaddedWith_ :: (bs -> Either err cs) -> bs -> Either (Base32Error err) cs+++instance TextHarness 'T32 Text BS.ByteString where+  decodeWith_ = T32.decodeBase32With+  decodePaddedWith_ = T32.decodeBase32PaddedWith+  decodeUnpaddedWith_ = T32.decodeBase32UnpaddedWith+  decodeHexWith_ = T32H.decodeBase32With+  decodeHexPaddedWith_ = T32H.decodeBase32PaddedWith+  decodeHexUnpaddedWith_ = T32H.decodeBase32UnpaddedWith++instance TextHarness 'TL32 TL.Text LBS.ByteString where+  decodeWith_ = TL32.decodeBase32With+  decodePaddedWith_ = TL32.decodeBase32PaddedWith+  decodeUnpaddedWith_ = TL32.decodeBase32UnpaddedWith+  decodeHexWith_ = TL32H.decodeBase32With+  decodeHexPaddedWith_ = TL32H.decodeBase32PaddedWith+  decodeHexUnpaddedWith_ = TL32H.decodeBase32UnpaddedWith++instance TextHarness 'TS32 TS.ShortText SBS.ShortByteString where+  decodeWith_ = TS32.decodeBase32With+  decodePaddedWith_ = TS32.decodeBase32PaddedWith+  decodeUnpaddedWith_ = TS32.decodeBase32UnpaddedWith+  decodeHexWith_ = TS32H.decodeBase32With+  decodeHexPaddedWith_ = TS32H.decodeBase32PaddedWith+  decodeHexUnpaddedWith_ = TS32H.decodeBase32UnpaddedWith++-- ------------------------------------------------------------------ --+-- Quickcheck instances++instance Arbitrary BS.ByteString where+    arbitrary = BS.pack <$> arbitrary+    shrink xs = BS.pack <$> shrink (BS.unpack xs)++instance CoArbitrary BS.ByteString where+    coarbitrary = coarbitrary . BS.unpack++instance Arbitrary LBS.ByteString where+    arbitrary = LBS.pack <$> arbitrary+    shrink xs = LBS.pack <$> shrink (LBS.unpack xs)++instance CoArbitrary LBS.ByteString where+    coarbitrary = coarbitrary . LBS.unpack++instance Arbitrary SBS.ShortByteString where+    arbitrary = SBS.pack <$> arbitrary+    shrink xs = SBS.pack <$> shrink (SBS.unpack xs)++instance CoArbitrary SBS.ShortByteString where+    coarbitrary = coarbitrary . SBS.unpack++instance Arbitrary T.Text where+    arbitrary = T.pack <$> arbitrary+    shrink xs = T.pack <$> shrink (T.unpack xs)++instance Arbitrary TL.Text where+    arbitrary = TL.pack <$> arbitrary+    shrink xs = TL.pack <$> shrink (TL.unpack xs)++instance CoArbitrary T.Text where+    coarbitrary = coarbitrary . T.unpack++instance CoArbitrary TL.Text where+    coarbitrary = coarbitrary . TL.unpack++instance Arbitrary TS.ShortText where+  arbitrary = TS.fromText <$> arbitrary+  shrink xs = fmap TS.fromText $ shrink (TS.toText xs)++instance CoArbitrary TS.ShortText where+  coarbitrary = coarbitrary . TS.toText
+ test/Main.hs view
@@ -0,0 +1,465 @@+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+-- |+-- Module       : Main+-- Copyright    : (c) 2019-2020 Emily Pillmore+-- License      : BSD-style+--+-- Maintainer   : Emily Pillmore <emilypi@cohomolo.gy>+-- Stability    : Experimental+-- Portability  : portable+--+-- This module contains the test implementation for the `base32` package+--+module Main+( main+) where+++import Prelude hiding (length)++import Data.Bifunctor (second)+import qualified "memory" Data.ByteArray.Encoding as Mem+import qualified Data.ByteString as BS+import Data.ByteString.Internal (c2w)+import "base32" Data.ByteString.Base32 as B32+import "base32" Data.ByteString.Base32.Hex as B32H+import qualified Data.ByteString.Lazy as LBS+import qualified Data.ByteString.Short as SBS+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.Text.Encoding.Base32.Error (Base32Error(..))+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TL+import qualified Data.Text.Short as TS+import Data.Word++import Internal++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck (testProperty)+++main :: IO ()+main = defaultMain tests+++tests :: TestTree+tests = testGroup "Base32 Tests"+  [ mkTree b32+    [ mkPropTree+    , mkUnitTree+    ]+  , mkTree bl32+    [ mkPropTree+    , mkUnitTree+    ]+  , mkTree bs32+    [ mkPropTree+    , mkUnitTree+    ]+  , mkTree t32+    [ mkPropTree+    , mkUnitTree+    , mkDecodeTree T.decodeUtf8' b32+    ]+  , mkTree tl32+    [ mkPropTree+    , mkUnitTree+    , mkDecodeTree TL.decodeUtf8' bl32+    ]+  , mkTree ts32+    [ mkPropTree+    , mkUnitTree+    , mkDecodeTree+      (second TS.fromText . T.decodeUtf8' . SBS.fromShort) bs32+    ]+  ]++-- ---------------------------------------------------------------- --+-- Test tree generation++-- | Make a test tree for a given label+--+mkTree+  :: forall a b proxy+  . Harness a b+  => proxy a+  -> [proxy a -> TestTree]+  -> TestTree+mkTree a = testGroup (label @a) . fmap ($ a)++-- | Make a test group with some name, lifting a test tree up to the correct+-- type information via some Harness+--+mkTests+  :: forall a b proxy+  . Harness a b+  => String+  -> [proxy a -> TestTree]+  -> proxy a+  -> TestTree+mkTests context ts = testGroup context . (<*>) ts . pure++-- | Make property tests for a given harness instance+--+mkPropTree :: forall a b proxy. Harness a b => proxy a -> TestTree+mkPropTree = mkTests "Property Tests"+  [ prop_roundtrip+  , prop_correctness+  , prop_padding_invariants+  , const prop_mem_coherence+  ]++-- | Make unit tests for a given harness instance+--+mkUnitTree+  :: forall a b proxy+  . Harness a b+  => proxy a+  -> TestTree+mkUnitTree = mkTests "Unit tests"+  [ rfcVectors+  , offsetVectors+  , validityTests+  ]++-- | Make unit tests for textual 'decode*With' functions+--+mkDecodeTree+  :: forall t a b c e proxy+  . ( TextHarness a b c+    , Harness t c+    , Show e+    )+  => (c -> Either e b)+  -> proxy t+  -> proxy a+  -> TestTree+mkDecodeTree utf8 t = mkTests "Decoding tests"+  [ decodeWithVectors utf8 t+  ]++-- ---------------------------------------------------------------- --+-- Property tests++prop_roundtrip :: forall a b proxy. Harness a b => proxy a -> TestTree+prop_roundtrip _ = testGroup "prop_roundtrip"+  [ testProperty "prop_std_roundtrip" $ \(bs :: b) ->+      Right (encode bs) == decode (encode (encode bs))+  , testProperty "prop_hex_roundtrip" $ \(bs :: b) ->+      Right (encodeHex bs) == decodeHex (encodeHex (encodeHex bs))+  , testProperty "prop_hex_roundtrip_nopad" $ \(bs :: b) ->+      Right (encodeHexNopad bs)+        == decodeHexNopad (encodeHexNopad (encodeHexNopad bs))+  ]++prop_correctness :: forall a b proxy. Harness a b => proxy a -> TestTree+prop_correctness _ = testGroup "prop_validity"+  [ testProperty "prop_std_valid" $ \(bs :: b) ->+    validate (encode bs)+  , testProperty "prop_hex_valid" $ \(bs :: b) ->+    validateHex (encodeHex bs)+  , testProperty "prop_std_correct" $ \(bs :: b) ->+    correct (encode bs)+  , testProperty "prop_hex_correct" $ \(bs :: b) ->+    correctHex (encodeHex bs)+  ]++prop_padding_invariants :: forall a b proxy. Harness a b => proxy a -> TestTree+prop_padding_invariants _ = testGroup "prop_padding_invariants"+  [ testProperty "prop_hex_nopad_roundtrip" $ \(bs :: b) ->+      Right (encodeHexNopad bs)+        == decodeHexNopad (encodeHexNopad (encodeHexNopad bs))++  , testProperty "prop_hex_pad_roundtrip" $ \(bs :: b) ->+      Right (encodeHex bs) == decodeHexPad (encodeHex (encodeHex bs))++  , testProperty "prop_hex_decode_invariant" $ \(bs :: b) ->+      ( decodeHexNopad (encodeHexNopad (encodeHex bs))+      == decodeHex (encodeHex (encodeHex bs))+      ) ||+      ( decodeHexPad (encodeHex (encodeHex bs))+      == decodeHex (encodeHex (encodeHex bs))+      )++  , testProperty "prop_std_decode_invariant" $ \(bs :: b) ->+      ( decodeNopad (encodeNopad (encode bs))+      == decode (encode (encode bs))+      ) ||+      ( decodePad (encode (encode bs))+      == decode (encode (encode bs))+      )++  , testProperty "prop_hex_padding_coherence" $ \(bs :: b) ->+      Right (encodeHex bs) == decodeHex (encodeHex (encodeHex bs))+      && Right (encodeHex bs) == decodeHexPad (encodeHex (encodeHex bs))++  , testProperty "prop_hex_nopadding_coherence" $ \(bs :: b) ->+      Right (encodeHexNopad bs) == decodeHexNopad (encodeHexNopad (encodeHexNopad bs))+      && Right (encodeHexNopad bs) == decodeHex (encodeHexNopad (encodeHexNopad bs))+  ]++-- | just a sanity check against `memory`+--+prop_mem_coherence :: TestTree+prop_mem_coherence = testGroup "prop_mem_coherence"+  [ testProperty "prop_std_mem_coherence" $ \bs ->+      Right bs == B32.decodeBase32 (B32.encodeBase32' bs)+      && Right bs == Mem.convertFromBase Mem.Base32 (Mem.convertToBase @BS.ByteString @BS.ByteString Mem.Base32 bs)+  ]++-- ---------------------------------------------------------------- --+-- Unit tests++-- | RFC 4328 test vectors+--+rfcVectors :: forall a b proxy. Harness a b => proxy a -> TestTree+rfcVectors _ = testGroup "RFC 4648 Test Vectors"+    [ testGroup "std alphabet"+      [ testCaseStd "" ""+      , testCaseStd "f" "MY======"+      , testCaseStd "fo" "MZXQ===="+      , testCaseStd "foo" "MZXW6==="+      , testCaseStd "foob" "MZXW6YQ="+      , testCaseStd "fooba" "MZXW6YTB"+      , testCaseStd "foobar" "MZXW6YTBOI======"+      ]+    , testGroup "hex alphabet"+      [ testCaseHex "" ""+      , testCaseHex "f" "CO======"+      , testCaseHex "fo" "CPNG===="+      , testCaseHex "foo" "CPNMU==="+      , testCaseHex "foob" "CPNMUOG="+      , testCaseHex "fooba" "CPNMUOJ1"+      , testCaseHex "foobar" "CPNMUOJ1E8======"+      ]+    ]+  where+    testCaseStd s t =+      testCaseSteps (show $ if s == "" then "empty" else s) $ \step -> do+        step "encode is sound"+        t @=? encode @a s++        step "decode is sound"+        Right s @=? decode (encode s)++    testCaseHex s t =+      testCaseSteps (show $ if s == "" then "empty" else s) $ \step -> do+        step "encode is sound"+        t @=? encodeHex @a s++        step "decode is sound"+        Right s @=? decodeHexPad t++-- | Unit test trees for the `decode*With` family of text-valued functions+--+decodeWithVectors+  :: forall t a b c e proxy+  . ( TextHarness a c b+    , Harness t b+    , Show e+    )+  => (b -> Either e c)+    -- ^ utf8+  -> proxy t+    -- ^ witness to the bytestring-ey dictionaries+  -> proxy a+    -- ^ witness to the text dictionaries+  -> TestTree+decodeWithVectors utf8 _ _ = testGroup "DecodeWith* unit tests"+  [ testGroup "decodeWith negative tests"+    [ testCase "decodeWith non-utf8 inputs on decodeUtf8" $ do+      case decodeWith_ @a utf8 "\1079743" of+        Left (DecodeError _) -> return ()+        _ -> assertFailure "decoding phase"+    , testCase "decodeWith valid utf8 inputs on decodeUtf8" $ do+      case decodeWith_ @a utf8 (encode @t "\1079743") of+        Left (ConversionError _) -> return ()+        _ -> assertFailure "conversion phase"+    , testCase "decodeHexWith non-utf8 inputs on decodeUtf8" $ do+      case decodeHexWith_ @a utf8 "\1079743" of+        Left (DecodeError _) -> return ()+        _ -> assertFailure "decoding phase"+    , testCase "decodePaddedWith non-utf8 inputs on decodeUtf8" $ do+      case decodePaddedWith_ @a utf8 "\1079743" of+        Left (DecodeError _) -> return ()+        _ -> assertFailure "decoding phase"+    , testCase "decodePaddedWith valid utf8 inputs on decodeUtf8" $ do+      case decodePaddedWith_ @a utf8 (encode @t "\1079743") of+        Left (ConversionError _) -> return ()+        _ -> assertFailure "conversion phase"+    , testCase "decodeUnpaddedWith non-utf8 inputs on decodeUtf8" $ do+      case decodeUnpaddedWith_ @a utf8 "\1079743" of+        Left (DecodeError _) -> return ()+        _ -> assertFailure "decoding phase"+    , testCase "decodeUnpaddedWith valid utf8 inputs on decodeUtf8" $ do+      case decodeUnpaddedWith_ @a utf8 (encodeNopad @t "\1079743") of+        Left (ConversionError _) -> return ()+        _ -> assertFailure "conversion phase"+    , testCase "decodeHexWith valid utf8 inputs on decodeUtf8" $ do+      case decodeHexWith_ @a utf8 (encodeHex @t "\1079743") of+        Left (ConversionError _) -> return ()+        _ -> assertFailure "conversion phase"+    , testCase "decodeHexPaddedWith non-utf8 inputs on decodeUtf8" $ do+      case decodeHexPaddedWith_ @a utf8 "\1079743" of+        Left (DecodeError _) -> return ()+        _ -> assertFailure "decoding phase"+    , testCase "decodeHexPaddedWith valid utf8 inputs on decodeUtf8" $ do+      case decodeHexPaddedWith_ @a utf8 (encodeHex @t "\1079743") of+        Left (ConversionError _) -> return ()+        _ -> assertFailure "conversion phase"+    , testCase "decodeHexUnpaddedWith non-utf8 inputs on decodeUtf8" $ do+      case decodeHexUnpaddedWith_ @a utf8 "\1079743" of+        Left (DecodeError _) -> return ()+        _ -> assertFailure "decoding phase"+    , testCase "decodeHexUnpaddedWith valid utf8 inputs on decodeUtf8" $ do+      case decodeHexUnpaddedWith_ @a utf8 (encodeHexNopad @t "\1079743") of+        Left (ConversionError _) -> return ()+        _ -> assertFailure "conversion phase"+    ]+  , testGroup "decodeWith positive tests"+    [ testCase "decodeWith utf8 inputs on decodeUtf8" $ do+      a <- either (assertFailure . show) pure $ decode @a "MZXW6YTBOI======"+      b <- either (assertFailure . show) pure $ decodeWith_ @a utf8 "MZXW6YTBOI======"+      a @=? b+    , testCase "decodePaddedWith utf8 inputs on decodeUtf8" $ do+      a <- either (assertFailure . show) pure $ decodePad @a "MZXW6YTBOI======"+      b <- either (assertFailure . show) pure $ decodePaddedWith_ @a utf8 "MZXW6YTBOI======"+      a @=? b+    , testCase "decodeUnpaddedWith utf8 inputs on decodeUtf8" $ do+      a <- either (assertFailure . show) pure $ decodeNopad @a "MZXW6YTBOI"+      b <- either (assertFailure . show) pure $ decodeUnpaddedWith_ @a utf8 "MZXW6YTBOI"+      a @=? b+    , testCase "decodeHexWith utf8 inputs on decodeUtf8" $ do+      a <- either (assertFailure . show) pure $ decodeHex @a "CPNMUOJ1E8======"+      b <- either (assertFailure . show) pure $ decodeHexWith_ @a utf8 "CPNMUOJ1E8======"+      a @=? b+    , testCase "decodeHexPaddedWith utf8 inputs on decodeUtf8" $ do+      a <- either (assertFailure . show) pure $ decodeHexPad @a "CPNMUOJ1E8======"+      b <- either (assertFailure . show) pure $ decodeHexPaddedWith_ @a utf8 "CPNMUOJ1E8======"+      a @=? b+    , testCase "decodeHexUnpaddedWith utf8 inputs on decodeUtf8" $ do+      a <- either (assertFailure . show) pure $ decodeHexNopad @a "CPNMUOJ1"+      b <- either (assertFailure . show) pure $ decodeHexUnpaddedWith_ @a utf8 "CPNMUOJ1"+      a @=? b+    ]+  ]++-- | Validity unit tests for the URL workflow+--+validityTests :: forall a b proxy. Harness a b => proxy a -> TestTree+validityTests _ = testGroup "Validity and correctness unit tests"+  [ testGroup "Validity unit tests"+    [ testCase "Hex padding tests" $ do+      not (validateHex @a "C") @? "C"+      validateHex @a "CO" @? "CO"+      validateHex @a "CPNG" @? "CPNG"+      validateHex @a "CPNMU" @? "CPNMU"+      validateHex @a "CPNMUOG" @? "CPNMUOG"+      validateHex @a "CPNMUOJ1" @? "CPNMUOJ1"+      validateHex @a "CPNMUOJ1E8" @? "CPNMUOJ1E8"+      validateHex @a "CO======" @? "CO======"+      validateHex @a "CPNG====" @? "CPNG===="+      validateHex @a "CPNMU===" @? "CPNMU==="+      validateHex @a "CPNMUOG=" @? "CPNMUOG="+      validateHex @a "CPNMUOJ1" @? "CPNMUOJ1"+      validateHex @a "CPNMUOJ1E8======" @? "CPNMUOJ1E8======"+    , testCase "Std padding tests" $ do+      not (validate @a "M") @? "M"+      validate @a "MY" @? "MY"+      validate @a "MZXQ" @? "MZXQ"+      validate @a "MZXW6" @? "MZXW6"+      validate @a "MZXW6YQ" @? "MZXW6YQ"+      validate @a "MZXW6YTB" @? "MZXW6YTB"+      validate @a "MZXW6YTBOI" @? "MZXW6YTBOI"+      validate @a "MY======" @? "MY======"+      validate @a "MZXQ====" @? "MZXQ===="+      validate @a "MZXW6===" @? "MZXW6==="+      validate @a "MZXW6YQ=" @? "MZXW6YQ="+      validate @a "MZXW6YTB" @? "MZXW6YTB"+      validate @a "MZXW6YTBOI======" @? "MZXW6YTBOI======"+    ]+  , testGroup "Correctness unit tests"+    [ testCase "Hex tests" $ do+      not (correctHex @a "C") @? "C"+      correctHex @a "CO" @? "CO"+      correctHex @a "CPNG" @? "CPNG"+      correctHex @a "CPNMU" @? "CPNMU"+      correctHex @a "CPNMUOG" @? "CPNMUOG"+      correctHex @a "CPNMUOJ1" @? "CPNMUOJ1"+      correctHex @a "CPNMUOJ1E8" @? "CPNMUOJ1E8"+      correctHex @a "CO======" @? "CO======"+      correctHex @a "CPNG====" @? "CPNG===="+      correctHex @a "CPNMU===" @? "CPNMU==="+      correctHex @a "CPNMUOG=" @? "CPNMUOG="+      correctHex @a "CPNMUOJ1" @? "CPNMUOJ1"+      correctHex @a "CPNMUOJ1E8======" @? "CPNMUOJ1E8======"+    , testCase "Std tests" $ do+      not (correct @a "M") @? "M"+      correct @a "MY" @? "MY"+      correct @a "MZXQ" @? "MZXQ"+      correct @a "MZXW6" @? "MZXW6"+      correct @a "MZXW6YQ" @? "MZXW6YQ"+      correct @a "MZXW6YTB" @? "MZXW6YTB"+      correct @a "MZXW6YTBOI" @? "MZXW6YTBOI"+      correct @a "MY======" @? "MY======"+      correct @a "MZXQ====" @? "MZXQ===="+      correct @a "MZXW6===" @? "MZXW6==="+      correct @a "MZXW6YQ=" @? "MZXW6YQ="+      correct @a "MZXW6YTB" @? "MZXW6YTB"+      correct @a "MZXW6YTBOI======" @? "MZXW6YTBOI======"+    ]+  ]++-- | Offset test vectors. This stresses the invalid char + incorrect padding+-- offset error messages+--+offsetVectors :: forall a b proxy. Harness a b => proxy a -> TestTree+offsetVectors _ = testGroup "Offset tests"+  [ testGroup "Hex - Invalid padding"+    [ testCase "Invalid staggered padding" $ do+      decodeHex @a "=PNMUOJ1E8======" @?= Left "invalid padding at offset: 0"+      decodeHex @a "C=NMUOJ1E8======" @?= Left "invalid padding at offset: 1"+      decodeHex @a "CP=MUOJ1E8======" @?= Left "invalid padding at offset: 2"+      decodeHex @a "CPN=UOJ1E8======" @?= Left "invalid padding at offset: 3"+      decodeHex @a "CPNM=OJ1E8======" @?= Left "invalid padding at offset: 4"+      decodeHex @a "CPNMU=J1E8======" @?= Left "invalid padding at offset: 5"+      decodeHex @a "CPNMUO=1E8======" @?= Left "invalid padding at offset: 6"+      decodeHex @a "CPNMUOJ=E8======" @?= Left "invalid padding at offset: 7"+    , testCase "Invalid character coverage" $ do+      decodeHex @a "%PNMUOJ1E8======" @?= Left "invalid character at offset: 0"+      decodeHex @a "C%NMUOJ1E8======" @?= Left "invalid character at offset: 1"+      decodeHex @a "CP%MUOJ1E8======" @?= Left "invalid character at offset: 2"+      decodeHex @a "CPN%UOJ1E8======" @?= Left "invalid character at offset: 3"+      decodeHex @a "CPNM%OJ1E8======" @?= Left "invalid character at offset: 4"+      decodeHex @a "CPNMU%J1E8======" @?= Left "invalid character at offset: 5"+      decodeHex @a "CPNMUO%1E8======" @?= Left "invalid character at offset: 6"+      decodeHex @a "CPNMUOJ%E8======" @?= Left "invalid character at offset: 7"+    ]+  , testGroup "Std - Invalid padding"+    [ testCase "Invalid staggered padding" $ do+      decode @a "=ZXW6YTBOI======" @?= Left "invalid padding at offset: 0"+      decode @a "M=XW6YTBOI======" @?= Left "invalid padding at offset: 1"+      decode @a "MZ=W6YTBOI======" @?= Left "invalid padding at offset: 2"+      decode @a "MZX=6YTBOI======" @?= Left "invalid padding at offset: 3"+      decode @a "MZXW=YTBOI======" @?= Left "invalid padding at offset: 4"+      decode @a "MZXW6=TBOI======" @?= Left "invalid padding at offset: 5"+      decode @a "MZXW6Y=BOI======" @?= Left "invalid padding at offset: 6"+      decode @a "MZXW6YT=OI======" @?= Left "invalid padding at offset: 7"+    , testCase "Invalid character coverage" $ do+      decode @a "%ZXW6YTBOI======" @?= Left "invalid character at offset: 0"+      decode @a "M%XW6YTBOI======" @?= Left "invalid character at offset: 1"+      decode @a "MZ%W6YTBOI======" @?= Left "invalid character at offset: 2"+      decode @a "MZX%6YTBOI======" @?= Left "invalid character at offset: 3"+      decode @a "MZXW%YTBOI======" @?= Left "invalid character at offset: 4"+      decode @a "MZXW6%TBOI======" @?= Left "invalid character at offset: 5"+      decode @a "MZXW6Y%BOI======" @?= Left "invalid character at offset: 6"+      decode @a "MZXW6YT%OI======" @?= Left "invalid character at offset: 7"+    ]+  ]