-- | SHA256 hash: wrapper around Aaron D. Gifford's C implementation.
-- 

{-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls #-}
module Bitcoin.Crypto.Hash.SHA256 
  ( SHA256(..)
  , sha256
  ) 
  where

--------------------------------------------------------------------------------

import Data.Char
import Data.Int
import Data.Word
import Data.Bits

import qualified Data.ByteString as B

import Control.Monad
import Foreign
import Foreign.C
import System.IO.Unsafe as Unsafe

import Bitcoin.Misc.OctetStream
import Bitcoin.Misc.HexString

--------------------------------------------------------------------------------

data SHA256_CTX

-- typedef struct _SHA256_CTX {
--  uint32_t state[8];
--  uint64_t sbitcount;
--  uint8_t  buffer[SHA256_BLOCK_LENGTH];
-- } SHA256_CTX;
--
instance Storable SHA256_CTX where
  alignment _ = 8
  sizeOf _    = 4*8 + 8 + 64 
  peek = error "SHA256_CTX/peek: not implemented"
  poke = error "SHA256_CTX/poke: not implemented"

--------------------------------------------------------------------------------

-- void SHA256_Init(SHA256_CTX *);
foreign import ccall safe "sha2.h SHA256_Init" c_SHA256_Init :: Ptr SHA256_CTX -> IO ()

-- void SHA256_Update(SHA256_CTX*, const uint8_t*, size_t);
foreign import ccall safe "sha2.h SHA256_Update" c_SHA256_Update :: Ptr SHA256_CTX -> Ptr Word8 -> CSize -> IO ()

-- void SHA256_Final(uint8_t[SHA256_DIGEST_LENGTH], SHA256_CTX*);
foreign import ccall safe "sha2.h SHA256_Final" c_SHA256_Final :: Ptr Word8 -> Ptr SHA256_CTX -> IO ()

-- char* SHA256_End(SHA256_CTX*, char[SHA256_DIGEST_STRING_LENGTH]);
foreign import ccall safe "sha2.h SHA256_End" c_SHA256_End :: Ptr SHA256_CTX -> Ptr Word8 -> IO (Ptr CChar)

-- char* SHA256_Data(const uint8_t*, size_t, char[SHA256_DIGEST_STRING_LENGTH]);
foreign import ccall safe "sha2.h SHA256_Data" c_SHA256_Data :: Ptr Word8 -> CSize -> Ptr SHA256_CTX -> IO (Ptr CChar)

--------------------------------------------------------------------------------

newtype SHA256 = SHA256 { unSHA256 :: B.ByteString } deriving (Eq,Ord)

instance Show SHA256 where show (SHA256 bs) = "SHA256<" ++ toHexStringChars bs ++ ">"

instance OctetStream SHA256 where
  toByteString = unSHA256
  fromByteString bs = case B.length bs of
    32 -> SHA256 bs
    _  -> error "SHA256/fromByteString: SHA256 is expected to be 32 bytes"

--------------------------------------------------------------------------------

sha256 :: OctetStream a => a -> SHA256
sha256 x = SHA256 $ Unsafe.unsafePerformIO (sha256_IO $ toByteString x)

sha256_IO :: B.ByteString -> IO B.ByteString
sha256_IO msg = do
  alloca $ \ctx -> do
    c_SHA256_Init ctx   
    B.useAsCStringLen msg $ \(cstr,len) -> c_SHA256_Update ctx (castPtr cstr) (fromIntegral len)
    allocaBytes 32 $ \pdigest -> do
      c_SHA256_Final pdigest ctx
      B.packCStringLen (castPtr pdigest,32)

{-
sha256String :: String -> HexStringLE
sha256String msg = hexEncode' False $ B.unpack $ sha256 $ B.pack $ map char_to_word8 msg
-}

--------------------------------------------------------------------------------