basen-bytestring (empty) → 0.1.0.0
raw patch · 11 files changed
+614/−0 lines, 11 filesdep +QuickCheckdep +basedep +basen-bytestringsetup-changed
Dependencies added: QuickCheck, base, basen-bytestring, bytestring
Files
- ChangeLog.md +3/−0
- LICENSE +30/−0
- README.md +31/−0
- Setup.hs +2/−0
- basen-bytestring.cabal +48/−0
- src/Data/ByteString/Base16.hs +76/−0
- src/Data/ByteString/Base32.hs +140/−0
- src/Data/ByteString/Base58.hs +1/−0
- src/Data/ByteString/Base64.hs +130/−0
- src/Data/ByteString/BaseN.hs +118/−0
- test/Properties.hs +35/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for basenstrings++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author William Fisher (c) 2018++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,31 @@+# basen-bytestring++[RFC-4648](https://tools.ietf.org/html/rfc4648) compliant base-n ByteStrings for+the common cases:++```+Data.ByteString.Base16+Data.ByteString.Base32+Data.ByteString.Base64+```++All with the same API:++```+module Data.ByteString.BaseN where++import qualified Data.ByteString.Char8 as B8++encode :: B8.ByteString -> B8.ByteString+decode :: B8.ByteString -> B8.ByteString++encodeAlphabet :: Enc -> B8.ByteString -> B8.ByteString+decodeAlphabet :: Enc -> B8.ByteString -> B8.ByteString+```++Currently `Data.ByteString.Base58` is not implemented although it is on the+roadmap.++The core `Data.ByteString.BaseN` contains all the common utilities for packing+bytes, processing ByteStrings, creating encodings, and encoding/decoding single+bytes.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ basen-bytestring.cabal view
@@ -0,0 +1,48 @@+name: basen-bytestring+version: 0.1.0.0+description: Base-N ByteStrings for base{16,32,58,64}+homepage: https://github.com/FilWisher/basen-bytestring#readme+bug-reports: https://github.com/FilWisher/basen-bytestring/issues+author: William Fisher+maintainer: williamsykesfisher@gmail.com+copyright: 2018 William Fisher+license: BSD3+license-file: LICENSE+build-type: Simple+cabal-version: >= 1.10++extra-source-files:+ ChangeLog.md+ README.md++source-repository head+ type: git+ location: https://github.com/FilWisher/basen-bytestring++library+ hs-source-dirs:+ src+ build-depends:+ base >=4.7 && <5+ , bytestring+ exposed-modules:+ Data.ByteString.Base16+ Data.ByteString.Base32+ Data.ByteString.Base58+ Data.ByteString.Base64+ other-modules:+ Data.ByteString.BaseN+ default-language: Haskell2010++test-suite basen-bytestring-test+ type: exitcode-stdio-1.0+ main-is: Properties.hs+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , basen-bytestring+ , bytestring+ , QuickCheck+ default-language: Haskell2010
+ src/Data/ByteString/Base16.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.ByteString.Base16+ ( encode+ , decode+ ) where++import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString.Internal as Internal++import Foreign.Storable (peek, poke)+import Foreign.Ptr (plusPtr, Ptr)+import System.IO.Unsafe (unsafePerformIO)++import Data.ByteString.BaseN+import Data.Bits (shiftL, shiftR, (.&.), (.|.))+import Data.Word++encode :: B8.ByteString -> B8.ByteString+encode = encodeAlphabet hexlower++decode :: B8.ByteString -> Either String B8.ByteString+decode = decodeAlphabet hexlower++encodeAlphabet :: Enc -> B8.ByteString -> B8.ByteString+encodeAlphabet enc src@(Internal.PS sfp soff slen) =+ unsafePerformIO $ byChunk 1 (slen * 2) onchunk onend src+ where+ encodeNibble :: (Word8 -> Word8) -> Ptr Word8 -> IO Word8+ encodeNibble fn p = encodeWord enc . fn <$> peek p++ onchunk :: Ptr Word8 -> Ptr Word8 -> IO Int+ onchunk sp dp = do+ poke dp =<< encodeNibble leftNibble sp+ poke (dp `plusPtr` 1) =<< encodeNibble rightNibble sp+ return 2+ + onend :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()+ onend sp dp rem = return ()++decodeAlphabet :: Enc -> B8.ByteString -> Either String B8.ByteString+decodeAlphabet enc src@(Internal.PS sfp soff slen)+ | slen == 0 = Right BS.empty+ | otherwise = + unsafePerformIO $ byChunkErr 2 dlen onchunk onend src+ where+ dlen = slen `div` 2++ onchunk :: Ptr Word8 -> Ptr Word8 -> IO (Either String Int)+ onchunk sp dp = do+ leftNibble <- decodeWord enc <$> peek sp+ rightNibble <- decodeWord enc <$> peek (sp `plusPtr` 1)+ case (.|.) <$> fmap (`shiftL` 4) leftNibble <*> rightNibble of+ Left err -> do+ l <- peek sp :: IO Word8+ r <- peek (sp `plusPtr` 1) :: IO Word8+ return $ Left $ "Invalid byte encountered. One of: " ++ B8.unpack (BS.pack [l,r])+ Right w -> do+ poke dp w+ return $ Right 1++ onend :: Ptr Word8 -> Ptr Word8 -> Int -> IO (Either String Int)+ onend sp dp rem = onchunk sp dp >> return (Right dlen)++leftNibble :: Word8 -> Word8+leftNibble n = n `shiftR` 4++rightNibble :: Word8 -> Word8+rightNibble n = 0x0F .&. n++hexlower :: Enc+hexlower = mkEnc "0123456789abcdef"++hexupper :: Enc+hexupper = mkEnc "0123456789ABCDEF"
+ src/Data/ByteString/Base32.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.ByteString.Base32+ ( encode+ , decode++ , encodeHex+ , decodeHex+ ) where++import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString as BS+import qualified Data.ByteString.Internal as Internal+import Data.ByteString.BaseN++import Data.Word+import Data.Bits (shiftL, shiftR, (.|.), (.&.))++import Foreign.Ptr (plusPtr, Ptr)+import Foreign.Storable (peek, poke)+import System.IO.Unsafe (unsafePerformIO)++pack5 :: Int -> Word8 -> Word64 -> Word64+pack5 off word buf = buf .|. (fromIntegral word `shiftL` (off * 5))++pack8x5 :: [Word8] -> Word64+pack8x5 bs = foldr (uncurry pack8) 0 (zip [4,3..0] bs)++unpack8x5 :: Word64 -> [Word8]+unpack8x5 w =+ map (`unpack8` w) [4,3..0]++unpack5 :: Int -> Word64 -> Word8+unpack5 off buf = fromIntegral $ buf `shiftR` (5 * off) .&. 0x1f++pack5x8 :: [Word8] -> Word64+pack5x8 bs = foldr (uncurry pack5) 0 (zip [7,6..0] bs)++unpack5x8 :: Word64 -> [Word8]+unpack5x8 bs =+ map (`unpack5` bs) [7,6..0]++padCeilN :: Int -> Int -> Int+padCeilN n x+ | remd == 0 = x+ | otherwise = (x - remd) + n+ where mask = n - 1+ remd = x .&. mask++encodeHex :: B8.ByteString -> B8.ByteString+encodeHex = encodeAlphabet alphabetHex++decodeHex :: B8.ByteString -> Either String B8.ByteString+decodeHex = decodeAlphabet alphabetHex++encode :: B8.ByteString -> B8.ByteString+encode = encodeAlphabet alphabet++decode :: B8.ByteString -> Either String B8.ByteString+decode = decodeAlphabet alphabet++encodeAlphabet :: Enc -> B8.ByteString -> B8.ByteString+encodeAlphabet enc src@(Internal.PS sfp soff slen) =+ unsafePerformIO $ byChunk 5 dlen onchunk onend src+ where+ (d, m) = (slen * 8) `divMod` 5+ dlen = padCeilN 8 (d + if m == 0 then 0 else 1)++ onchunk sp dp = do+ words <- unpack5x8 . pack8x5 <$> traverse peek (map (sp `plusPtr`) [0..4])+ pokeN dp 8 $ map (encodeWord enc) words+ return 8++ onend sp dp 0 = return ()+ onend sp dp rem = do+ words <- unpack5x8 . pack8x5 <$> sequence+ [ peek sp + , if rem > 1 then peek $ sp `plusPtr` 1 else return 0+ , if rem > 2 then peek $ sp `plusPtr` 2 else return 0+ , if rem > 3 then peek $ sp `plusPtr` 3 else return 0+ , if rem > 4 then peek $ sp `plusPtr` 4 else return 0+ ]+ let encoded = map (encodeWord enc) words+ poke dp (encoded !! 0)+ poke (dp `plusPtr` 1) (encoded !! 1)+ poke (dp `plusPtr` 2) (if rem < 2 then 0x3d else encoded !! 2)+ poke (dp `plusPtr` 3) (if rem < 2 then 0x3d else encoded !! 3)+ poke (dp `plusPtr` 4) (if rem < 3 then 0x3d else encoded !! 4)+ poke (dp `plusPtr` 5) (if rem < 4 then 0x3d else encoded !! 5)+ poke (dp `plusPtr` 6) (if rem < 4 then 0x3d else encoded !! 6)+ poke (dp `plusPtr` 7) (0x3d :: Word8)++alphabet :: Enc+alphabet = mkEnc "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"++alphabetHex :: Enc+alphabetHex = mkEnc "0123456789ABCDEFGHIJKLMNOPQRSTUV"++decodeAlphabet :: Enc -> B8.ByteString -> Either String B8.ByteString+decodeAlphabet enc src@(Internal.PS sfp soff slen)+ | slen `mod` 8 /= 0 = Left "ByteString wrong length for valid Base32 encoding"+ | slen == 0 = Right BS.empty+ | otherwise =+ unsafePerformIO $ byChunkErr 8 dlen onchunk onend src+ where+ (d, m) = (slen * 5) `divMod` 8+ dlen = d + if m == 0 then 0 else 1++ -- Maps number of padding bytes in source buffer to number of valid bytes+ -- to write to destination buffer.+ validBytes :: Int -> Either String Int+ validBytes 0 = Right 5+ validBytes 1 = Right 4+ validBytes 3 = Right 3+ validBytes 4 = Right 2+ validBytes 6 = Right 1+ validBytes n = Left $ "Invalid amount of padding: " ++ show n++ decodeBytes = mapM $ decodeWord enc++ onchunk :: Ptr Word8 -> Ptr Word8 -> IO (Either String Int)+ onchunk sp dp = do+ words <- traverse (peek . (sp `plusPtr`)) [0..7]+ case (unpack8x5 . pack5x8) <$> decodeBytes words of+ Left err -> return $ Left err+ Right decoded -> do+ pokeN dp 5 decoded+ return $ Right 5++ onend :: Ptr Word8 -> Ptr Word8 -> Int -> IO (Either String Int)+ onend sp dp rem = do+ words <- traverse (peek . (sp `plusPtr`)) [0..7]+ let+ npad = length $ takeWhile (==0x3d) $ reverse words+ decode = (unpack8x5 . pack5x8) <$> decodeBytes (take (8-npad) words)+ case (,) <$> validBytes npad <*> decode of+ Left err -> return $ Left err+ Right (n, decoded) -> do+ pokeN dp n decoded+ return $ Right $ dlen - (5 - n)
+ src/Data/ByteString/Base58.hs view
@@ -0,0 +1,1 @@+module Data.ByteString.Base58 where
+ src/Data/ByteString/Base64.hs view
@@ -0,0 +1,130 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.ByteString.Base64+ ( encode+ , decode+ + , encodeURL+ , decodeURL+ ) where++import Data.Word+import Data.Bits (shiftL, shiftR, (.&.), (.|.))++import qualified Data.ByteString.Internal as Internal+import qualified Data.ByteString.Char8 as B8+import Data.ByteString.BaseN++import Foreign.Ptr (plusPtr, Ptr)+import Foreign.ForeignPtr (withForeignPtr)+import Foreign.Storable (peek, poke)+import System.IO.Unsafe (unsafePerformIO)++-- Pack a Word32 with a 6-bits at offset `off`. Offset is counted from the right+-- so `pack6 word 0 byte` packs the right-most 6 bits.+pack6 :: Int -> Word8 -> Word32 -> Word32+pack6 off word buf = buf .|. (fromIntegral word `shiftL` (off * 6))++-- Unpack 6 bits from a Word32 at offset `off`. Offset is counted form the right+-- so `unpack6 word 0` unpacks the 6 right-most bits into a Word8.+unpack6 :: Int -> Word32 -> Word8+unpack6 off buf = fromIntegral $ buf `shiftR` (6 * off) .&. 0x3f++pack8x3 :: [Word8] -> Word32+pack8x3 bs = foldr (uncurry pack8) 0 (zip [2,1,0] bs)++unpack8x3 :: Word32 -> [Word8]+unpack8x3 w =+ map (`unpack8` w) [2,1,0]++pack6x4 :: [Word8] -> Word32+pack6x4 bs = foldr (uncurry pack6) 0 (zip [3,2..0] bs)++unpack6x4 :: Word32 -> [Word8]+unpack6x4 bs =+ map (`unpack6` bs) [3,2..0]++encode :: B8.ByteString -> B8.ByteString+encode = encodeAlphabet alphabet++decode :: B8.ByteString -> Either String B8.ByteString+decode = decodeAlphabet alphabet++encodeURL :: B8.ByteString -> B8.ByteString+encodeURL = encodeAlphabet alphabetURL++decodeURL :: B8.ByteString -> Either String B8.ByteString+decodeURL = decodeAlphabet alphabetURL++decodeAlphabet :: Enc -> B8.ByteString -> Either String B8.ByteString+decodeAlphabet enc src@(Internal.PS sfp soff slen)+ | drem /= 0 = Left "ByteString wrong length for valid Base64 encoding padding"+ | dlen <= 0 = Right B8.empty+ | otherwise = + unsafePerformIO $ byChunkErr 4 dlen onchunk onend src+ where+ (di, drem) = slen `divMod` 4+ dlen = di * 3++ onchunk :: Ptr Word8 -> Ptr Word8 -> IO (Either String Int)+ onchunk sp dp = do+ words <- traverse (peek . (sp `plusPtr`)) [0..3]+ case (unpack8x3 . pack6x4) <$> traverse (decodeWord enc) words of+ Left err -> return $ Left err+ Right decoded -> do+ pokeN dp 3 decoded+ return $ Right 3++ -- Maps number of padding bytes in source buffer to number of valid bytes+ -- to write to destination buffer.+ validBytes :: Int -> Either String Int+ validBytes 0 = Right 3+ validBytes 1 = Right 2+ validBytes 2 = Right 1+ validBytes n = Left $ "Invalid amount of padding: " ++ show n++ decodeBytes = mapM $ decodeWord enc++ onend :: Ptr Word8 -> Ptr Word8 -> Int -> IO (Either String Int)+ onend sp dp rem = do+ words <- traverse (peek . (sp `plusPtr`)) [0..3]+ let + npad = length $ takeWhile (==0x3d) $ reverse words+ decode = (unpack8x3 . pack6x4) <$> decodeBytes (take (4-npad) words)+ case (,) <$> validBytes npad <*> decode of+ Left err -> return $ Left err+ Right (n, decoded) -> do+ pokeN dp n decoded+ return $ Right $ dlen - (3 - n)+++alphabet :: Enc+alphabet = mkEnc "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"++alphabetURL :: Enc+alphabetURL = mkEnc "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"++encodeAlphabet :: Enc -> B8.ByteString -> B8.ByteString+encodeAlphabet enc src@(Internal.PS sfp soff slen) =+ unsafePerformIO $ byChunk 3 dlen onchunk onend src++ where+ dlen = ((slen + 2) `div` 3) * 4++ onchunk sp dp = do+ w <- unpack6x4 . pack8x3 <$> traverse (peek . (sp `plusPtr`)) [0..2]+ pokeN dp 4 $ map (encodeWord enc) w+ return 4++ onend sp dp 0 = return ()+ onend sp dp rem = do+ w <- (unpack6x4 . pack8x3) <$> traverse (peek . (sp `plusPtr`)) [0..rem-1]+ let encoded = map (encodeWord enc) w+ poke dp (encoded !! 0)+ poke (dp `plusPtr` 1) (encoded !! 1)+ poke (dp `plusPtr` 2) (if rem == 1 then 0x3d else encoded !! 2)+ poke (dp `plusPtr` 3) (0x3d :: Word8)++++
+ src/Data/ByteString/BaseN.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE OverloadedStrings #-}++module Data.ByteString.BaseN+ ( Enc(..)+ , mkEnc+ , encodeWord+ , decodeWord+ , pack8+ , unpack8+ , byChunk+ , byChunkErr+ , pokeN+ ) where++import qualified Data.ByteString.Internal as Internal+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString as BS++import Data.Word+import Data.Bits (shiftL, shiftR, (.|.), (.&.), Bits)+import Data.Maybe (fromMaybe)++import Foreign.Ptr (plusPtr, minusPtr, Ptr)+import Foreign.Storable (poke)+import Foreign.ForeignPtr (withForeignPtr)++pokeN :: Ptr Word8 -> Int -> [Word8] -> IO ()+pokeN dp n bs =+ mapM_ insert $ zip [0..n-1] bs+ where+ insert (off, v) = poke (dp `plusPtr` off) v++-- Pack a Word32 with a Word8 at offset `off`. Offset is counted from the right+-- so `pack8 word 0 byte` packs the word with the right-most byte:+pack8 :: (Num a, Bits a) => Int -> Word8 -> a -> a+pack8 off word buf = buf .|. (fromIntegral word `shiftL` (off * 8))++-- Unpack a byte from a Word32 at offset `off`. Offset is counted form the right+-- so `unpack8 word 0` unpacks the right-most byte.+unpack8 :: (Integral a, Bits a) => Int -> a -> Word8+unpack8 off buf = fromIntegral $ buf `shiftR` (8 * off) .&. 0xff++-- Enc represents an encoding. The first ByteString is the map from decoded+-- bytes to encoded bytes. The second ByteString is the map from encoded bytes+-- to decoded bytes.+--+-- The assumptions with Enc types is:+-- o That the encoded alphabet does not contain the byte 255 (it is used as a+-- sentinal)+-- o That the alphabet supplied to `mkEnc` is long enough for the encoding: no+-- bounds checking is done.+--+data Enc = Enc + { encmap :: B8.ByteString+ , decmap :: B8.ByteString+ }+ deriving (Show)++-- Produce an `Enc` from an encoding alphabet. The decoding alphabet is derived+-- from the encoding alphabet. Non-present characters are signified with the+-- byte 0xFF. This means 0xFF cannot be present in the encoding alphabet.+mkEnc :: B8.ByteString -> Enc+mkEnc table = Enc table $+ BS.pack + [ fromIntegral $ fromMaybe 0xFF (i `BS.elemIndex` table) + | i <- [0..254] + ]++encodeWord :: Enc -> Word8 -> Word8+encodeWord (Enc enc _) word = enc `BS.index` fromIntegral word++decodeWord :: Enc -> Word8 -> Either String Word8+decodeWord (Enc _ dec) word = case dec `BS.index` fromIntegral word of+ 255 -> Left ("Not valid encoding: " ++ show word)+ n -> Right n++byChunk :: + Int -> + Int ->+ (Ptr Word8 -> Ptr Word8 -> IO Int) -> + (Ptr Word8 -> Ptr Word8 -> Int -> IO ()) -> + B8.ByteString -> + IO B8.ByteString+byChunk chunksize dlen onchunk onend (Internal.PS sfp soff slen) =+ withForeignPtr sfp $ \sptr -> do+ let+ end = sptr `plusPtr` (soff + slen)+ loop sp dp+ | sp `plusPtr` (chunksize-1) >= end = onend sp dp (end `minusPtr` sp)+ | otherwise = do+ advance <- onchunk sp dp+ loop (sp `plusPtr` chunksize) (dp `plusPtr` advance)+ dfp <- Internal.mallocByteString dlen+ withForeignPtr dfp $ \dptr -> do+ loop sptr dptr+ return $ Internal.PS dfp 0 dlen++byChunkErr :: + Int ->+ Int ->+ (Ptr Word8 -> Ptr Word8 -> IO (Either String Int)) ->+ (Ptr Word8 -> Ptr Word8 -> Int -> IO (Either String Int)) ->+ B8.ByteString ->+ IO (Either String B8.ByteString)+byChunkErr chunksize dlen onchunk onend (Internal.PS sfp soff slen) =+ withForeignPtr sfp $ \sptr -> do+ let+ end = sptr `plusPtr` (soff + slen)+ loop sp dp+ | sp `plusPtr` chunksize > end = return $ Left "Not enough bytes remaining"+ | sp `plusPtr` chunksize == end = onend sp dp (end `minusPtr` sp)+ | otherwise = do+ advance <- onchunk sp dp+ case advance of+ Left err -> return $ Left err+ Right n -> loop (sp `plusPtr` chunksize) (dp `plusPtr` n)+ dfp <- Internal.mallocByteString dlen+ fmap (Internal.PS dfp 0) <$> withForeignPtr dfp (loop sptr)
+ test/Properties.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE TemplateHaskell #-}++import Test.QuickCheck (Arbitrary(..), choose, forAll)+import Test.QuickCheck.All (quickCheckAll)+import Control.Monad (void)+import Data.Word++import qualified Data.ByteString.Base64 as Base64+import qualified Data.ByteString.Base32 as Base32+import qualified Data.ByteString.Base16 as Base16+import qualified Data.ByteString.Char8 as B8++instance Arbitrary B8.ByteString where+ arbitrary = B8.pack <$> arbitrary++prop_base64_inverse :: B8.ByteString -> Bool+prop_base64_inverse bs =+ either (const False) (bs==)+ $ Base64.decode $ Base64.encode bs++prop_base16_inverse :: B8.ByteString -> Bool+prop_base16_inverse bs =+ either (const False) (bs==)+ $ Base16.decode $ Base16.encode bs++prop_base32_inverse :: B8.ByteString -> Bool+prop_base32_inverse bs =+ either (const False) (bs==)+ $ Base32.decode $ Base32.encode bs++return []+runTests = $quickCheckAll++main :: IO ()+main = void runTests