{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : Data.ByteString.Base16.Lazy
-- Copyright   : (c) 2011 MailRank, Inc.
--
-- License     : BSD
-- Maintainer  : bos@serpentine.com
-- Stability   : experimental
-- Portability : GHC
--
-- Fast and efficient encoding and decoding of base16-encoded strings.

module Data.ByteString.Base16.Lazy
    (
      encode
    , decode
    ) where

import Data.Word (Word8)
import qualified Data.ByteString.Base16 as B16
import qualified Data.ByteString as B
import qualified Data.ByteString.Unsafe as B
import Data.ByteString.Lazy.Internal

-- | Encode a string into base16 form.  The result will always be a
-- multiple of 2 bytes in length.
--
-- Example:
--
-- > encode "foo"  == "666f6f"
encode :: ByteString -> ByteString
encode :: ByteString -> ByteString
encode (Chunk ByteString
c ByteString
cs) = ByteString -> ByteString -> ByteString
Chunk (ByteString -> ByteString
B16.encode ByteString
c) (ByteString -> ByteString
encode ByteString
cs)
encode ByteString
Empty        = ByteString
Empty

-- | Decode a string from base16 form. The first element of the
-- returned tuple contains the decoded data. The second element starts
-- at the first invalid base16 sequence in the original string.
--
-- This function operates as lazily as possible over the input chunks.
-- The only instance in which it is non-lazy is if an odd-length chunk
-- ends with a byte that is valid base16.
--
-- Examples:
--
-- > decode "666f6f"  == ("foo", "")
-- > decode "66quux"  == ("f", "quux")
-- > decode "666quux" == ("f", "6quux")
decode :: ByteString -> (ByteString, ByteString)
decode :: ByteString -> (ByteString, ByteString)
decode = (ByteString
 -> (ByteString, ByteString) -> (ByteString, ByteString))
-> (ByteString, ByteString)
-> ByteString
-> (ByteString, ByteString)
forall a. (ByteString -> a -> a) -> a -> ByteString -> a
foldrChunks ByteString -> (ByteString, ByteString) -> (ByteString, ByteString)
go (ByteString
Empty, ByteString
Empty)
  where go :: ByteString -> (ByteString, ByteString) -> (ByteString, ByteString)
go ByteString
c ~(ByteString
y,ByteString
z)
           | Int
len Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = (ByteString -> ByteString -> ByteString
chunk ByteString
h ByteString
y, ByteString
z)
           | Int
len Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 Bool -> Bool -> Bool
&& Word8 -> Bool
isHex (ByteString -> Word8
B.unsafeHead ByteString
t) =
               case ByteString
z of
                 Chunk ByteString
a ByteString
as | Word8 -> Bool
isHex (ByteString -> Word8
B.unsafeHead ByteString
a)
                   -> let (ByteString
q,ByteString
_) = ByteString -> (ByteString, ByteString)
B16.decode (ByteString
t ByteString -> Word8 -> ByteString
`B.snoc` ByteString -> Word8
B.unsafeHead ByteString
a)
                      in (ByteString -> ByteString -> ByteString
chunk ByteString
h (ByteString -> ByteString -> ByteString
chunk ByteString
q ByteString
y), ByteString -> ByteString -> ByteString
chunk (ByteString -> ByteString
B.unsafeTail ByteString
a) ByteString
as)
                 ByteString
_ -> (ByteString -> ByteString -> ByteString
chunk ByteString
h ByteString
y, ByteString -> ByteString -> ByteString
chunk ByteString
t ByteString
z)
           | Bool
otherwise = (ByteString -> ByteString -> ByteString
chunk ByteString
h ByteString
y, ByteString -> ByteString -> ByteString
chunk ByteString
t ByteString
z)
            where (ByteString
h,ByteString
t) = ByteString -> (ByteString, ByteString)
B16.decode ByteString
c
                  len :: Int
len = ByteString -> Int
B.length ByteString
t

isHex :: Word8 -> Bool
isHex :: Word8 -> Bool
isHex Word8
w = (Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
48 Bool -> Bool -> Bool
&& Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
57) Bool -> Bool -> Bool
|| (Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
97 Bool -> Bool -> Bool
&& Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
102) Bool -> Bool -> Bool
|| (Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
65 Bool -> Bool -> Bool
&& Word8
w Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
70)