base64-bytes 0.1.0.0 → 0.1.1.0
raw patch · 5 files changed
+128/−10 lines, 5 filesdep +word-compatdep −small-bytearray-builderdep ~bytebuilddep ~primitivePVP ok
version bump matches the API change (PVP)
Dependencies added: word-compat
Dependencies removed: small-bytearray-builder
Dependency ranges changed: bytebuild, primitive
API changes (from Hackage documentation)
+ Data.Bytes.Base64: decode64 :: Bytes -> Maybe Word64
+ Data.Bytes.Base64.Url: decode64 :: Bytes -> Maybe Word64
Files
- CHANGELOG.md +6/−0
- base64-bytes.cabal +4/−2
- src/Data/Bytes/Base64.hs +40/−4
- src/Data/Bytes/Base64/Url.hs +47/−0
- test/Main.hs +31/−4
CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for base64-bytes +## 0.1.1.0 -- 2022-12-07++* Support GHC 9.2+* Add `decode64`+* Decode base64-url bytes to Word64+ ## 0.1.0.0 -- 2020-06-01 * Initial release.
base64-bytes.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: base64-bytes-version: 0.1.0.0+version: 0.1.1.0 synopsis: Base64 encoding of byte sequences homepage: https://github.com/andrewthad/base64-bytes bug-reports: https://github.com/andrewthad/base64-bytes/issues@@ -16,6 +16,7 @@ library exposed-modules: Data.Bytes.Base64+ Data.Bytes.Base64.Url build-depends: , base >=4.12 && <5 , primitive >=0.7 && <0.8@@ -24,6 +25,7 @@ , run-st >=0.1 && <0.2 , bytebuild >=0.3.4 && <0.4 , natural-arithmetic >=0.1.1 && <0.2+ , word-compat >=0.0.4 hs-source-dirs: src default-language: Haskell2010 ghc-options: -Wall -O2@@ -37,7 +39,7 @@ , base , base64-bytes , natural-arithmetic >=0.1.1- , small-bytearray-builder+ , bytebuild , primitive , byteslice >=0.1.4.0 ghc-options: -O2 -Wall
src/Data/Bytes/Base64.hs view
@@ -1,11 +1,12 @@+{-# language BangPatterns #-} {-# language BinaryLiterals #-} {-# language BlockArguments #-} {-# language DataKinds #-}+{-# language ExplicitNamespaces #-} {-# language MagicHash #-} {-# language NoStarIsType #-}-{-# language BangPatterns #-}+{-# language PatternSynonyms #-} {-# language ScopedTypeVariables #-}-{-# language ExplicitNamespaces #-} {-# language TypeApplications #-} {-# language TypeOperators #-} {-# language UnboxedTuples #-}@@ -14,6 +15,7 @@ ( encode , builder , recodeBoundedBuilder+ , decode64 ) where import GHC.TypeNats (type (+),type (*),Div)@@ -24,17 +26,20 @@ import Data.Primitive (ByteArray(..),MutableByteArray(..)) import Data.Primitive (newByteArray,unsafeFreezeByteArray,readByteArray) import Data.Primitive.Ptr (indexOffPtr)-import Data.Word (Word8)+import Data.Word (Word8,Word64) import GHC.Exts (Ptr(Ptr),Int(I#),State#,(+#),(-#)) import GHC.ST (ST(ST))-import GHC.Word (Word(W#),Word32(W32#))+import GHC.Word (Word(W#),Word32)+import GHC.Word.Compat (pattern W32#) import qualified Arithmetic.Nat as Nat import qualified Arithmetic.Types as Arithmetic+import qualified Data.Bytes as Bytes import qualified Data.Bytes.Builder.Unsafe as BU import qualified Data.Bytes.Builder.Bounded.Unsafe as BBU import qualified Data.Primitive.ByteArray.BigEndian as BE import qualified Data.Primitive.ByteArray.LittleEndian as LE+import qualified Data.Primitive.Ptr as PM import qualified GHC.Exts as Exts -- | Encode a byte sequence with base64.@@ -211,3 +216,34 @@ unST :: ST s a -> State# s -> (# State# s, a #) unST (ST f) s = f s++-- Decode a base64-url-encoded 64-bit word. Rejects encoded numbers+-- greater than or equal to @2^64@. This maps the rightmost byte to+-- the 6 least significant bits of the word.+decode64 :: Bytes -> Maybe Word64+decode64 bs+ | Bytes.length bs > 10 = Nothing+ | otherwise = Bytes.foldlM+ (\ !(acc :: Word64) b -> case PM.indexOffPtr decodeTable (fromIntegral @Word8 @Int b) of+ 0xFF -> Nothing+ w -> pure $! (unsafeShiftL acc 6 .|. fromIntegral @Word8 @Word64 w)+ ) 0 bs++decodeTable :: Ptr Word8+decodeTable = Ptr+ "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3e\xff\xff\xff\x3f\+ \\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\xff\xff\xff\x63\xff\xff\+ \\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\+ \\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xff\xff\xff\xff\xff\+ \\xff\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\+ \\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
+ src/Data/Bytes/Base64/Url.hs view
@@ -0,0 +1,47 @@+{-# language BangPatterns #-}+{-# language MagicHash #-}+{-# language ScopedTypeVariables #-}+{-# language TypeApplications #-}++module Data.Bytes.Base64.Url+ ( decode64+ ) where++import Data.Bytes (Bytes)+import GHC.Exts (Ptr(Ptr))+import Data.Word (Word8,Word64)+import Data.Bits (unsafeShiftL,(.|.))++import qualified Data.Bytes as Bytes+import qualified Data.Primitive.Ptr as PM++-- Decode a base64-url-encoded 64-bit word. Rejects encoded numbers+-- greater than or equal to @2^64@. This maps the rightmost byte to+-- the 6 least significant bits of the word. +decode64 :: Bytes -> Maybe Word64+decode64 bs+ | Bytes.length bs > 10 = Nothing+ | otherwise = Bytes.foldlM+ (\ !(acc :: Word64) b -> case PM.indexOffPtr table (fromIntegral @Word8 @Int b) of+ 0xFF -> Nothing+ w -> pure $! (unsafeShiftL acc 6 .|. fromIntegral @Word8 @Word64 w)+ ) 0 bs++table :: Ptr Word8+table = Ptr+ "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x3e\xff\xff\+ \\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\xff\xff\xff\x63\xff\xff\+ \\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\+ \\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xff\xff\xff\xff\x3f\+ \\xff\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\+ \\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\+ \\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"#
test/Main.hs view
@@ -1,19 +1,25 @@ {-# language BangPatterns #-}+{-# language LambdaCase #-} import Control.Monad (when)+import Data.Char (ord) import Data.Primitive (ByteArray) import Data.Word (Word8) import Numeric (showHex) import qualified Arithmetic.Nat as Nat-import qualified Data.ByteArray.Builder.Bounded as BB+import qualified Data.Bytes.Builder.Bounded as BB import qualified Data.Bytes as Bytes+import qualified Data.Bytes.Text.Latin1 as Latin1 import qualified Data.Primitive as PM import qualified GHC.Exts as Exts import qualified Data.Bytes.Base64 as Base64+import qualified Data.Bytes.Base64.Url as Base64Url main :: IO () main = do+ putStr "Base 64 Encoding\n"+ putStr "================\n" putStr "Encoding: foobar\n" putStr "Expected: 5a6d3976596d4679 (Zm9vYmFy)\n" putStr "Got: "@@ -34,6 +40,24 @@ putStr "Got: " printHex actualNumbers when (actualNumbers /= expectedNumbers) (fail "Did not match")+ putStr "Base 64 URL Decoding\n"+ putStr "====================\n"+ putStr "Decoding: _\n"+ putStr "Expected: 63\n"+ underscoreDecodeResult <- case Base64Url.decode64 (Bytes.singleton (c2w '_')) of+ Nothing -> fail "Could not decode"+ Just w -> pure w+ putStr ("Got: " ++ show underscoreDecodeResult ++ "\n")+ when (underscoreDecodeResult /= 63) $+ (fail "Base64 URL decode of single underscore failed")+ putStr "Decoding: _-a\n"+ putStr "Expected: 262042\n"+ urlResB <- case Base64Url.decode64 (Latin1.fromString "_-a") of+ Nothing -> fail "Could not decode"+ Just w -> pure w+ putStr ("Got: " ++ show urlResB ++ "\n")+ when (urlResB /= 262042) $+ (fail "Base64 URL decode of _-a failed") putStr "\nAll tests succeeded!\n" printHex :: ByteArray -> IO ()@@ -46,13 +70,13 @@ else "\n" actualFoobar :: ByteArray-actualFoobar = Base64.encode (Bytes.fromAsciiString "foobar")+actualFoobar = Base64.encode (Latin1.fromString "foobar") actualHelloworld :: ByteArray-actualHelloworld = Base64.encode (Bytes.fromAsciiString "helloworld")+actualHelloworld = Base64.encode (Latin1.fromString "helloworld") actualCamel :: ByteArray-actualCamel = Base64.encode (Bytes.fromAsciiString "camel")+actualCamel = Base64.encode (Latin1.fromString "camel") actualNumbers :: ByteArray actualNumbers = BB.run Nat.constant@@ -78,3 +102,6 @@ [0x4d,0x54,0x49,0x7a,0x4c,0x6a ,0x59,0x33,0x4f,0x44,0x6b,0x3d ]++c2w :: Char -> Word8+c2w = fromIntegral . ord