--[[----------------------------------------------------------------------------

WFCryptoTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2019 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

local KSIdGenerator = require "KSIdGenerator"

local KSData = require "KSData"
local KSDate = require "KSDate"

local KSTableUtils = require "KSTableUtils"
local KSPathUtils = require "KSPathUtils"

local WFCrypto = require "WFCrypto"

local WFRxTestCase = require "WFRxTestCase"

local WFFileUtils = require "WFFileUtils"

local WFTestCertificateAuthority = require "WFTestCertificateAuthority"

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

local WFCryptoTest = {}

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

-- From WFCatContentRangeUploaderTest

local function SeekWriteBigFile( f, offset )

	local maxInt32 = 2^31

	f:seek( 'set', math.min( offset, maxInt32-1 ) )
	f:write( 'A' )
	local remainingOffset = offset - maxInt32

	while remainingOffset > 0 do
		f:seek( 'end', math.min( remainingOffset, maxInt32-1 ) )
		f:write( 'A' )
		remainingOffset = remainingOffset - maxInt32
	end

end

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

local function makeFileOfSize( originFilePath, fileSize )

	local f = io.open( originFilePath, "w" )
	if f then
		SeekWriteBigFile( f, fileSize - 1 )
		f:close()
	end

	return originFilePath

end

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

function WFCryptoTest.rxTestSplitSubject( rxTest )

	local splitSubject = WFCrypto.splitSubject( '/C=US/ST=California/L=San Jose/O=Adobe Inc./OU=Digital Imaging/CN=WFCrypto' )

	local expected = {
		C='US',
		ST='California',
		L='San Jose',
		O='Adobe Inc.',
		OU='Digital Imaging',
		CN='WFCrypto'
	}

	rxTest:expectEqual( true, KSTableUtils.deepEquals( splitSubject, expected ) )

	rxTest:expectSpecificError( 'invalid subject', WFCrypto.splitSubject, 'noise/C=US' )

	rxTest:expectSpecificError( 'invalid subject fragment C=US=UK', WFCrypto.splitSubject, '/C=US=UK' )

end

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

function WFCryptoTest.rxTestGenerateKeyPair( rxTest )

	rxTest:setTimeLimit( 10 )

	local privKey, pubKey = WFCrypto.generateKeyPair()
	
	local privatePrefix = '-----BEGIN PRIVATE KEY-----'
	local publicPrefix = '-----BEGIN PUBLIC KEY-----'
	
	rxTest:expectEqual( privKey:sub( 1, #privatePrefix ), privatePrefix )
	rxTest:expectEqual( pubKey:sub( 1, #publicPrefix ), publicPrefix )

end

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

function WFCryptoTest.rxTestAsyncGenerateKeyPair( rxTest )
	
	rxTest:setTimeLimit( 20 )

	local privatePrefix = '-----BEGIN PRIVATE KEY-----'
	local publicPrefix = '-----BEGIN PUBLIC KEY-----'	
	local keyObservable = WFCrypto.asyncGenerateKeyPair()
		:map( function( privKey, pubKey )
		
			return privKey:sub( 1, #privatePrefix ) == privatePrefix and pubKey:sub( 1, #publicPrefix ) == publicPrefix 
		
		end )
		
	rxTest:expectSingleValue( keyObservable, true )

end


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

function WFCryptoTest.rxTestGenerateCSR( rxTest )

	rxTest:setTimeLimit( 10 )

	local key, _ = WFCrypto.generateKeyPair()
	
	local cn = KSIdGenerator.uuidInLowercaseHex() 	
	local csr = WFCrypto.generateCSR( cn, key )
	
	local prefix = '-----BEGIN CERTIFICATE REQUEST-----'

	rxTest:expectEqual( csr:sub( 1, #prefix ), prefix )
	
end

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

-- When this certificate expires, use any valid certificate
-- (a self signed certificate would work)
local letsencryptCertPem = [[-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
]]

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

local testKeyPem = [[-----BEGIN PRIVATE KEY-----
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
C9Jum34bjyKph2h3mXQAXXpT/m4ymS0=
-----END PRIVATE KEY-----
]]

local testCertPem = [[
-----BEGIN CERTIFICATE-----
MIIGKzCCBBOgAwIBAgIRAM4O+4NRz3vxbov9Id8afoswDQYJKoZIhvcNAQELBQAw
dDETMBEGA1UECAwKQ2FsaWZvcm5pYTERMA8GA1UEBwwIU2FuIEpvc2UxEzARBgNV
BAoMCkFkb2JlIEluYy4xGDAWBgNVBAsMD0RpZ2l0YWwgSW1hZ2luZzEbMBkGA1UE
AwwSTGlnaHRyb29tIHAycCByb290MB4XDTIwMDEyNzEzNTIxNFoXDTIwMDQyNzE0
NTIxNFowga8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMREwDwYD
VQQHDAhTYW4gSm9zZTETMBEGA1UECgwKQWRvYmUgSW5jLjEYMBYGA1UECwwPRGln
aXRhbCBJbWFnaW5nMUkwRwYDVQQDDEA1OTdjMmU5NjFkOTE5NTNlNjI5MGQwM2Ew
ZDE3ZjlmMmY3NTIyZjMxNmZiODVkMzcwM2UxYjA1NjBjOWFkZmI1MIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAve8PAJVIpTiToetwiAdkczd0cr7Gmbi4
s36/vRX7+u1qEmfqYk0mOyjnnAQVpamXeXOr/zvw+YhVLA3khX95RUP9s1lHj5HJ
/rmzfdh4T/222FO9/62P2gN6f+PhI+/S8gOr8ZJ5XWpP4l90QwxQV/n97g4+Y5oR
pZTx+IcGxkCipPIaJRB3tNK3/c0aVPMd/+QKaFaQKp53GuzO2FwOFFM8bJY3l3mR
ymKun7VAZ8l83D7pfAP6I+mEitK0ZsfVQClkJF9epDlA6vFUU1tq8aVbe+Wd0Hy3
firNe2e0Hyaz6UtJQUfykZKV9Y6XWUkrBEt1Vqzm3cqbHmCGACl/J76giJeAkhMK
bd5IpoxAoC/uF7daqJwsrI9+7LZvjV2eFV2lXES+UJdrCOTNIRmFrqdhflimLjMb
iIf1feyFDTcLBXKKZEHdD78IBMQ17bCg70cDFQQcS/U/j1CMAtBrSxP7637QkYpk
Lj7NBCOw4jSNWTLLs3WpJwvylTGFjEZRmf3tCm21HTTRmMAlW30RLSD+TffECEdL
PyigMRUrOv1fR9Cfgbcouc4Mou1v5qAgWS+rsOWgQHGz27Mt7wGPwIEIEQ3lr0M6
2woa/LAeg4THtudzYW5t5oprUO0SZEwyMGM+gBZBh8YurMJiwnW6VYBTM1VL/U0r
ZvewsQMXxrsCAwEAAaN8MHowCQYDVR0TBAIwADAfBgNVHSMEGDAWgBTb7up5BYwN
kkEkGyLxA2RSD1On/DAdBgNVHQ4EFgQU5beyxdrlrs++ZxP+z7XkQyctnfAwDgYD
VR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkq
hkiG9w0BAQsFAAOCAgEAJW6/JZXRcwEXNDqO8CvOlquI0Ti8FVXPCHgRabqKKHOR
v2vzAPAqpMhJKntThD/4Ve/mA/kDTyK8vHKUN0YMlzTQSbRHCK37n3G039vR8bBv
Qivog97L5SGUyNXHny6iwwXFhLvGaChkeU7Tkm8h+8hv+d5YqJb+NOSSRs3Q0Jur
zA+NK4HYZNXJ/qasey/h0QTopK0OKUNQwtS8tI0nfbgByTEpdK2QBLAocqvX2yZr
JcLccLb+uKMjs2bUUQIbCQc/D3T+Lyt+JB6YcfwimzDXJtuJYKMhhf5dqQJX5Y/0
oQk39g8B148ztbg08rRIPv/3ygLueB/47qJTw9Ud1udBUW9LBvIzsbRwsSZ7QwQ5
1FveEJBAK/m1Qd/VqTWoeKZnPgX4yHrHkHS2hKiMVgIAYl1nbN3D42gDc3t8hQN2
WwcsueeB9CxYcpCt7857UVdJJOruGg+/u7uXwQnGycmKZblnxRFoAdqgtHeeAuoL
TTRZV90FdpiaReqr6kXrXicn/Nn5yV7kj76C7oTcwMBtsimrZviUc0rH2XdkguFR
VvgTuQ3siXOCY5V+f0rbR9tVM9+mYjyabmliF6eGTNPcc357Fu6SFR1oeZ4I7sps
1rf1yeMBjQxCOYb5p4K2zFAm9WUkcuAOoGD13FxX2CpLQs/EYB9MIfKy7yHgmNA=
-----END CERTIFICATE-----
]]

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

function WFCryptoTest.rxTestIsValidNow( rxTest )

	local adobeCert = WFTestCertificateAuthority.certificate()

	rxTest:expectEqual( true, adobeCert.isValidNow )
	
end

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

function WFCryptoTest.rxTestSignAndVerifyCert( rxTest )

	local adobeTestCaCert = WFTestCertificateAuthority.certificate()

	local csr = WFCrypto.generateCSR( 'WFCryptoTest.rxTestSignCert', testKeyPem )

	local testCert = adobeTestCaCert:sign( csr, WFTestCertificateAuthority.privateKey() )

	local adobeTestCa = WFCrypto.WFCAStore()

	adobeTestCa:add( adobeTestCaCert )

	local success = testCert:verify( adobeTestCa )

	rxTest:expectEqual( true, success )

	adobeTestCa:reset()

	success = testCert:verify( adobeTestCa )

	rxTest:expectEqual( false, success )

end

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

function WFCryptoTest.rxTestVerifyCertNegative( rxTest )

	local adobeTestCaCert = WFTestCertificateAuthority.certificate()

	local adobeTestCa = WFCrypto.WFCAStore()

	adobeTestCa:add( adobeTestCaCert )

	local letsencryptCert = WFCrypto.WFCertificate( letsencryptCertPem )

	local success = letsencryptCert:verify( adobeTestCa )

	rxTest:expectEqual( false, success )

end

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

function WFCryptoTest.rxTestCertGetCommonName( rxTest )

	local adobeCert = WFTestCertificateAuthority.certificate()
	
	rxTest:expectEqual( "Lightroom p2p test root", adobeCert.commonName )
	
end

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

function WFCryptoTest.rxTestCertGetNotAfter( rxTest )

	local adobeCert = WFTestCertificateAuthority.certificate()
	
	rxTest:expectEqual( "2030-03-30T13:37:14Z", adobeCert.notAfter )
	
end

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

function WFCryptoTest.rxTestCertGetNotBefore( rxTest )

	local adobeCert = WFTestCertificateAuthority.certificate()
	
	rxTest:expectEqual( "2020-04-01T13:37:14Z", adobeCert.notBefore )
	
end

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

function WFCryptoTest.rxTestCAStoreSize( rxTest )

	local adobeTestCa = WFCrypto.WFCAStore()

	rxTest:expectEqual( 0, adobeTestCa:size() )

	adobeTestCa:add( WFTestCertificateAuthority.certificate() )

	rxTest:expectEqual( 1, adobeTestCa:size() )

	adobeTestCa:reset()

	rxTest:expectEqual( 0, adobeTestCa:size() )

end

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

function WFCryptoTest.rxTestReadWriteP12( rxTest )

	-- test the happy path

	local testCert = WFCrypto.WFCertificate( testCertPem )

	local fileName = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'caches' ), 'testcert.p12' )

	-- Use openssl pkcs12 -in testcert.p12 to inspect manually
	testCert:writeP12( fileName, testKeyPem, 'test password' )

	rxTest:finally( function()
		WFFileUtils.delete( fileName )
	end )

	local p12Cert, p12Key = WFCrypto.readP12( fileName, 'test password' )

	rxTest:expectEqual( testCertPem, p12Cert.pem )

	rxTest:expectEqual( testKeyPem, p12Key )

	-- and the not so happy paths

	rxTest:expectSpecificError( 'writeP12 failed with BIO_new_file', testCert.writeP12, testCert, '/invalid/path', testKeyPem, 'test password' )

	rxTest:expectSpecificError( ':ignore:', testCert.writeP12, testCert, fileName, '', 'test password' )

end

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

function WFCryptoTest.rxTestReadWritePEM( rxTest )

	-- test the happy path

	local testCert = WFCrypto.WFCertificate( testCertPem )

	local fileName = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'caches' ), 'testcert.pem' )

	-- Use openssl pkcs12 -in testcert.p12 to inspect manually
	testCert:writePEM( fileName )

	rxTest:finally( function()
		WFFileUtils.delete( fileName )
	end )

	local readCert = WFCrypto.readPEM( fileName )

	rxTest:expectEqual( testCertPem, readCert.pem )

	-- and the not so happy path

	rxTest:expectSpecificError( 'writePEM failed with BIO_new_file', testCert.writePEM, testCert, '/invalid/path' )

end

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

function WFCryptoTest.rxTestCertificateFromKSData( rxTest )

	-- test the certificate constructor via KSData
	local pemData = KSData.make( testCertPem )
		
	local certificate = WFCrypto.WFCertificate( pemData )
	
	rxTest:expectEqual( certificate.pem, testCertPem )
	
end

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

function WFCryptoTest.rxTestRandomPassword( rxTest )

	local password = WFCrypto.randomPassword( 12 )

	rxTest:expectEqual( #password, 12 )

	password = WFCrypto.randomPassword( 32 )

	rxTest:expectEqual( #password, 32 )

	rxTest:expectSpecificError( 'too short', WFCrypto.randomPassword, 11 )

	rxTest:expectSpecificError( 'charset too small', WFCrypto.randomPassword, 12, "abcdefghi" )

	local charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

	password = WFCrypto.randomPassword( 12, charset )

	password:gsub( ".", function(c)
		rxTest:expectEqual( true, not not charset:find( c ) )
	end)

end

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

local function getDigestTestData()
	local input = "Calculate a hash (aka message digest) of data. Implementations are from Sun (java.security.MessageDigest) and GNU."
	local t = {}
	local function add( ... )
		t[ #t + 1 ] = { ... }
	end

--	add( "MD4", input, "28aa04c0d6ab8fa0e5f0fc00dcd821fc" )
	add( "MD5", input, "889de37475010a39510c419e5657b6a6" )
	add( "SHA1", input, "3f22f2af267f5ceff036f9b1429c48ccf4995a63" )
	add( "SHA224", input, "0e082428797124051f4a1825a3495c771f561dcb6f3757b85046b6af" )
	add( "SHA256", input, "2ed398c94aad35718659e6263855d5321a87d0bdf05085dc4aa7f34757a83249" )
	add( "SHA512", input, "7ed4cb153957127c0c277b771dcfcc6d7de3ce557424cbedf48c42aa608b1c9a6547282fe9a2054d80c937baefeaae8fba975007fa70189832ba870a3387a5f4" )


-- CommonCrypto on Mac/iOS does not support HMAC+MD4
--	add( "HMAC", input, "d6f19f0ff19b7d21be742ee331417ccc", "MD4", "secret" )
	add( "HMAC", input, "99f3a240ea9fee53a4d69763f8462e6d", "MD5", "secret" )
	add( "HMAC", input, "a029b8b6888d9a11851895401d14ac122cd30760", "SHA1", "secret" )
	add( "HMAC", input, "53654f988ffe55549b566a73c0b2779768999d6a39074fa1a84f2ad53160393c", "SHA256", "secret" )
	add( "HMAC", input, "39eedf9024e13fff87460d92be201ac6a9f31b82697195f2221199b0b25a98f61d307e259c102a9bb8a21099e661650fcc62ed00e13dccde7bd2380bd8c701c8", "SHA512", "secret" )
	return t
	
end

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

local hexstr = '0123456789abcdef'
local hex = {}

local ct = 0
for c in string.gmatch( hexstr, '.' ) do
	hex[ c ] = ct
	ct = ct + 1
end

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

local function verify( rxTest, expected, digest, bin )

	rxTest:expectEqual( digest, expected )
	local t = {}

	for i = 1, math.floor( #expected/2 ) do
		local achr = string.sub( expected, i * 2 - 1, i * 2 - 1 )
		local bchr = string.sub( expected, i * 2, i * 2 )
		local a = assert( hex[ achr ], achr )
		local b = assert( hex[ bchr ], bchr )
		local c = string.char( a * 16 + b )
		table.insert( t, c )
	end
	rxTest:expectEqual( bin, table.concat( t ) )

end

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

local function quickDigest( rxTest, digestType, input, expected, ... )
	
	local digest, bin = WFCrypto[ digestType ].digest( input, ... )
	
	rxTest:logf( "quick: ", digestType, input, digest )
	verify( rxTest, expected, digest, bin )
	
end

function WFCryptoTest.rxTestDigest( rxTest )

	rxTest:setTimeLimit( 2 )

	for _, params in ipairs( getDigestTestData() ) do
		quickDigest( rxTest, unpack( params ) )
	end
end

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

local function updateDigest( rxTest, digestType, input, expected, ... )
	
	if digestType ~= "HMAC" then
		local digester = WFCrypto[ digestType ].init( ... )
		
		-- turn this input string into a series of parts by splitting it on 'a' chars
		local parts = {}
		for s in string.gmatch( input, "[^a]+" ) do
			table.insert( parts, s )
			table.insert( parts, "a" )
		end
		parts[ #parts ] = nil
		for _, s in ipairs( parts ) do
			digester:update( s )
		end
		
		local digest, bin = digester:digest()
		rxTest:logf( "update: ", digestType, input, digest )
		verify( rxTest, expected, digest, bin )
	end
	
end

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

function WFCryptoTest.rxTestUpdate( rxTest )
        
	rxTest:setTimeLimit( 2 )

	for _, params in ipairs( getDigestTestData() ) do
		updateDigest( rxTest, unpack( params ) )
	end
	
end

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

function WFCryptoTest.rxTestDigestFile( rxTest )

	rxTest:setTimeLimit( 10 )

	local testDir = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), 'assets' )
	local testFile = KSPathUtils.child( testDir, '32MB.mov' )

	local expectedSHA256 = WFCrypto.digestFile( testFile, 'SHA256' )

	local digestObservable = WFCrypto.asyncDigestFile( testFile, 'SHA256' )

	rxTest:expectSingleValue( digestObservable, expectedSHA256 )

end

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

function WFCryptoTest.rxTestDigestFile_testPerformance( rxTest )

	rxTest:setTimeLimit( 1600 )

	local testDir = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), 'assets' )

	local testFile512MB = makeFileOfSize( KSPathUtils.child( testDir, '512MB.aaa' ), 512 * 1024 * 1024 )

	local testFile2GB = makeFileOfSize( KSPathUtils.child( testDir, '2GB.aaa' ), 2 * 1024 * 1024 * 1024 )

	rxTest:measurePerformance(function()
		for i = 1, 5 do
			rxTest:expectEqual( '7e888e2b217a7bb74d4d38f8dbf90cd88c2e92e6767f3171d7d65733611d0e05',
				WFCrypto.digestFile( KSPathUtils.child( testDir, '1.5MB.jpg' ), 'SHA256' ) )
		end
	end,
	'digestFile 5x 1.5MB.jpg' )

	rxTest:measurePerformance(function()
		for i = 1, 5 do
			rxTest:expectEqual( '27da2d8141467b2422b7af71ebaec4113c41308194adcb5848c8fbb577b1f09a',
				WFCrypto.digestFile( KSPathUtils.child( testDir, 'xmp_6MB.rdf' ), 'SHA256' ) )
		end
	end,
	'digestFile 5x xmp_6MB.rdf' )

	rxTest:measurePerformance(function()
		for i = 1, 5 do
			rxTest:expectEqual( '5fcb91ca8e248d2aee68b32735552e866ef98ae6d2a87e165cebd28bbeb9efab',
				WFCrypto.digestFile( KSPathUtils.child( testDir, '32MB.mov' ), 'SHA256' ) )
		end
	end,
	'digestFile 5x 32MB.mov' )

	rxTest:measurePerformance(function()
		for i = 1, 5 do
			rxTest:expectEqual( 'f666154d0d9ef41e6dac3622eb61c85fae14dbaaaf556855e1aa457f92efdb72',
				WFCrypto.digestFile( testFile512MB, 'SHA256' ) )
		end
	end,
	'digestFile 5x 512MB' )

	-- only run this once (the test may time out already)
	rxTest:measurePerformance(function()
		rxTest:expectEqual( 'ad25a3fb21f0987696e2032c725445581371b8e734584cae45e5a43bee31860a',
			WFCrypto.digestFile( testFile2GB, 'SHA256' ) )
	end,
	'digestFile 1x 2GB' )

end

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


function WFCryptoTest.rxTestAsyncDigestFile_testPerformance( rxTest )

	rxTest:setTimeLimit( 1600 )

	local testDir = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), 'assets' )

	local testFile512MB = makeFileOfSize( KSPathUtils.child( testDir, '512MB.aaa' ), 512 * 1024 * 1024 )

	local testFile2GB = makeFileOfSize( KSPathUtils.child( testDir, '2GB.aaa' ), 2 * 1024 * 1024 * 1024 )

	rxTest:measurePerformance(function()
		for i = 1, 5 do
			rxTest:expectSingleValue( 
				WFCrypto.asyncDigestFile( KSPathUtils.child( testDir, '1.5MB.jpg' ), 'SHA256' ),
					'7e888e2b217a7bb74d4d38f8dbf90cd88c2e92e6767f3171d7d65733611d0e05' )
		end
	end,
	'asyncDigestFile 5x 1.5MB.jpg' )

	rxTest:measurePerformance(function()
		for i = 1, 5 do
			rxTest:expectSingleValue( 
				WFCrypto.asyncDigestFile( KSPathUtils.child( testDir, 'xmp_6MB.rdf' ), 'SHA256' ),
					'27da2d8141467b2422b7af71ebaec4113c41308194adcb5848c8fbb577b1f09a' )
		end
	end,
	'asyncDigestFile 5x xmp_6MB.rdf' )

	rxTest:measurePerformance(function()
		for i = 1, 5 do
			rxTest:expectSingleValue(
				WFCrypto.asyncDigestFile( KSPathUtils.child( testDir, '32MB.mov' ), 'SHA256' ),
					'5fcb91ca8e248d2aee68b32735552e866ef98ae6d2a87e165cebd28bbeb9efab' )
		end
	end,
	'asyncDigestFile 5x 32MB.mov' )

	rxTest:measurePerformance(function()
		for i = 1, 5 do
			rxTest:expectSingleValue( WFCrypto.asyncDigestFile( testFile512MB, 'SHA256' ),
				'f666154d0d9ef41e6dac3622eb61c85fae14dbaaaf556855e1aa457f92efdb72' )
		end
	end,
	'asyncDigestFile 5x 512MB' )

	-- only run this once (the test may time out already)
	rxTest:measurePerformance(function()
		rxTest:expectSingleValue( WFCrypto.asyncDigestFile( testFile2GB, 'SHA256' ),
			'ad25a3fb21f0987696e2032c725445581371b8e734584cae45e5a43bee31860a' )
	end,
	'asyncDigestFile 1x 2GB' )

end

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

function WFCryptoTest.rxTestDigestSpeed_testPerformance( rxTest )

	rxTest:setTimeLimit( 600 )

	-- populate a few thousand random strings

	local hashCandidates = {}
	local numCandidates = 10000

	for i = 1, numCandidates do

		local s = WFCrypto.randomPassword( 2048 )
		hashCandidates[i] = s

	end

	local digest, bin

	local candidateIndex
	local supportedAlgoritms = { "MD5", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512" }

	-- For each of our supported algorithms, hash 100_000 strings and report back the time taken per algorithm

	-- Just to make future benchmarking easier, make the test set the same per algorithm
	local inputs = {}
	for i = 1, 100000 do -- Duplicates are allowed

		local candidateIndex = math.random( numCandidates )
		inputs[i] = hashCandidates[ candidateIndex ]

	end

	for _, algo in pairs( supportedAlgoritms ) do

		rxTest:measurePerformance(function()
			for i, input in ipairs( inputs ) do

				if i == 1 then
					-- quick check to verify that the algorithm is working as one would expect
					digest, bin = WFCrypto[ algo ].digest( input )
					assert( digest )
					assert( bin )

				else
					WFCrypto[ algo ].digest( input )
				end

			end


		end, string.format( "%s-algorithm", algo ) )
	end


end

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

function WFCryptoTest.rxTestDigestFileFixedSha256( rxTest )

	rxTest:setTimeLimit( 10 )

	-- I've used the command line utility shasum -a 256 to generate these hashes independently
	local testData = {
		{
			path = 'wffileutils/file-2.txt', -- this is an empty file
			sha256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
		},
		{
			path = 'assets/1.5MB.jpg',
			sha256 = '7e888e2b217a7bb74d4d38f8dbf90cd88c2e92e6767f3171d7d65733611d0e05'
		},
		{ 
			path = 'assets/xmp_6MB.rdf',
			sha256 = '27da2d8141467b2422b7af71ebaec4113c41308194adcb5848c8fbb577b1f09a'
		},
		{
			path = 'assets/32MB.mov',
			sha256 = '5fcb91ca8e248d2aee68b32735552e866ef98ae6d2a87e165cebd28bbeb9efab'
		},
	}

	local testDataDir = KSPathUtils.getStandardFilePath( 'testdata' )

	for _, test in ipairs( testData ) do

		local testFile = KSPathUtils.child( testDataDir, test.path )

		rxTest:expectEqual( test.sha256, WFCrypto.digestFile( testFile, 'SHA256' ) )
	
		rxTest:expectSingleValue( WFCrypto.asyncDigestFile( testFile, 'SHA256' ), test.sha256 )
		
		rxTest:expectSingleValue( WFCrypto.asyncDigestData( KSData.memoryMappedContentsOfFile( testFile ), 'SHA256' ), test.sha256 )

	end

end

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

function WFCryptoTest.rxTestDigestFileWithTwoAlgorithms( rxTest )

	rxTest:setTimeLimit( 10 )

	local testDir = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), 'assets' )
	local testFile = KSPathUtils.child( testDir, '1.5MB.jpg' )

	local expectedSHA256 = WFCrypto.digestFile( testFile, 'SHA256' )
	local expectedMD5 = WFCrypto.digestFile( testFile, 'MD5' )

	local digestSHA256AndMD5 = WFCrypto.asyncDigestFile( testFile, 'SHA256', 'MD5' )

	rxTest:expectSingleValue( digestSHA256AndMD5, expectedSHA256, expectedMD5 )

end

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

function WFCryptoTest.rxTestDigestData( rxTest )

	rxTest:setTimeLimit( 10 )

	local testDir = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), 'assets' )
	local testFile = KSPathUtils.child( testDir, '1.5MB.jpg' )

	local expectedSHA256 = WFCrypto.digestFile( testFile, 'SHA256' )

	local digestObservable = WFCrypto.asyncDigestData( KSData.memoryMappedContentsOfFile( testFile ), 'SHA256' )

	rxTest:expectSingleValue( digestObservable, expectedSHA256 )

end

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

function WFCryptoTest.rxTestDigestDataWithTwoAlgorithms( rxTest )

	rxTest:setTimeLimit( 10 )

	local testDir = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), 'assets' )
	local testFile = KSPathUtils.child( testDir, '1.5MB.jpg' )

	local expectedSHA256 = WFCrypto.digestFile( testFile, 'SHA256' )
	local expectedMD5 = WFCrypto.digestFile( testFile, 'MD5' )

	local digestSHA256AndMD5 = WFCrypto.asyncDigestData( KSData.memoryMappedContentsOfFile( testFile ), 'SHA256', 'MD5' )

	rxTest:expectSingleValue( digestSHA256AndMD5, expectedSHA256, expectedMD5 )

end

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

function WFCryptoTest.rxTestDigestMissingFile( rxTest )

	rxTest:setTimeLimit( 10 )

	local missingFile = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), 'MissingFile.txt' )

	local digestMissingFile = WFCrypto.asyncDigestFile( missingFile, 'SHA256' )

	rxTest:expectOnlyError( digestMissingFile, "WFCrypto.asyncDigestFile: Unable to open file." )

end

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

function WFCryptoTest.rxTestDigestInvalidAlgorithm( rxTest )

	rxTest:setTimeLimit( 10 )

	local testDir = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), 'assets' )
	local testFile = KSPathUtils.child( testDir, '1.5MB.jpg' )

	local success, message = pcall( WFCrypto.digestFile, testFile, 'InvalidAlgorithm' )

	rxTest:expectEqual( false, success )

	rxTest:expectEqual(
		"bad argument #2 (algorithm) to (null) (WFDigestAlgorithm expected, got string)",
		message )

end

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

return WFRxTestCase.wrapSuite( 'WFCryptoTest', WFCryptoTest )
