-- | Misc. shared constants and functions.
module Crypto.Threefish.Common (
    Tweak (..), showBytes, keyConst, e2m, defaultTweak, parseHex, readHex
  ) where
import Numeric (showHex)
import Data.Word
import Data.Bits
import Data.Serialize
import Control.Applicative
import qualified Data.ByteString as BS
import Data.Char
import Data.Default

hexDigit :: Char -> Maybe Word8
hexDigit c | c >= '0' && c <= '9' = Just $ fromIntegral $ ord c - ord '0'
           | c >= 'a' && c <= 'f' = Just $ fromIntegral $ ord c - ord 'a' + 10
           | c >= 'A' && c <= 'F' = Just $ fromIntegral $ ord c - ord 'A' + 10
           | otherwise            = Nothing

-- | Parses a string of hexadecimal digits into a ByteString.
parseHex :: String -> Maybe BS.ByteString
parseHex = go []
  where
    go :: [Word8] -> String -> Maybe BS.ByteString
    go bytes (h:l:xs) = do
      h' <- hexDigit h
      l' <- hexDigit l
      go (((h' `shiftL` 4) .|. l') : bytes) xs
    go bytes [] =
      Just (BS.pack (reverse bytes))
    go _ _ =
      Nothing

-- | Show a little endian Word64 as a string of bytes.
showBytes :: Word64 -> String
showBytes =
    go (8 :: Int)
  where
    sr = shiftR
    go 0 _ = ""
    go n w = showHex ((w`sr`4).&.15) $ showHex (w.&.15) $ go (n-1) (w`sr`8)

-- | Key constant for Threefish.
keyConst :: Word64
keyConst = 0x1BD11BDAA9FC1A22

-- | Default tweak when Threefish is used in CBC, CTR, etc. modes.
defaultTweak :: Tweak
defaultTweak = Tweak 0 0

-- | Turn an Either computation into its Maybe counterpart.
e2m :: Either a b -> Maybe b
e2m (Right x) = Just x
e2m _         = Nothing

-- | Threefish tweak value. Please see the Skein specification for info on
--   how to use this.
data Tweak = Tweak {-# UNPACK #-} !Word64
                   {-# UNPACK #-} !Word64

instance Serialize Tweak where
  put (Tweak low high) = putWord64le low >> putWord64le high
  get = Tweak <$> getWord64le <*> getWord64le

instance Show Tweak where
  show (Tweak low high) = showBytes low ++ showBytes high

instance Default Tweak where
  def = defaultTweak

-- | Read any deserializable type from a hex string.
readHex :: Serialize a => String -> Maybe a
readHex s = parseHex s >>= e2m . decode