module Bitcoin.Misc.HexString where
import Data.Array
import Data.Char
import Data.Int
import Data.Word
import Data.Bits
import Bitcoin.Misc.OctetStream
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])
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
hexDecode :: HexString -> [Word8]
hexDecode (HexString s) = case safeHexDecode s of
Just ws -> ws
Nothing -> error "hexDecode: input is not a hex string"
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"