module Data.ByteString.Base16
(
encode
, decode
) where
import Data.Bits ((.&.), shiftL, shiftR)
import Data.ByteString.Char8 (empty)
import Data.ByteString.Internal (ByteString(..), createAndTrim', unsafeCreate)
import Data.ByteString.Unsafe (unsafeIndex)
import Data.Word (Word8)
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Ptr (Ptr, minusPtr, plusPtr)
import Foreign.Storable (peek, poke)
import System.IO.Unsafe (unsafePerformIO)
digits :: ByteString
digits = "0123456789abcdef"
encode :: ByteString -> ByteString
encode (PS sfp soff slen)
| slen > maxBound `div` 2 = error "Data.ByteString.Base16.encode: input too large"
| otherwise = unsafeCreate (slen*2) $ \dptr ->
withForeignPtr sfp $ \sptr ->
enc (sptr `plusPtr` soff) dptr
where
enc sptr = go sptr where
e = sptr `plusPtr` slen
go s d | s == e = return ()
| otherwise = do
x <- peek8 s
poke d . unsafeIndex digits $ x `shiftR` 4
poke (d `plusPtr` 1) . unsafeIndex digits $ x .&. 0xf
go (s `plusPtr` 1) (d `plusPtr` 2)
decode :: ByteString -> (ByteString, ByteString)
decode (PS sfp soff slen) =
unsafePerformIO . createAndTrim' (slen `div` 2) $ \dptr ->
withForeignPtr sfp $ \sptr ->
dec (sptr `plusPtr` soff) dptr
where
dec sptr = go sptr where
e = sptr `plusPtr` if odd slen then slen 1 else slen
go s d | s == e = let len = e `minusPtr` sptr
in return (0, len `div` 2, ps sfp (soff+len) (slenlen))
| otherwise = do
let hex w
| w >= 48 && w <= 57 = w 48
| w >= 97 && w <= 102 = w 97 + 10
| w >= 65 && w <= 70 = w 65 + 10
| otherwise = 0xff
hi <- hex `fmap` peek8 s
lo <- hex `fmap` peek8 (s `plusPtr` 1)
if lo == 0xff || hi == 0xff
then let len = s `minusPtr` sptr
in return (0, len `div` 2, ps sfp (soff+len) (slenlen))
else do
poke d . fromIntegral $ lo + (hi `shiftL` 4)
go (s `plusPtr` 2) (d `plusPtr` 1)
peek8 :: Ptr Word8 -> IO Int
peek8 p = fromIntegral `fmap` peek p
ps :: ForeignPtr Word8 -> Int -> Int -> ByteString
ps fp off len
| len <= 0 = empty
| otherwise = PS fp off len