bitcoin-hs-0.0.1: Bitcoin/Misc/HexString.hs
-- | Encoding and decoding hex strings
{-# LANGUAGE EmptyDataDecls #-}
module Bitcoin.Misc.HexString where
--------------------------------------------------------------------------------
import Data.Array
import Data.Char
import Data.Int
import Data.Word
import Data.Bits
-- import Control.Monad ( liftM )
import Bitcoin.Misc.OctetStream
--------------------------------------------------------------------------------
-- | The phantom type is used to encode endianness
newtype HexString = HexString { unHexString :: String } deriving (Eq,Show)
--------------------------------------------------------------------------------
toHexString :: OctetStream a => a -> HexString
toHexString = toHexString' False
toHexString' :: OctetStream a => Bool -> a -> HexString
toHexString' uppercase x = hexEncode' uppercase (toWord8List x)
toHexStringChars :: OctetStream a => a -> String
toHexStringChars = unHexString . toHexString
fromHexString :: OctetStream a => HexString -> a
fromHexString = fromWord8List . hexDecode
--------------------------------------------------------------------------------
reverseHexString :: HexString -> HexString
reverseHexString = HexString . unsafeReverseHexString . unHexString
unsafeReverseHexString :: String -> String
unsafeReverseHexString = fromPairs . reverse . toPairs where
toPairs :: [Char] -> [(Char,Char)]
toPairs (x:y:rest) = (x,y) : toPairs rest
toPairs [] = []
toPairs _ = error "unsafeReverseHexString/toPairs: odd number of characters"
fromPairs :: [(Char,Char)] -> [Char]
fromPairs = concatMap (\(x,y) -> [x,y])
--------------------------------------------------------------------------------
-- * encoding and decoding ByteStrings as (little-endian) hex strings
safeHexDecode :: String -> Maybe [Word8]
safeHexDecode s = if even (length s) && all isHexDigit s then Just (go s) else Nothing where
go (x:y:rest) = (shiftL (f x) 4 + f y) : go rest
go [] = []
go [x] = error "hexDecode: expecting even number of characters"
f :: Char -> Word8
f c | c >= '0' && c <= '9' = ordW c - 48
| c >= 'A' && c <= 'F' = ordW c - 65 + 10
| c >= 'a' && c <= 'f' = ordW c - 97 + 10
| otherwise = error "hexDecode: unexpected character"
ordW :: Char -> Word8
ordW = fromIntegral . ord
{-
-- already implemented in Data.Char
isHexDigit :: Char -> Bool
isHexDigit c = (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F')
-}
--------------------------------------------------------------------------------
-- | from "4142" to [0xAB]
hexDecode :: HexString -> [Word8]
hexDecode (HexString s) = case safeHexDecode s of
Just ws -> ws
Nothing -> error "hexDecode: input is not a hex string"
-- | from [0xAB] to "4142"
hexEncode :: [Word8] -> HexString
hexEncode = hexEncode' False
hexEncode' :: Bool -> [Word8] -> HexString
hexEncode' useCapitalLetters = HexString . concatMap worker where
worker :: Word8 -> String
worker w = [ table ! (fromIntegral $ shiftR w 4) , table ! (fromIntegral $ w .&. 15) ]
table = if useCapitalLetters then capitalHexTable else smallHexTable
showHexWord8 :: Word8 -> String
showHexWord8 w = [ smallHexTable ! (fromIntegral $ shiftR w 4) , smallHexTable ! (fromIntegral $ w .&. 15) ]
capitalHexTable, smallHexTable :: Array Word8 Char
capitalHexTable = listArray (0,15) "0123456789ABCDEF"
smallHexTable = listArray (0,15) "0123456789abcdef"
--------------------------------------------------------------------------------