{-| Module : Z.Data.Vector.Hex Description : Hex codec for bytes. Copyright : (c) Dong Han, 2017-2018 License : BSD Maintainer : winterland1989@gmail.com Stability : experimental Portability : non-portable This module provides hex encoding & decoding tools, as well as 'HexBytes' newtype with hex textual instances. -} module Z.Data.Vector.Hex ( -- * The HexBytes type HexBytes(..) -- * Encoding & Decoding functions , hexEncode , hexEncodeText , hexEncodeBuilder , hexDecode , hexDecode' , hexDecodeWS , hexDecodeWS' , HexDecodeException(..) -- * Internal C FFIs , hs_hex_encode, hs_hex_encode_upper, hs_hex_decode ) where import Control.Exception import Data.Word import Data.Bits (unsafeShiftL, unsafeShiftR, (.&.)) import Data.Hashable (Hashable(..)) import GHC.Stack import System.IO.Unsafe import qualified Z.Data.Vector.Base as V import qualified Z.Data.Builder.Base as B import qualified Z.Data.Text.Base as T import qualified Z.Data.Text.Print as T import qualified Z.Data.JSON as JSON import Z.Foreign -- | New type wrapper for 'V.Bytes' with hex encoding(uppercase) Show\/JSON instances. newtype HexBytes = HexBytes { unHexBytes :: V.Bytes } deriving (Eq, Ord) deriving newtype (Monoid, Semigroup, Hashable) instance Show HexBytes where show (HexBytes bs) = T.unpack $ hexEncodeText True bs instance T.Print HexBytes where {-# INLINE toUTF8BuilderP #-} toUTF8BuilderP _ (HexBytes bs) = B.quotes (hexEncodeBuilder True bs) instance JSON.JSON HexBytes where {-# INLINE fromValue #-} fromValue = JSON.withText "Z.Data.Text.HexBytes" $ \ t -> case hexDecode (T.getUTF8Bytes t) of Just bs -> return (HexBytes bs) Nothing -> JSON.fail' "illegal hex encoding bytes" {-# INLINE toValue #-} toValue (HexBytes bs) = JSON.String (hexEncodeText True bs) {-# INLINE encodeJSON #-} encodeJSON (HexBytes bs) = hexEncodeBuilder True bs -- | Encode 'V.Bytes' using hex(base16) encoding. hexEncode :: Bool -- ^ uppercase? -> V.Bytes -> V.Bytes {-# INLINE hexEncode #-} hexEncode upper (V.PrimVector arr s l) = fst . unsafeDupablePerformIO $ do allocPrimVectorUnsafe (l `unsafeShiftL` 1) $ \ buf# -> withPrimArrayUnsafe arr $ \ parr _ -> if upper then hs_hex_encode_upper buf# 0 parr s l else hs_hex_encode buf# 0 parr s l -- | 'B.Builder' version of 'hexEncode'. hexEncodeBuilder :: Bool -- ^ uppercase? -> V.Bytes -> B.Builder () {-# INLINE hexEncodeBuilder #-} hexEncodeBuilder upper (V.PrimVector arr s l) = B.writeN (l `unsafeShiftL` 1) (\ (MutablePrimArray mba#) i -> do withPrimArrayUnsafe arr $ \ parr _ -> if upper then hs_hex_encode_upper mba# i parr s l else hs_hex_encode mba# i parr s l) -- | Text version of 'hexEncode'. hexEncodeText :: Bool -- ^ uppercase? -> V.Bytes -> T.Text {-# INLINE hexEncodeText #-} hexEncodeText upper = T.Text . hexEncode upper -- | Decode a hex encoding string, return Nothing on illegal bytes or incomplete input. hexDecode :: V.Bytes -> Maybe V.Bytes {-# INLINABLE hexDecode #-} hexDecode ba | V.length ba == 0 = Just V.empty | V.length ba .&. 1 == 1 = Nothing | otherwise = unsafeDupablePerformIO $ do (arr, r) <- withPrimVectorUnsafe ba $ \ ba# s l -> allocPrimArrayUnsafe (l `unsafeShiftR` 1) $ \ buf# -> hs_hex_decode buf# ba# s l if r < 0 then return Nothing else return (Just (V.PrimVector arr 0 r)) -- | Decode a hex encoding string, ignore ASCII whitespace(space, tab, newline, vertical tab, form feed, carriage return). -- -- This is useful when you get some hex nibbles by pasting from web, note only whitesapces between bytes(two nibbles) are allowed: -- -- >>> hexDecodeWS "6f7481 da0e53" -- Just [111,116,129,218,14,83] -- >>> hexDecodeWS "6f7481d a0e53" -- Nothing -- hexDecodeWS :: V.Bytes -> Maybe V.Bytes {-# INLINABLE hexDecodeWS #-} hexDecodeWS ba | V.length ba == 0 = Just V.empty | otherwise = unsafeDupablePerformIO $ do (arr, r) <- withPrimVectorUnsafe ba $ \ ba# s l -> allocPrimArrayUnsafe (l `unsafeShiftR` 1) $ \ buf# -> hs_hex_decode_ws buf# ba# s l if r < 0 then return Nothing else return (Just (V.PrimVector arr 0 r)) -- | Exception during hex decoding. data HexDecodeException = IllegalHexBytes V.Bytes CallStack | IncompleteHexBytes V.Bytes CallStack deriving Show instance Exception HexDecodeException -- | Decode a hex encoding string, throw 'HexDecodeException' on error. hexDecode' :: HasCallStack => V.Bytes -> V.Bytes {-# INLINABLE hexDecode' #-} hexDecode' ba = case hexDecode ba of Just r -> r _ -> throw (IllegalHexBytes ba callStack) -- | Decode a hex encoding string, ignore ASCII whitespace(space, tab, newline, vertical tab, form feed, carriage return), throw 'HexDecodeException' on error. hexDecodeWS' :: HasCallStack => V.Bytes -> V.Bytes {-# INLINABLE hexDecodeWS' #-} hexDecodeWS' ba = case hexDecodeWS ba of Just r -> r _ -> throw (IllegalHexBytes ba callStack) -------------------------------------------------------------------------------- foreign import ccall unsafe hs_hex_encode :: MBA# Word8 -> Int -> BA# Word8 -> Int -> Int -> IO () foreign import ccall unsafe hs_hex_encode_upper :: MBA# Word8 -> Int -> BA# Word8 -> Int -> Int -> IO () foreign import ccall unsafe hs_hex_decode :: MBA# Word8 -> BA# Word8 -> Int -> Int -> IO Int foreign import ccall unsafe hs_hex_decode_ws :: MBA# Word8 -> BA# Word8 -> Int -> Int -> IO Int