{-# LANGUAGE OverloadedStrings, LambdaCase #-}
module Main (main) where
import Crypto.Hash.GOST34112012
import Prelude hiding (readFile, putStrLn)
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Text.Encoding (encodeUtf8)
import Data.Text (Text, toUpper, pack, unpack)
import Data.Text.IO (putStrLn)
import qualified Data.Text.IO as TIO
import Control.Monad (when, forM_)
import System.CPUTime (getCPUTime, cpuTimePrecision)
import Control.Exception (catchJust)
import System.IO (IOMode(..), withBinaryFile)
import System.IO.Error (ioeGetErrorType, isDoesNotExistErrorType)
main :: IO ()
main = do
t0 <- getCPUTimeNS
report t0 "Processing 'empty' test"
hashB16_512 "" >>= test t0 "empty" emptyB16_512
hashB16_256 "" >>= test t0 "empty" emptyB16_256
report t0 "Processing 'two symbols' test"
hashB16_512 "xe" >>= test t0 "two ascii" twoAsciiB16_512
hashB16_512 "хе" >>= test t0 "two unicode" twoUnicodeB16_512
report t0 "Processing 'LICENSE' test"
TIO.readFile "LICENSE" >>=
hashB16_512 >>= test t0 "LICENSE" licenseB16_512
TIO.readFile "LICENSE" >>=
hashB16_256 >>= test t0 "LICENSE" licenseB16_256
TIO.readFile "c_src/streebog/LICENSE" >>=
hashB16_512 >>= test t0 "LICENSE" licenseStreebogB16_512
TIO.readFile "c_src/streebog/LICENSE.GPL2" >>=
hashB16_256 >>= test t0 "LICENSE.GPL2" licenseGpl2B16_256
sequenceFileRead 256 "c_src/streebog/LICENSE.GPL2" >>=
test t0 "LICENSE.GPL2" licenseGpl2B16_256
sequenceFileRead 512 "c_src/streebog/LICENSE.GPL2" >>=
test t0 "LICENSE.GPL2" licenseGpl2B16_512
forM_ [ (bs, i) | i <- [1, 3, 4, 5, 6], bs <- [512, 256] ] $ uncurry $
testEtalon t0 TIO.readFile hashB16
testEtalon t0 B.readFile hashB16B 512 2
testEtalon t0 B.readFile hashB16B 256 2
-- 4G file! Use make4G if needed
catchFileNotFound (report t0 "The missing etalon M7 is skipped. \
\The file takes about 4 gbytes, and can be generated by the \
\test/etalon/make4Gb command.") $ do
testEtalon' t0 512 7 $ sequenceFileRead 512
testEtalon' t0 256 7 $ sequenceFileRead 256
-- testEtalon t0 B.readFile hashB16B 256 7
testEtalon t0 B.readFile hashB16B 512 8
testEtalon t0 B.readFile hashB16B 256 8
report t0 "Tests passed"
pure ()
etalonPath :: FilePath
etalonPath = "test/etalon/"
emptyB16_512 :: Text
emptyB16_512 = "8E945DA209AA869F0455928529BCAE4679E9873AB707B55315F56CEB98BEF0A7362F715528356EE83CDA5F2AAC4C6AD2BA3A715C1BCD81CB8E9F90BF4C1C1A8A"
emptyB16_256 :: Text
emptyB16_256 = "3F539A213E97C802CC229D474C6AA32A825A360B2A933A949FD925208D9CE1BB"
twoAsciiB16_512 :: Text
twoAsciiB16_512 = toUpper "f5e2ec8bf5705a0b6ed1afd6e469a3cabd1a0476d8ae904aafdb094c69b33f5f2a890b9258c205dde383b83a1a391f18685b4395de4b4d1037944448ce797068"
twoUnicodeB16_512 :: Text
twoUnicodeB16_512 = toUpper "c90966f0594acbfec047b11b02d03344e19070e35b36c523577223aeae0ad8d3172d8955174faac47a256628ac7e69427222ace2eae454ea7fa52086ee43fa97"
licenseB16_512 :: Text
licenseB16_512 = toUpper "40253a453e526e62d96af2e6efe64d794039908053fdedaf0e5340f6369a966dc9341d1f988c9169e2b1fb51f4987b219e6b26417e5b1afa34df09e002efdddd"
licenseB16_256 :: Text
licenseB16_256 = toUpper "14e7341e97e50e47526b333820dc53bc12d70fe6df76ebf2231531951b4a1819"
licenseGpl2B16_256 :: Text
licenseGpl2B16_256 = "972B18C6ABA96CBADB6C1F817DFB7CBCA2E08BCA5513819E05CEF85B69A7E1CC"
licenseGpl2B16_512 :: Text
licenseGpl2B16_512 = toUpper "7ecacddc1e3c92dbe2cda16f2b035bd5278cda5ab248f94117853c900105295d15490175f1c07d38d235a48fd9c165e01673bab76a12bb9c924da752189aeb9b"
licenseStreebogB16_512 :: Text
licenseStreebogB16_512 = toUpper "6ee316287bab10429c3c66311c606406fbb2f42848de9007c239cc6d9f43ea1a0f272ee3a4e55cc9850a0542a8c4e73a44a4aa08827c2803d0ac32db1ce8e4b7"
etalonsCheck :: String -> Text -> Bool
etalonsCheck tid csum = maybe False (== csum) $ lookup tid [
("1.512", "D7630524FCAF7054613064EE5FE1D19885FCD33B2F74FB074F1A6724420E127F"),
("1.256", "C81762568A91969C2FFBC4EE9F4817B636043BFA95A9A25FBCDBAF4ADB8D3ACE"),
("2.512", "35A4B0011506B6521A7222FECAF2E97F6C3482F55ABA4F821D6302E8EF553551"),
("2.256", "9F86EABE1AC4DEC3B46378DB6470ABEEFE9AEADE12116A269875F391C2B9763E"),
("3.512", "1B101143197C9B5CFAC2F2D9A0A29BE8315B76FF442BFA8C7003522CF6BF76EF"),
("3.256", "D789EC569F4910EB3CCF37D04C4EAE9119CA8BF50F10E4CC8D4DB8ED641374B5"),
("4.512", "5A269B8F9126EF8233F9255EC30E41295692294DC0F52584BC01EF91C0315BCD"),
("4.256", "DFC267C06977089381D049BF3532862CBBA81991079FA6706AC66C3A2E6E1803"),
("5.512", "6E543523B6BE13F3F516D3B6F4C29D66A3E57F562F494AACD2BCD9BDA641B20B"),
("5.256", "502BD3C103F5C751F09E8129AFF7445CD3036F16182883773964A2DDFF42FC29"),
("6.512", "F3A1BFBE060CE023343549259840BAD60A5718A7996911E9DFDBCFD4698FD79F"),
("6.256", "CBF20C9196004329067882AE468E8B850600E23687CD846419E38B9D18123566"),
("7.256", "6AC9BCEC2F70243C9E55331E553BB351266B638085A46F7EFF3D053FD1B1EA7C"),
("7.512", "18C144CED8D060D8D2AB933B8EF7D30973E6ECB3E02C548804A29A0CB616DE46"),
("8.512", "6F2ED35F3541D12C25F3C7B3FC049DD3053E4F2687331C86FB907BA132D01A17"),
("8.256", "AAAD2B63E972EA3EAF14922255786A56ECBC9DFF7589C81C40DC96F52F3695EA")
]
sequenceFileRead :: Int -> FilePath -> IO Text
sequenceFileRead bs fname = withBinaryFile fname ReadMode $ \h -> do
ctx <- initGOST34112012 bs
-- kinda "random" buffer size
let bsize = (+ 21) . (flip mod 262144) . (* 3) <$> getGOST34112012BufSize ctx
let loop = bsize >>= B.hGet h >>= \case
"" -> pure ()
b -> updateGOST34112012 ctx b >> loop
loop
hex <$> finishGOST34112012 ctx
testEtalon' :: Integer -> Int -> Int -> (FilePath -> IO Text) -> IO ()
testEtalon' t0 bs n r = do
(tid, chk, fname) <- readEtalon bs n
report t0 $ "Processing etalon " ++ tid ++ " file " ++ fname
tst <- r fname
test t0 (pack $ tid ++ " (" ++ fname ++ ")") (toUpper chk) tst
testEtalon :: Integer -> (FilePath -> IO t) -> (Int -> t -> IO Text) -> Int -> Int -> IO ()
testEtalon t0 r h bs n = testEtalon' t0 bs n $ \fname -> r fname >>= h bs
test :: Integer -> Text -> Text -> Text -> IO ()
test t0 msg tst chk = when (chk /= tst) testFail where
testFail = do
report t0 $ unpack $ "Test '" <> msg <> "' FAILED: '" <> chk <> "' /= '" <> tst <> "'"
fail $ unpack msg
readEtalon :: Int -> Int -> IO (String, Text, FilePath)
readEtalon bs n = let tid = show n ++ "." ++ show bs in do
chk <- TIO.readFile $ etalonPath ++ "H" ++ tid
csum <- hashB16 256 chk
if not $ etalonsCheck tid csum
then fail $ unpack $ "Etalon csum mismatch! Please fix function etalonCheck: '" <>
" (\"" <> pack tid <> "\", \"" <> csum <> "\")"
else pure (tid, chk, etalonPath ++ "M" ++ show n ++ ".src")
hashB16_512 :: Text -> IO Text
hashB16_512 = fmap hex . hashGOST34112012_512 . encodeUtf8
hashB16_256 :: Text -> IO Text
hashB16_256 = fmap hex . hashGOST34112012_256 . encodeUtf8
hashB16 :: Int -> Text -> IO Text
hashB16 bs = hashB16B bs . encodeUtf8
hashB16B :: Int -> ByteString -> IO Text
hashB16B bs = fmap hex . hashGOST34112012 bs
hex :: ByteString -> Text
hex = let h = ("0123456789ABCDEF" !!) . fromEnum
in pack . B.foldr (\b a -> h (div b 16) : h (mod b 16) : a) ""
getCPUTimeNS :: IO Integer
getCPUTimeNS = fmap (flip div cpuTimePrecision) getCPUTime
report :: Integer -> String -> IO ()
report t0 msg = getCPUTimeNS >>= \t -> putStrLn $ pack $ " [" ++ show (t - t0) ++ "] " ++ msg
catchFileNotFound :: IO () -> IO () -> IO ()
catchFileNotFound h m = catchJust (\e -> if isDoesNotExistErrorType (ioeGetErrorType e) then Just () else Nothing) m (const h)