{-# LINE 1 "Data/Digest/BCrypt.hsc" #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LINE 2 "Data/Digest/BCrypt.hsc" #-}

module Data.Digest.BCrypt
    ( bcrypt
    , genSalt
    , packBSalt
    , BSalt
    )
where


{-# LINE 12 "Data/Digest/BCrypt.hsc" #-}

{-# LINE 13 "Data/Digest/BCrypt.hsc" #-}

{-# LINE 14 "Data/Digest/BCrypt.hsc" #-}

import Foreign
import Foreign.C.Types
import Foreign.C.String
import qualified Foreign.Ptr ( nullPtr )
import qualified System.IO.Unsafe ( unsafePerformIO )
import Data.ByteString.Char8 (split)
import qualified Data.ByteString.Unsafe as B
import qualified Data.ByteString.Internal as B ( fromForeignPtr
                                               , c_strlen
                                               )
import qualified Data.ByteString as B


-- | BCrypt salt for passing to bcrypt.
newtype BSalt = BSalt { -- | Deconstruct a BSalt to a bytestring
                        unBSalt::B.ByteString
                      } deriving (Eq, Ord, Show)

-- | Given a bytestring, construct a BSalt type, with some minimal checking
packBSalt :: B.ByteString   -- ^ Bytestring version of a BSalt
             -> Maybe BSalt -- ^ BSalt if the salt string validated
packBSalt s = do
    let unpacked = split '$' s in
        case unpacked of
          _:version:rounds:hashed:xs ->
            let vlen = B.length version
                rlen = B.length rounds
                hlen = B.length hashed in
            if and $ (vlen >= 2) : (rlen >= 1) : [hlen == 53]
            then Just $ BSalt s
            else Nothing
          _ -> Nothing

-- | Given a cost from 4-32 and a random seed of 16 bytes generate a salt.
-- Seed should be 16 bytes from a secure random generator
genSalt :: Integer         -- ^ Compute cost
           -> B.ByteString -- ^ 16 byte secure random seed
           -> Maybe BSalt  -- ^ Returned salt or Nothing
genSalt cost seed
       | B.length seed /= 16 = Nothing
       | otherwise = unsafePerformIO $
        B.useAsCString seed $ \s ->
             allocaBytes 30 $ \out -> do
{-# LINE 58 "Data/Digest/BCrypt.hsc" #-}
                 let seed' = (fromIntegral cost::CInt)
                 bsalt <- c_bcrypt_gensalt out seed' (castPtr s)
                 result <- B.packCString bsalt
                 return $ Just $ BSalt result

-- | Hash a password based on a BSalt with a given cost
bcrypt :: B.ByteString -- ^ Data to hash
          -> BSalt     -- ^ salt generated by genSalt
          -> B.ByteString
bcrypt key (BSalt salt) = unsafePerformIO $
       B.useAsCString key $ \k -> B.useAsCString salt $ \s ->
           allocaBytes 128 $ \out -> do
{-# LINE 70 "Data/Digest/BCrypt.hsc" #-}
               cptr <- c_bcrypt out k s
               if cptr == nullPtr -- On error, returns NULL
                 then return B.empty
                 else B.packCString cptr

foreign import ccall unsafe "bcrypt.h bcrypt_gensalt"
    c_bcrypt_gensalt :: CString -> CInt -> Ptr Word8 -> IO CString

foreign import ccall unsafe "bcrypt.h bcrypt"
    c_bcrypt :: CString -> CString -> CString -> IO CString