/* test_lib.c - unit tests and benchmark for LibRHash algorithms
 *
 * Copyright (c) 2008, Aleksey Kravchenko <rhash.admin@gmail.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE  INCLUDING ALL IMPLIED WARRANTIES OF  MERCHANTABILITY
 * AND FITNESS.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT,  OR CONSEQUENTIAL DAMAGES  OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE,  DATA OR PROFITS,  WHETHER IN AN ACTION OF CONTRACT,  NEGLIGENCE
 * OR OTHER TORTIOUS ACTION,  ARISING OUT OF  OR IN CONNECTION  WITH THE USE  OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include "test_lib.h"
#include "byte_order.h"
#include "test_utils.h"

#ifdef USE_RHASH_DLL
# define RHASH_API __declspec(dllimport)
#endif
#include "rhash.h"
#include "rhash_torrent.h"

#include <assert.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include <fcntl.h>
#include <sys/stat.h>
#if defined(_WIN32)
# include <io.h>
#endif

/*=========================================================================*
 *                              Test vectors                               *
 *=========================================================================*/

/* verified by cksfv */
const char* crc32_tests[] = {
	"", "00000000",
	"a", "E8B7BE43",
	"abc", "352441C2",
	"message digest", "20159D7F",
	"abcdefghijklmnopqrstuvwxyz", "4C2750BD",
	"The quick brown fox jumps over the lazy dog", "414FA339",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "1FC2E6D2",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "7CA94A72",
	0
};

const char* crc32c_tests[] = {
	"", "00000000",
	"a", "C1D04330",
	"abc", "364B3FB7",
	"message digest", "02BD79D0",
	"abcdefghijklmnopqrstuvwxyz", "9EE6EF25",
	"The quick brown fox jumps over the lazy dog", "22620404",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "A245D57D",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "477A6781",
	0
};

const char* md4_tests[] = {
	"", "31D6CFE0D16AE931B73C59D7E0C089C0",
	"a", "BDE52CB31DE33E46245E05FBDBD6FB24",
	"abc", "A448017AAF21D8525FC10AE87AA6729D",
	"message digest", "D9130A8164549FE818874806E1C7014B",
	"abcdefghijklmnopqrstuvwxyz", "D79E1C308AA5BBCDEEA8ED63DF412DA9",
	"The quick brown fox jumps over the lazy dog", "1BEE69A46BA811185C194762ABAEAE90",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "043F8582F241DB351CE627E153E7F0E4",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "E33B4DDC9C38F2199C3E7B164FCC0536",
	0
};

/* for short messages ed2k test vectors coincide with md4 */
#define ed2k_tests md4_tests

/* test vectors from spec */
const char* md5_tests[] = {
	"", "D41D8CD98F00B204E9800998ECF8427E",
	"a", "0CC175B9C0F1B6A831C399E269772661",
	"abc", "900150983CD24FB0D6963F7D28E17F72",
	"message digest", "F96B697D7CB7938D525A2F31AAF161D0",
	"abcdefghijklmnopqrstuvwxyz", "C3FCD3D76192E4007DFB496CCA67E13B",
	"The quick brown fox jumps over the lazy dog", "9E107D9D372BB6826BD81D3542A419D6",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "D174AB98D277D9F5A5611C2C9F419D9F",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "57EDF4A22BE3C955AC49DA2E2107B67A",
	0
};

/* test vectors from spec */
const char* sha1_tests[] = {
	"", "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709",
	"a", "86F7E437FAA5A7FCE15D1DDCB9EAEAEA377667B8",
	"abc", "A9993E364706816ABA3E25717850C26C9CD0D89D",
	"message digest", "C12252CEDA8BE8994D5FA0290A47231C1D16AAE3",
	"The quick brown fox jumps over the lazy dog", "2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "761C457BF73B14D27E9E9265C46F4B4DDA11F940",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "50ABF5706A150990A08B2C5EA40FA0E585554732",
	0
};

/* tests from spec and NESSIE test vectors */
const char* tiger_tests[] = {
	"", "3293AC630C13F0245F92BBB1766E16167A4E58492DDE73F3",
	"a", "77BEFBEF2E7EF8AB2EC8F93BF587A7FC613E247F5F247809",
	"abc", "2AAB1484E8C158F2BFB8C5FF41B57A525129131C957B5F93",
	"Tiger", "DD00230799F5009FEC6DEBC838BB6A27DF2B9D6F110C7937",
	"The quick brown fox jumps over the lazy dog", "6D12A41E72E644F017B6F0E2F7B44C6285F06DD5D2C5B075",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-", "F71C8583902AFB879EDFE610F82C0D4786A3A534504486B5",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ=abcdefghijklmnopqrstuvwxyz+0123456789", "48CEEB6308B87D46E95D656112CDF18D97915F9765658957",
	"Tiger - A Fast New Hash Function, by Ross Anderson and Eli Biham", "8A866829040A410C729AD23F5ADA711603B3CDD357E4C15E",
	"Tiger - A Fast New Hash Function, by Ross Anderson and Eli Biham, proceedings of Fast Software Encryption 3, Cambridge.", "CE55A6AFD591F5EBAC547FF84F89227F9331DAB0B611C889",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-", "C54034E5B43EB8005848A7E0AE6AAC76E4FF590AE715FD25",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "8DCEA680A17583EE502BA38A3C368651890FFBCCDC49A8CC",

	"message digest", "D981F8CB78201A950DCF3048751E441C517FCA1AA55A29F6",
	"abcdefghijklmnopqrstuvwxyz", "1714A472EEE57D30040412BFCC55032A0B11602FF37BEEE9",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "0F7BF9A19B9C58F2B7610DF7E84F0AC3A71C631E7B53F78E",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "8DCEA680A17583EE502BA38A3C368651890FFBCCDC49A8CC",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "1C14795529FD9F207A958F84C52F11E887FA0CABDFD91BFD",
	0
};

/* verified by strong dc++ */
const char* tth_tests[] = {
	"", "LWPNACQDBZRYXW3VHJVCJ64QBZNGHOHHHZWCLNQ",
	"a", "CZQUWH3IYXBF5L3BGYUGZHASSMXU647IP2IKE4Y",
	"abc", "ASD4UJSEH5M47PDYB46KBTSQTSGDKLBHYXOMUIA",
	"message digest", "YM432MSOX5QILIH2L4TNO62E3O35WYGWSBSJOBA",
	"abcdefghijklmnopqrstuvwxyz", "LMHNA2VYO465P2RDOGTR2CL6XKHZNI2X4CCUY5Y",
	"The quick brown fox jumps over the lazy dog", "WLM2MITXFTCQXEOYO3M4EL5APES353NQLI66ORY",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "TF74ENF7MF2WPDE35M23NRSVKJIRKYRMTLWAHWQ",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "NBKCANQ2ODNTSV4C7YJFF3JRAV7LKTFIPHQNBJY",
	0
};

const char* aich_tests[] = {
	"", "3I42H3S6NNFQ2MSVX7XZKYAYSCX5QBYJ",
	"a", "Q336IN72UWT7ZYK5DXOLT2XK5I3XMZ5Y",
	"abc", "VGMT4NSHA2AWVOR6EVYXQUGCNSONBWE5",
	"message digest", "YERFFTW2RPUJSTK7UAUQURZDDQORNKXD",
	"abcdefghijklmnopqrstuvwxyz", "GLIQY64M7FSXBSQEZY37FIM5QQSA2OUJ",
	"The quick brown fox jumps over the lazy dog", "F7KODRT2FUUPZ3MET3Q3W5XHHENZH2YS",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "OYOEK67XHMKNE7U6SJS4I32LJXNBD6KA",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "KCV7K4DKCUEZBIELFRPKID5A4WCVKRZS",
	0
};

const char* whirlpool_tests[] = {
	"", "19FA61D75522A4669B44E39C1D2E1726C530232130D407F89AFEE0964997F7A73E83BE698B288FEBCF88E3E03C4F0757EA8964E59B63D93708B138CC42A66EB3",
	"a", "8ACA2602792AEC6F11A67206531FB7D7F0DFF59413145E6973C45001D0087B42D11BC645413AEFF63A42391A39145A591A92200D560195E53B478584FDAE231A",
	"abc", "4E2448A4C6F486BB16B6562C73B4020BF3043E3A731BCE721AE1B303D97E6D4C7181EEBDB6C57E277D0E34957114CBD6C797FC9D95D8B582D225292076D4EEF5",
	"message digest", "378C84A4126E2DC6E56DCC7458377AAC838D00032230F53CE1F5700C0FFB4D3B8421557659EF55C106B4B52AC5A4AAA692ED920052838F3362E86DBD37A8903E",
	"abcdefghijklmnopqrstuvwxyz", "F1D754662636FFE92C82EBB9212A484A8D38631EAD4238F5442EE13B8054E41B08BF2A9251C30B6A0B8AAE86177AB4A6F68F673E7207865D5D9819A3DBA4EB3B",
	"The quick brown fox jumps over the lazy dog", "B97DE512E91E3828B40D2B0FDCE9CEB3C4A71F9BEA8D88E75C4FA854DF36725FD2B52EB6544EDCACD6F8BEDDFEA403CB55AE31F03AD62A5EF54E42EE82C3FB35",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
	"DC37E008CF9EE69BF11F00ED9ABA26901DD7C28CDEC066CC6AF42E40F82F3A1E08EBA26629129D8FB7CB57211B9281A65517CC879D7B962142C65F5A7AF01467",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890",
	"466EF18BABB0154D25B9D38A6414F5C08784372BCCB204D6549C4AFADB6014294D5BD8DF2A6C44E538CD047B2681A51A2C60481E88C5A20B2C2A80CF3A9A083B",
	"abcdbcdecdefdefgefghfghighijhijk",
	"2A987EA40F917061F5D6F0A0E4644F488A7A5A52DEEE656207C562F988E95C6916BDC8031BC5BE1B7B947639FE050B56939BAAA0ADFF9AE6745B7B181C3BE3FD",
	0
};

/* test vectors from RIPEMD-160 spec */
const char* ripemd_tests[] = {
	"", "9C1185A5C5E9FC54612808977EE8F548B2258D31",
	"a", "0BDC9D2D256B3EE9DAAE347BE6F4DC835A467FFE",
	"abc", "8EB208F7E05D987A9B044A8E98C6B087F15A0BFC",
	"message digest", "5D0689EF49D2FAE572B881B123A85FFA21595F36",
	"abcdefghijklmnopqrstuvwxyz", "F71C27109C692C1B56BBDCEB5B9D2865B3708DBC",
	"The quick brown fox jumps over the lazy dog", "37F332F68DB77BD9D7EDD4969571AD671CF9DD3B",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "12A053384A9C0C88E405A06C27DCF49ADA62EB2B",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "B0E20B6E3116640286ED3A87A5713079B21F5189",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "9B752E45573D4B39F4DBD3323CAB82BF63326BFB",
	0
};

/*
 * Two important test-cases (some libraries calculate them incorrectly):
 * GOST94( <100000 characters of 'a'> ) = 5C00CCC2734CDD3332D3D4749576E3C1A7DBAF0E7EA74E9FA602413C90A129FA
 * GOST94( <128 characters of 'U'> ) = 53A3A3ED25180CEF0C1D85A074273E551C25660A87062A52D926A9E8FE5733A4
 */

/* test vectors from internet, verified by OpenSSL and some other programs */
const char* gost94_tests[] = {
	"", "CE85B99CC46752FFFEE35CAB9A7B0278ABB4C2D2055CFF685AF4912C49490F8D",
	"a", "D42C539E367C66E9C88A801F6649349C21871B4344C6A573F849FDCE62F314DD",
	"abc", "F3134348C44FB1B2A277729E2285EBB5CB5E0F29C975BC753B70497C06A4D51D",
	"message digest", "AD4434ECB18F2C99B60CBE59EC3D2469582B65273F48DE72DB2FDE16A4889A4D",
	"The quick brown fox jumps over the lazy dog", "77B7FA410C9AC58A25F49BCA7D0468C9296529315EACA76BD1A10F376D1F4294",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "95C1AF627C356496D80274330B2CFF6A10C67B5F597087202F94D06D2338CF8E",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "CC178DCAD4DF619DCAA00AAC79CA355C00144E4ADA2793D7BD9B3518EAD3CCD3",
	"UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "53A3A3ED25180CEF0C1D85A074273E551C25660A87062A52D926A9E8FE5733A4",
	/* two test strings from GOST standard */
	"This is message, length=32 bytes", "B1C466D37519B82E8319819FF32595E047A28CB6F83EFF1C6916A815A637FFFA",
	"Suppose the original message has length = 50 bytes", "471ABA57A60A770D3A76130635C1FBEA4EF14DE51F78B4AE57DD893B62F55208",
	"The quick brown fox jumps over the lazy cog", "A3EBC4DAAAB78B0BE131DAB5737A7F67E602670D543521319150D2E14EEEC445", /* test from Wikipedia */
	0
};

/* tested with openssl */
const char* gost94_cryptopro_tests[] = {
	"", "981E5F3CA30C841487830F84FB433E13AC1101569B9C13584AC483234CD656C0",
	"a", "E74C52DD282183BF37AF0079C9F78055715A103F17E3133CEFF1AACF2F403011",
	"abc", "B285056DBF18D7392D7677369524DD14747459ED8143997E163B2986F92FD42C",
	"message digest", "BC6041DD2AA401EBFA6E9886734174FEBDB4729AA972D60F549AC39B29721BA0",
	"The quick brown fox jumps over the lazy dog", "9004294A361A508C586FE53D1F1B02746765E71B765472786E4770D565830A76",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "73B70A39497DE53A6E08C67B6D4DB853540F03E9389299D9B0156EF7E85D0F61",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "6BC7B38989B28CF93AE8842BF9D752905910A7528A61E5BCE0782DE43E610C90",
	"UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU", "1C4AC7614691BBF427FA2316216BE8F10D92EDFD37CD1027514C1008F649C4E8",
	"This is message, length=32 bytes", "2CEFC2F7B7BDC514E18EA57FA74FF357E7FA17D652C75F69CB1BE7893EDE48EB",
	"Suppose the original message has length = 50 bytes", "C3730C5CBCCACF915AC292676F21E8BD4EF75331D9405E5F1A61DC3130A65011",
	0
};

const char* gost12_256_tests[] = {
	"", "3F539A213E97C802CC229D474C6AA32A825A360B2A933A949FD925208D9CE1BB",
	"a", "BA31099B9CC84EC2A671E9313572378920A705B363B031A1CB4FC03E01CE8DF3",
	"abc", "4E2919CF137ED41EC4FB6270C61826CC4FFFB660341E0AF3688CD0626D23B481",
	"message digest", "0D45451B2004234DE7FBD289B89C665A494FFEFE93C2FF6D6F99677C99086BFF",
	"012345678901234567890123456789012345678901234567890123456789012", "9D151EEFD8590B89DAA6BA6CB74AF9275DD051026BB149A452FD84E5E57B5500",
	"abcdefghijklmnopqrstuvwxyz", "C9086ED61FB0A090AAF4438EFD39F0D060CB3EC7E25343B5C4C350054BFD3E27",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "74F945F366AB17DD1E7D114AB9ADF68B97A8D6A1CBBE299CBA06B77735457F94",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "2C6644B1F5AB3E0AB56ADF1FEEB4D6A8742FCFC61B53B69C3B536AC283AB88AA",
	"The quick brown fox jumps over the lazy dog", "3E7DEA7F2384B6C5A3D0E24AAA29C05E89DDD762145030EC22C71A6DB8B2C1F4",
	0
};

const char* gost12_512_tests[] = {
	"", "8E945DA209AA869F0455928529BCAE4679E9873AB707B55315F56CEB98BEF0A7362F715528356EE83CDA5F2AAC4C6AD2BA3A715C1BCD81CB8E9F90BF4C1C1A8A",
	"012345678901234567890123456789012345678901234567890123456789012", "1B54D01A4AF5B9D5CC3D86D68D285462B19ABC2475222F35C085122BE4BA1FFA00AD30F8767B3A82384C6574F024C311E2A481332B08EF7F41797891C1646F48",
	"a", "8B2A40ECAB7B7496BC4CC0F773595452BAF658849B495ACC3BA017206810EFB00420CCD73FB3297E0F7890941B84AC4A8BC27E3C95E1F97C094609E2136ABB7E",
	"abc", "28156E28317DA7C98F4FE2BED6B542D0DAB85BB224445FCEDAF75D46E26D7EB8D5997F3E0915DD6B7F0AAB08D9C8BEB0D8C64BAE2AB8B3C8C6BC53B3BF0DB728",
	"message digest", "96B52F322E3ECF6348D177608E2DDB084309C1642A94923C0BC50E41E4CC50E851D1DD94E4B7A35C30503CAF87E3E2AC334E2C805ADB99B5ADB5443DD4AC23C8",
	"abcdefghijklmnopqrstuvwxyz", "EC7B127DCCA6B0D741B10ED42062CC4487B4A93F96CFC7FAF2E7F79778B1F44159089C91FB0910BEC0EEE7CDCA524FCF291CF933FFF406F4F3A03872F2341FF8",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "CC49B68C195D18D3FEF26F3D4A6554DB62298B96D19FBEFA52A139E8558D0528535569EBDEA172692857EDE3351C02FE9D749EF7273DCECACA5B3E295511650B",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "116201023E88D93A4D076BA77207E8702C6CFA6FCC69B82BB22AE6BE9B63F16B19BAAF8771E01E6DC25C2B4486FA3BBF8601905762CBBAD5BA25A1E034879192",
	"The quick brown fox jumps over the lazy dog", "D2B793A0BB6CB5904828B5B6DCFB443BB8F33EFC06AD09368878AE4CDC8245B97E60802469BED1E7C21A64FF0B179A6A1E0BB74D92965450A0ADAB69162C00FE",
	0
};

/* test vectors verified by mhash */
const char* snefru256_tests[] = {
	"", "8617F366566A011837F4FB4BA5BEDEA2B892F3ED8B894023D16AE344B2BE5881",
	"a", "45161589AC317BE0CEBA70DB2573DDDA6E668A31984B39BF65E4B664B584C63D",
	"abc", "7D033205647A2AF3DC8339F6CB25643C33EBC622D32979C4B612B02C4903031B",
	"message digest", "C5D4CE38DAA043BDD59ED15DB577500C071B917C1A46CD7B4D30B44A44C86DF8",
	"abcdefghijklmnopqrstuvwxyz", "9304BB2F876D9C4F54546CF7EC59E0A006BEAD745F08C642F25A7C808E0BF86E",
	"The quick brown fox jumps over the lazy dog", "674CAA75F9D8FD2089856B95E93A4FB42FA6C8702F8980E11D97A142D76CB358",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "83AA9193B62FFD269FAA43D31E6AC2678B340E2A85849470328BE9773A9E5728",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "D5FCE38A152A2D9B83AB44C29306EE45AB0AED0E38C957EC431DAB6ED6BB71B8",
	0
};

/* test vectors verified by mhash */
const char* snefru128_tests[] = {
	"", "8617F366566A011837F4FB4BA5BEDEA2",
	"a", "BF5CE540AE51BC50399F96746C5A15BD",
	"abc", "553D0648928299A0F22A275A02C83B10",
	"message digest", "96D6F2F4112C4BAF29F653F1594E2D5D",
	"abcdefghijklmnopqrstuvwxyz", "7840148A66B91C219C36F127A0929606",
	"The quick brown fox jumps over the lazy dog", "59D9539D0DD96D635B5BDBD1395BB86C",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "0EFD7F93A549F023B79781090458923E",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "D9204ED80BB8430C0B9C244FE485814A",
	0
};

/* checked against test vectors: http://www.randombit.net/text/has160.html */
const char* has160_tests[] = {
	"", "307964EF34151D37C8047ADEC7AB50F4FF89762D",
	"a", "4872BCBC4CD0F0A9DC7C2F7045E5B43B6C830DB8",
	"abc", "975E810488CF2A3D49838478124AFCE4B1C78804",
	"message digest", "2338DBC8638D31225F73086246BA529F96710BC6",
	"abcdefghijklmnopqrstuvwxyz", "596185C9AB6703D0D0DBB98702BC0F5729CD1D3C",
	"The quick brown fox jumps over the lazy dog", "ABE2B8C711F9E8579AA8EB40757A27B4EF14A7EA",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "CB5D7EFBCA2F02E0FB7167CABB123AF5795764E5",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "07F05C8C0773C55CA3A5A695CE6ACA4C438911B5",
	0
};

/* unconfirmed test vectors */
const char* sha224_tests[] = {
	"", "D14A028C2A3A2BC9476102BB288234C415A2B01F828EA62AC5B3E42F",
	"a", "ABD37534C7D9A2EFB9465DE931CD7055FFDB8879563AE98078D6D6D5",
	"abc", "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7",
	"message digest", "2CB21C83AE2F004DE7E81C3C7019CBCB65B71AB656B22D6D0C39B8EB",
	"abcdefghijklmnopqrstuvwxyz", "45A5F72C39C5CFF2522EB3429799E49E5F44B356EF926BCF390DCCC2",
	"The quick brown fox jumps over the lazy dog", "730E109BD7A8A32B1CB9D9A09AA2325D2430587DDBC0C38BAD911525",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "75388B16512776CC5DBA5DA1FD890150B0C6455CB4F58B1952522525",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "BFF72B4FCB7D75E5632900AC5F90D219E05E97A7BDE72E740DB393D9",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "B50AECBE4E9BB0B57BC5F3AE760A8E01DB24F203FB3CDCD13148046E",
	0
};

/* test vectors from the NESSIE project */
const char* sha256_tests[] = {
	"", "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
	"a", "CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB",
	"abc", "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD",
	"message digest", "F7846F55CF23E14EEBEAB5B4E1550CAD5B509E3348FBC4EFA3A1413D393CB650",
	"abcdefghijklmnopqrstuvwxyz", "71C480DF93D6AE2F1EFAD1447C66C9525E316218CF51FC8D9ED832F2DAF18B73",
	"The quick brown fox jumps over the lazy dog", "D7A8FBB307D7809469CA9ABCB0082E4F8D5651E46D3CDB762D02D0BF37C9E592",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "DB4BFCBD4DA0CD85A60C3C37D3FBD8805C77F15FC6B1FDFE614EE0A7C8FDB4C0",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "F371BC4A311F2B009EEF952DD83CA80E2B60026C8E935592D0F9C308453C813E",
	0
};

const char* sha384_tests[] = {
	"", "38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898B95B",
	"a", "54A59B9F22B0B80880D8427E548B7C23ABD873486E1F035DCE9CD697E85175033CAA88E6D57BC35EFAE0B5AFD3145F31",
	"abc", "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1631A8B605A43FF5BED8086072BA1E7CC2358BAECA134C825A7",
	"message digest", "473ED35167EC1F5D8E550368A3DB39BE54639F828868E9454C239FC8B52E3C61DBD0D8B4DE1390C256DCBB5D5FD99CD5",
	"abcdefghijklmnopqrstuvwxyz", "FEB67349DF3DB6F5924815D6C3DC133F091809213731FE5C7B5F4999E463479FF2877F5F2936FA63BB43784B12F3EBB4",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "3391FDDDFC8DC7393707A65B1B4709397CF8B1D162AF05ABFE8F450DE5F36BC6B0455A8520BC4E6F5FE95B1FE3C8452B",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "1761336E3F7CBFE51DEB137F026F89E01A448E3B1FAFA64039C1464EE8732F11A5341A6F41E0C202294736ED64DB1A84",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "B12932B0627D1C060942F5447764155655BD4DA0C9AFA6DD9B9EF53129AF1B8FB0195996D2DE9CA0DF9D821FFEE67026",
	0
};

const char* sha512_tests[] = {
	"", "CF83E1357EEFB8BDF1542850D66D8007D620E4050B5715DC83F4A921D36CE9CE47D0D13C5D85F2B0FF8318D2877EEC2F63B931BD47417A81A538327AF927DA3E",
	"a", "1F40FC92DA241694750979EE6CF582F2D5D7D28E18335DE05ABC54D0560E0F5302860C652BF08D560252AA5E74210546F369FBBBCE8C12CFC7957B2652FE9A75",
	"abc", "DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A274FC1A836BA3C23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F",
	"message digest", "107DBF389D9E9F71A3A95F6C055B9251BC5268C2BE16D6C13492EA45B0199F3309E16455AB1E96118E8A905D5597B72038DDB372A89826046DE66687BB420E7C",
	"abcdefghijklmnopqrstuvwxyz", "4DBFF86CC2CA1BAE1E16468A05CB9881C97F1753BCE3619034898FAA1AABE429955A1BF8EC483D7421FE3C1646613A59ED5441FB0F321389F77F48A879C7B1F1",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "204A8FC6DDA82F0A0CED7BEB8E08A41657C16EF468B228A8279BE331A703C33596FD15C13B1B07F9AA1D3BEA57789CA031AD85C7A71DD70354EC631238CA3445",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "1E07BE23C26A86EA37EA810C8EC7809352515A970E9253C26F536CFC7A9996C45C8370583E0A78FA4A90041D71A4CEAB7423F19C71B9D5A3E01249F0BEBD5894",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "72EC1EF1124A45B047E8B7C75A932195135BB61DE24EC0D1914042246E0AEC3A2354E093D76F3048B456764346900CB130D2A4FD5DD16ABB5E30BCB850DEE843",
	0
};

/* SHA3 test vectors were verified by the reference implementation of Keccak */
const char* sha3_224_tests[] = {
	"", "6B4E03423667DBB73B6E15454F0EB1ABD4597F9A1B078E3F5B5A6BC7",
	"a", "9E86FF69557CA95F405F081269685B38E3A819B309EE942F482B6A8B",
	"abc", "E642824C3F8CF24AD09234EE7D3C766FC9A3A5168D0C94AD73B46FDF",
	"message digest", "18768BB4C48EB7FC88E5DDB17EFCF2964ABD7798A39D86A4B4A1E4C8",
	"abcdefghijklmnopqrstuvwxyz", "5CDECA81E123F87CAD96B9CBA999F16F6D41549608D4E0F4681B8239",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "A67C289B8250A6F437A20137985D605589A8C163D45261B15419556E",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "0526898E185869F91B3E2A76DD72A15DC6940A67C8164A044CD25CC8",
	"The quick brown fox jumps over the lazy dog", "D15DADCEAA4D5D7BB3B48F446421D542E08AD8887305E28D58335795",
	0
};
const char* sha3_256_tests[] = {
	"", "A7FFC6F8BF1ED76651C14756A061D662F580FF4DE43B49FA82D80A4B80F8434A",
	"a", "80084BF2FBA02475726FEB2CAB2D8215EAB14BC6BDD8BFB2C8151257032ECD8B",
	"abc", "3A985DA74FE225B2045C172D6BD390BD855F086E3E9D525B46BFE24511431532",
	"message digest", "EDCDB2069366E75243860C18C3A11465ECA34BCE6143D30C8665CEFCFD32BFFD",
	"abcdefghijklmnopqrstuvwxyz", "7CAB2DC765E21B241DBC1C255CE620B29F527C6D5E7F5F843E56288F0D707521",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "A79D6A9DA47F04A3B9A9323EC9991F2105D4C78A7BC7BEEB103855A7A11DFB9F",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "293E5CE4CE54EE71990AB06E511B7CCD62722B1BEB414F5FF65C8274E0F5BE1D",
	"The quick brown fox jumps over the lazy dog", "69070DDA01975C8C120C3AADA1B282394E7F032FA9CF32F4CB2259A0897DFC04",
	0
};
const char* sha3_384_tests[] = {
	"", "0C63A75B845E4F7D01107D852E4C2485C51A50AAAA94FC61995E71BBEE983A2AC3713831264ADB47FB6BD1E058D5F004",
	"a", "1815F774F320491B48569EFEC794D249EEB59AAE46D22BF77DAFE25C5EDC28D7EA44F93EE1234AA88F61C91912A4CCD9",
	"abc", "EC01498288516FC926459F58E2C6AD8DF9B473CB0FC08C2596DA7CF0E49BE4B298D88CEA927AC7F539F1EDF228376D25",
	"message digest", "D9519709F44AF73E2C8E291109A979DE3D61DC02BF69DEF7FBFFDFFFE662751513F19AD57E17D4B93BA1E484FC1980D5",
	"abcdefghijklmnopqrstuvwxyz", "FED399D2217AAF4C717AD0C5102C15589E1C990CC2B9A5029056A7F7485888D6AB65DB2370077A5CADB53FC9280D278F",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "D5B972302F5080D0830E0DE7B6B2CF383665A008F4C4F386A61112652C742D20CB45AA51BD4F542FC733E2719E999291",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "3C213A17F514638ACB3BF17F109F3E24C16F9F14F085B52A2F2B81ADC0DB83DF1A58DB2CE013191B8BA72D8FAE7E2A5E",
	"The quick brown fox jumps over the lazy dog", "7063465E08A93BCE31CD89D2E3CA8F602498696E253592ED26F07BF7E703CF328581E1471A7BA7AB119B1A9EBDF8BE41",
	0
};
const char* sha3_512_tests[] = {
	"", "A69F73CCA23A9AC5C8B567DC185A756E97C982164FE25859E0D1DCC1475C80A615B2123AF1F5F94C11E3E9402C3AC558F500199D95B6D3E301758586281DCD26",
	"a", "697F2D856172CB8309D6B8B97DAC4DE344B549D4DEE61EDFB4962D8698B7FA803F4F93FF24393586E28B5B957AC3D1D369420CE53332712F997BD336D09AB02A",
	"abc", "B751850B1A57168A5693CD924B6B096E08F621827444F70D884F5D0240D2712E10E116E9192AF3C91A7EC57647E3934057340B4CF408D5A56592F8274EEC53F0",
	"message digest", "3444E155881FA15511F57726C7D7CFE80302A7433067B29D59A71415CA9DD141AC892D310BC4D78128C98FDA839D18D7F0556F2FE7ACB3C0CDA4BFF3A25F5F59",
	"abcdefghijklmnopqrstuvwxyz", "AF328D17FA28753A3C9F5CB72E376B90440B96F0289E5703B729324A975AB384EDA565FC92AADED143669900D761861687ACDC0A5FFA358BD0571AAAD80ACA68",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "D1DB17B4745B255E5EB159F66593CC9C143850979FC7A3951796ABA80165AAB536B46174CE19E3F707F0E5C6487F5F03084BC0EC9461691EF20113E42AD28163",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "9524B9A5536B91069526B4F6196B7E9475B4DA69E01F0C855797F224CD7335DDB286FD99B9B32FFE33B59AD424CC1744F6EB59137F5FB8601932E8A8AF0AE930",
	"The quick brown fox jumps over the lazy dog", "01DEDD5DE4EF14642445BA5F5B97C15E47B9AD931326E4B0727CD94CEFC44FFF23F07BF543139939B49128CAF436DC1BDEE54FCB24023A08D9403F9B4BF0D450",
	0
};

#ifdef USE_KECCAK
const char* keccak_224_tests[] = {
	"", "F71837502BA8E10837BDD8D365ADB85591895602FC552B48B7390ABD",
	"a", "7CF87D912EE7088D30EC23F8E7100D9319BFF090618B439D3FE91308",
	"abc", "C30411768506EBE1C2871B1EE2E87D38DF342317300A9B97A95EC6A8",
	"message digest", "B53B2CD638F440FA49916036ACDB22245673992FB1B1963B96FB9E93",
	"abcdefghijklmnopqrstuvwxyz", "162BAB64DC3BA594BD3B43FD8ABEC4AA03B36C2784CAC53A58F9B076",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "4FB72D7B6B24BD1F5D4B8EF559FD9188EB66CAA01BCE34C621A05412",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "744C1765A53043E186BC30BAB07FA379B421CF0BCA8224CB83E5D45B",
	"The quick brown fox jumps over the lazy dog", "310AEE6B30C47350576AC2873FA89FD190CDC488442F3EF654CF23FE",
	0
};

const char* keccak_256_tests[] = {
	"", "C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470",
	"a", "3AC225168DF54212A25C1C01FD35BEBFEA408FDAC2E31DDD6F80A4BBF9A5F1CB",
	"abc", "4E03657AEA45A94FC7D47BA826C8D667C0D1E6E33A64A036EC44F58FA12D6C45",
	"message digest", "856AB8A3AD0F6168A4D0BA8D77487243F3655DB6FC5B0E1669BC05B1287E0147",
	"abcdefghijklmnopqrstuvwxyz", "9230175B13981DA14D2F3334F321EB78FA0473133F6DA3DE896FEB22FB258936",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "6E61C013AEF4C6765389FFCD406DD72E7E061991F4A3A8018190DB86BD21EBB4",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "1523A0CD0E7E1FAABA17E1C12210FABC49FA99A7ABC061E3D6C978EEF4F748C4",
	"The quick brown fox jumps over the lazy dog", "4D741B6F1EB29CB2A9B9911C82F56FA8D73B04959D3D9D222895DF6C0B28AA15",
	0
};

const char* keccak_384_tests[] = {
	"", "2C23146A63A29ACF99E73B88F8C24EAA7DC60AA771780CCC006AFBFA8FE2479B2DD2B21362337441AC12B515911957FF",
	"a", "85E964C0843A7EE32E6B5889D50E130E6485CFFC826A30167D1DC2B3A0CC79CBA303501A1EEABA39915F13BAAB5ABACF",
	"abc", "F7DF1165F033337BE098E7D288AD6A2F74409D7A60B49C36642218DE161B1F99F8C681E4AFAF31A34DB29FB763E3C28E",
	"message digest", "8A377DB088C43E44040A2BFB26676704999D90527913CABFF0A3484825DAA54D3061E67DA7D836A0805356962AF310E8",
	"abcdefghijklmnopqrstuvwxyz", "C5A708EC2178D8C398461547435E482CEE0D85DE3D75DDBFF54E6606A7E9F994F023A6033B2BF4C516A5F71FC7470D1A",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "7377C5707506575C26937F3DF0D44A773F8C7452C074EE1725C1AB62F741F95059459D64CAEBF35A7C247FE28616CAB6",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "FD6E89CBE3271545F94C3E6786803260F929C1589E3091AFD58CF32EF53A4F29B69C1166CB2982E2CB65CF5EB903E669",
	"The quick brown fox jumps over the lazy dog", "283990FA9D5FB731D786C5BBEE94EA4DB4910F18C62C03D173FC0A5E494422E8A0B3DA7574DAE7FA0BAF005E504063B3",
	0
};

const char* keccak_512_tests[] = {
	"", "0EAB42DE4C3CEB9235FC91ACFFE746B29C29A8C366B7C60E4E67C466F36A4304C00FA9CAF9D87976BA469BCBE06713B435F091EF2769FB160CDAB33D3670680E",
	"a", "9C46DBEC5D03F74352CC4A4DA354B4E9796887EEB66AC292617692E765DBE400352559B16229F97B27614B51DBFBBB14613F2C10350435A8FEAF53F73BA01C7C",
	"abc", "18587DC2EA106B9A1563E32B3312421CA164C7F1F07BC922A9C83D77CEA3A1E5D0C69910739025372DC14AC9642629379540C17E2A65B19D77AA511A9D00BB96",
	"message digest", "CCCC49FA63822B00004CF6C889B28A035440FFB3EF50E790599935518E2AEFB0E2F1839170797F7763A5C43B2DCF02ABF579950E36358D6D04DFDDC2ABAC7545",
	"abcdefghijklmnopqrstuvwxyz", "E55BDCA64DFE33F36AE3153C727833F9947D92958073F4DD02E38A82D8ACB282B1EE1330A68252A54C6D3D27306508CA765ACD45606CAEAF51D6BDC459F551F1",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "D5FA6B93D54A87BBDE52DBB44DAF96A3455DAEF9D60CDB922BC4B72A5BBBA97C5BF8C59816FEDE302FC64E98CE1B864DF7BE671C968E43D1BAE23AD76A3E702D",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "BC08A9A245E99F62753166A3226E874896DE0914565BEE0F8BE29D678E0DA66C508CC9948E8AD7BE78EAA4EDCED482253F8AB2E6768C9C8F2A2F0AFFF083D51C",
	"The quick brown fox jumps over the lazy dog", "D135BB84D0439DBAC432247EE573A23EA7D3C9DEB2A968EB31D47C4FB45F1EF4422D6C531B5B9BD6F449EBCC449EA94D0A8F05F62130FDA612DA53C79659F609",
	0
};
#endif /* USE_KECCAK */

/* verified by eBASH SUPERCOP implementation */
const char* edonr256_tests[] = {
	"", "86E7C84024C55DBDC9339B395C95E88DB8F781719851AD1D237C6E6A8E370B80",
	"a", "943AA9225A2CF154EC2E4DD81237720BA538CA8DF2FD83C0B893C5D265F353A0",
	"abc", "0360F65D97C2152EA6EBE3D462BF49831E2D5F67B6140992320585D89FD271CE",
	"message digest", "8D27558F4DD9307614A8166CADB136927D1E79A0C04BD8EF77C3FAFC0917E28A",
	"abcdefghijklmnopqrstuvwxyz", "5415737AF0D827459EFACB7FE33C0E89CF807E6E608A4D70EF9DEB07BF3BF6BF",
	"The quick brown fox jumps over the lazy dog", "E77A5AC00923B86C1811D42F1CB1198F43412A6D987DC98BDAE11E6D91399609",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "19DE86BC3F0481098A3E623AA1330995043300A9A5D6C2AD584705F62686417F",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "3B57F954420F49FAC6A80CE6CE013FDB47E71CE824DA78A8F66864203D8EF252",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "286F39D5168775C8E541ED2F0FE3ECF3146380B9C479DE41BD847E866420A776",
	0
};

/* verified by eBASH SUPERCOP implementation */
const char* edonr512_tests[] = {
	"", "C7AFBDF3E5B4590EB0B25000BF83FB16D4F9B722EE7F9A2DC2BD382035E8EE38D6F6F15C7B8EEC85355AC59AF989799950C64557EAB0E687D0FCBDBA90AE9704",
	"a", "B59EC44F7BEEF8A04CEED38A973D77C65E22E9458D5F67B497948DA34986C093B5EFC5483FBEE55F2F740FCAD31F18D80DB44BB6B8843E7FD599188E7C07233B",
	"abc", "FE79BCFA310245D9139DA8BC91B99FD022326F7F3ACA1DFDFB6C84E4125D71FE9BB6A1D41AFCE358F8472835220A7829D5146B2BBFC8E5C2627F60A9B517C1A4",
	"message digest", "A76B6C5CA8778F39EC1F85D64BADBDBF329725C9A6FB92656D94A82922A26FD51D271A6F135F33157143B960CD8D7D20DC99503AA39871FD64050E061689E4E3",
	"abcdefghijklmnopqrstuvwxyz", "754640B7B01782C1F345A3864B456DB805E39163FA1A06113A37CB8FB18D30F8DC43C7C3FDB407849CAD437C90DBD28E28AEFEF8898589B388ADEBA153B3DE0B",
	"The quick brown fox jumps over the lazy dog", "B986ADABFA9ADB1E5B152B6D64C733389082E354FDE2FD9740FAEA6766F440EA4391FC745BB9B11A821756944077BB30723F616645492C70FA4C614DB7E9D45B",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "EE5EF974E8677636970A50E7636EC34EFB1F9D8023C715A26747D73D3665D78D2BB4962381901F76892A630133D476A278E4E3C62176FCE1563904636284415B",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "0755F846450A6F84001846E1066828727BF5975383867B87E0120F27B79482524EB01137459185F73C24C23BDD9D901AD1577C3EA1A824E6ACE34BBBA119E92F",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "0998912DA5B13FC5D7332CBC3B240E44547CE9C861867D901DD39D5A43D2EE80686BC4AD70DFF9159FE12CE94255AD5467B2B59D31562FC08B3697B67323075F",
	0
};

/* verified by b2sum utility */
const char* blake2s_tests[] = {
	"", "69217A3079908094E11121D042354A7C1F55B6482CA1A51E1B250DFD1ED0EEF9",
	"a", "4A0D129873403037C2CD9B9048203687F6233FB6738956E0349BD4320FEC3E90",
	"abc", "508C5E8C327C14E2E1A72BA34EEB452F37458B209ED63A294D999B4C86675982",
	"message digest", "FA10AB775ACF89B7D3C8A6E823D586F6B67BDBAC4CE207FE145B7D3AC25CD28C",
	"abcdefghijklmnopqrstuvwxyz", "BDF88EB1F86A0CDF0E840BA88FA118508369DF186C7355B4B16CF79FA2710A12",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "C75439EA17E1DE6FA4510C335DC3D3F343E6F9E1CE2773E25B4174F1DF8B119B",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "FDAEDB290A0D5AF9870864FEC2E090200989DC9CD53A3C092129E8535E8B4F66",
	"The quick brown fox jumps over the lazy dog", "606BEEEC743CCBEFF6CBCDF5D5302AA855C256C29B88C8ED331EA1A6BF3C8812",
	0
};

/* verified by b2sum utility */
const char* blake2b_tests[] = {
	"", "786A02F742015903C6C6FD852552D272912F4740E15847618A86E217F71F5419D25E1031AFEE585313896444934EB04B903A685B1448B755D56F701AFE9BE2CE",
	"a", "333FCB4EE1AA7C115355EC66CEAC917C8BFD815BF7587D325AEC1864EDD24E34D5ABE2C6B1B5EE3FACE62FED78DBEF802F2A85CB91D455A8F5249D330853CB3C",
	"abc", "BA80A53F981C4D0D6A2797B69F12F6E94C212F14685AC4B74B12BB6FDBFFA2D17D87C5392AAB792DC252D5DE4533CC9518D38AA8DBF1925AB92386EDD4009923",
	"message digest", "3C26CE487B1C0F062363AFA3C675EBDBF5F4EF9BDC022CFBEF91E3111CDC283840D8331FC30A8A0906CFF4BCDBCD230C61AAEC60FDFAD457ED96B709A382359A",
	"abcdefghijklmnopqrstuvwxyz", "C68EDE143E416EB7B4AAAE0D8E48E55DD529EAFED10B1DF1A61416953A2B0A5666C761E7D412E6709E31FFE221B7A7A73908CB95A4D120B8B090A87D1FBEDB4C",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "99964802E5C25E703722905D3FB80046B6BCA698CA9E2CC7E49B4FE1FA087C2EDF0312DFBB275CF250A1E542FD5DC2EDD313F9C491127C2E8C0C9B24168E2D50",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "686F41EC5AFFF6E87E1F076F542AA466466FF5FBDE162C48481BA48A748D842799F5B30F5B67FC684771B33B994206D05CC310F31914EDD7B97E41860D77D282",
	"The quick brown fox jumps over the lazy dog", "A8ADD4BDDDFD93E4877D2746E62817B116364A1FA7BC148D95090BC7333B3673F82401CF7AA2E4CB1ECD90296E3F14CB5413F8ED77BE73045B13914CDCD6A918",
	0
};

/* verified by b3sum from github.com/BLAKE3-team/BLAKE3 */
const char* blake3_tests[] = {
	"", "AF1349B9F5F9A1A6A0404DEA36DCC9499BCB25C9ADC112B7CC9A93CAE41F3262",
	"a", "17762FDDD969A453925D65717AC3EEA21320B66B54342FDE15128D6CAF21215F",
	"abc", "6437B3AC38465133FFB63B75273A8DB548C558465D79DB03FD359C6CD5BD9D85",
	"message digest", "7BC2A2EEB95DDBF9B7ECF6ADCB76B453091C58DC43955E1D9482B1942F08D19B",
	"abcdefghijklmnopqrstuvwxyz", "2468EEC8894ACFB4E4DF3A51EA916BA115D48268287754290AAE8E9E6228E85F",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "8BEE3200BAA9F3A1ACD279F049F914F110E730555FF15109BD59CDD73895E239",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "F263ACF51621980B9C8DE5DA4A17D314984E05ABE4A21CC83A07FE3E1E366DD1",
	"The quick brown fox jumps over the lazy dog", "2F1514181AADCCD913ABD94CFA592701A5686AB23F8DF1DFF1B74710FEBC6D4A",
	0
};

/* BTIH calculated with filename = "test.txt", verified using uTorrent */
const char* btih_with_filename_tests[] = {
	"", "042C8E2D2780B0AFAE6599A02914D6C3F1515B12",
	"a", "7527A903193C87093C05DE0F0F81126A4B98EE1A",
	"abc", "CBF4F6D5CCDE0E6DD6BC8F013AA7F920900C11A2",
	"message digest", "FFFCE897C2D5FB8ED4B6AD773CC0FFA071AEC393",
	"abcdefghijklmnopqrstuvwxyz", "606A31B06B17547C226C9EA8EE00EEBA6E0E5BFC",
	"The quick brown fox jumps over the lazy dog", "1EED1B4C56186456E3DE420FE69A67F53CA6A52A",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "6BD6F0B19FA3F54CE0311BF6D2D6D3955B1BD20C",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "95880C5A8EB06C1AC28BA6A531505E0F2BCD77AE",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "674E17AA21981A33892119E601DF3E2E689C4E62",
	0
};

/* BTIH calculated without a filename, can't be verified by torrent tools */
const char* btih_without_a_filename_tests[] = {
	"", "A4A6678B3A933D1D9A182CC38E73124F7672C7EB",
	"a", "827CD89846FC132E2E67E29C2784C65443BB4DC1",
	"abc", "88713704608141F4E86F58C86247CA1E3D91D864",
	"message digest", "C654901E82A8FC13C343271520FAF52EBEEF2183",
	"abcdefghijklmnopqrstuvwxyz", "4AE72ED186D196D8F117E449D34E216B0941FF61",
	"The quick brown fox jumps over the lazy dog", "A9328020229C163BBF07181C8DF37CE84FC66589",
	"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "DB9A7E577A346FF058D78576102F2EB9DC849018",
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "7F8FEED959E89BB097B5F741C0342B6DF0A7864D",
	"12345678901234567890123456789012345678901234567890123456789012345678901234567890", "92D6F01285FDA7D43E7D09A5AFF01C383CEEE610",
	0
};

/** Set of test vectors for one hash function */
struct test_vectors_t {
	/** Hash function id */
	unsigned hash_id;
	/** Array of pairs (message, message_digest) */
	const char** tests;
};

/**
 * Array of test vectors for short messages
 */
struct test_vectors_t short_test_vectors[] = {
	{ RHASH_CRC32, crc32_tests },
	{ RHASH_CRC32C, crc32c_tests },
	{ RHASH_MD4, md4_tests },
	{ RHASH_MD5, md5_tests },
	{ RHASH_SHA1, sha1_tests },
	{ RHASH_TIGER, tiger_tests },
	{ RHASH_TTH, tth_tests },
	{ RHASH_BTIH, btih_with_filename_tests },
	{ RHASH_BTIH, btih_without_a_filename_tests },
	{ RHASH_ED2K, ed2k_tests },
	{ RHASH_AICH, aich_tests },
	{ RHASH_WHIRLPOOL, whirlpool_tests },
	{ RHASH_RIPEMD160, ripemd_tests },
	{ RHASH_GOST12_256, gost12_256_tests },
	{ RHASH_GOST12_512, gost12_512_tests },
	{ RHASH_GOST94, gost94_tests },
	{ RHASH_GOST94_CRYPTOPRO, gost94_cryptopro_tests },
	{ RHASH_HAS160, has160_tests },
	{ RHASH_SNEFRU128, snefru128_tests },
	{ RHASH_SNEFRU256, snefru256_tests },
	{ RHASH_SHA224, sha224_tests },
	{ RHASH_SHA256, sha256_tests },
	{ RHASH_SHA384, sha384_tests },
	{ RHASH_SHA512, sha512_tests },
	{ RHASH_SHA3_224, sha3_224_tests },
	{ RHASH_SHA3_256, sha3_256_tests },
	{ RHASH_SHA3_384, sha3_384_tests },
	{ RHASH_SHA3_512, sha3_512_tests },
	{ RHASH_EDONR256, edonr256_tests },
	{ RHASH_EDONR512, edonr512_tests },
	{ RHASH_BLAKE2S, blake2s_tests },
	{ RHASH_BLAKE2B, blake2b_tests },
	{ RHASH_BLAKE3, blake3_tests },
	{ 0, 0 }
};

/*=========================================================================*
 *                         Debug output and logging                        *
 *=========================================================================*/
static int g_verbose = 0;

static void log_va(int level, const char* format, va_list args)
{
	if (g_verbose >= level) {
		vprintf(format, args);
		fflush(stdout);
	}
}

/**
 * Print a formatted debug message.
 * The message is shown only in verbose mode (-v).
 * @param format the format of the message
 */
static void dbg(const char* format, ...)
{
	va_list vl;
	va_start(vl, format);
	log_va(1, format, vl);
	va_end(vl);
}

/**
 * Print a formatted debug message.
 * The message is shown only in very verbose mode (-v -v).
 * @param format the format of the message
 */
static void dbg2(const char* format, ...)
{
	va_list vl;
	va_start(vl, format);
	log_va(2, format, vl);
	va_end(vl);
}

/**
 * The total number of errors
 */
static int g_errors_count = 0;

/**
 * Print a formatted error message.
 * @param line the line number of the reported error
 * @param format the format of the message
 */
static void log_error_impl(int line, const char* format, ...)
{
	va_list vl;
	va_start(vl, format);
	printf("%s:%d: error: ", __FILE__, line);
	log_va(0, format, vl);
	va_end(vl);
	g_errors_count++;
}

#define log_error(msg) \
	(log_error_impl(__LINE__, (msg)))
#define log_error1(msg, a) \
	(log_error_impl(__LINE__, (msg), (a)))
#define log_error2(msg, a, b) \
	(log_error_impl(__LINE__, (msg), (a), (b)))
#define log_error3(msg, a, b, c) \
	(log_error_impl(__LINE__, (msg), (a), (b), (c)))
#define log_error4(msg, a, b, c, d) \
	(log_error_impl(__LINE__, (msg), (a), (b), (c), (d)))
#define log_error5(msg, a, b, c, d, e) \
	(log_error_impl(__LINE__, (msg), (a), (b), (c), (d), (e)))

#define CHECK_NOOP {}
#define CHECK_IMPL(failed, cmd, msg) \
	if (failed) { \
		log_error(msg); \
		cmd; \
	}
#define CHECK_EQ(a, b, msg) CHECK_IMPL((a) != (b), CHECK_NOOP, msg)
#define CHECK_NE(a, b, msg) CHECK_IMPL((a) == (b), CHECK_NOOP, msg)
#define CHECK_TRUE(condition, msg) CHECK_IMPL(!(condition), CHECK_NOOP, msg)
#define REQUIRE_EQ(a, b, msg) CHECK_IMPL((a) != (b), return, msg)
#define REQUIRE_NE(a, b, msg) CHECK_IMPL((a) == (b), return, msg)
#define REQUIRE_TRUE(condition, msg) CHECK_IMPL(!(condition), return, msg)


/*=========================================================================*
 *               Functions for calculating a message digest                *
 *=========================================================================*/

enum ChunkedDataBitFlags {
	CHDT_NO_FLAGS = 0,
	CHDT_SET_FILENAME = 1,
	CHDT_REPEAT_SMALL_CHUNK = 2
};

/**
 * Calculate message digest of the data by chunks of the specified size.
 *
 * @param hash_id id of the hash algorithm to use
 * @param data the data to hash
 * @param chunk_size the size of a chunk
 * @param total_size the total size of the data to be hashed
 * @param flags bit flags to control the hashing process
 */
static char* hash_data_by_chunks(unsigned hash_id, const char* data, size_t chunk_size, size_t total_size, unsigned flags)
{
	struct rhash_context* ctx;
	size_t left, size;
	static char out[130];

	if (rhash_get_hash_length(hash_id) >= (int)sizeof(out)) {
		log_error("too big hash length\n");
		return "";
	}

	ctx = rhash_init(hash_id);
	if (!ctx) {
		log_error1("got NULL context for hash_id=0x%08x\n", hash_id);
		return "";
	}
	if ((hash_id == RHASH_BTIH || hash_id == RHASH_ALL_HASHES) && (flags & CHDT_SET_FILENAME)) {
		CHECK_NE(0, rhash_torrent_add_file(ctx, "test.txt", (unsigned long long)total_size),
			"failed to add filename");
	}
	if (!!(flags & CHDT_REPEAT_SMALL_CHUNK)) {
		/* repeat the small chunk of data until the total_size is reached */
		for (left = total_size; left > 0; left -= size) {
			size = (left > chunk_size ? chunk_size : left);
			rhash_update(ctx, (const unsigned char*)data, size);
		}
	} else {
		/* split the long data buffer and hash it by small chunks */
		size_t index;
		for (index = 0, left = total_size; left > 0; index += size, left -= size) {
			size = (left > chunk_size ? chunk_size : left);
			rhash_update(ctx, (const unsigned char*)data + index, size);
		}
	}
	rhash_final(ctx, 0);
	rhash_print(out, ctx, hash_id, RHPR_UPPERCASE);
	rhash_free(ctx);
	return out;
}

/**
 * Test a hash algorithm against a message of given length, consisting
 * of repeated chunks.
 * Report error if calculated hash differs from the expected value.
 *
 * @param hash_id id of the algorithm to test
 * @param data the data to hash
 * @param chunk_size the size of the chunk in bytes
 * @param total_size the total size of the data to be hashed
 * @param hash the expected hash value
 * @param flags bit flags to control the hashing process
 */
static void assert_hash_long_msg(unsigned hash_id, const char* data, size_t chunk_size, size_t total_size, const char* hash, const char* msg_name, unsigned flags)
{
	char* result;
	result = hash_data_by_chunks(hash_id, data, chunk_size, total_size, flags);
	if (strcmp(result, hash) != 0) {
		const char* hash_name = rhash_get_name(hash_id); /* the hash function name */
		if (msg_name)
			log_error4("%s(%s) = %s, expected %s\n", hash_name, msg_name, result, hash);
		else
			log_error4("%s(\"%s\") = %s, expected %s\n", hash_name, data, result, hash);
	}
}

/**
 * Calculate message digest for the data of given size.
 *
 * @param hash_id id of the hash algorithm to use
 * @param data the data to hash
 * @param data_size the size of the data
 * @param flags bit flags to control the hashing process
 */
static char* hash_data(unsigned hash_id, const char* data, size_t data_size, unsigned flags)
{
	return hash_data_by_chunks(hash_id, data, data_size, data_size, flags);
}

/**
 * Calculate message digest of the given message.
 *
 * @param hash_id id of the hash algorithm to use
 * @param str the message to hash
 * @param flags bit flags to control the hashing process
 */
static char* hash_message(unsigned hash_id, const char* str, unsigned flags)
{
	return hash_data(hash_id, str, strlen(str), flags);
}

/**
 * Test a hash algorithm on given message by comparing the result hash value
 * against the expected one. Report error on fail.
 *
 * @param hash_id id of the algorithm to test
 * @param str the message to hash
 * @param expected_hash the expected hash value
 * @param flags bit flags to control the hashing process
 */
static void assert_hash(unsigned hash_id, const char* str, const char* expected_hash, unsigned flags)
{
	size_t length = strlen(str);
	assert_hash_long_msg(hash_id, str, length, length, expected_hash, NULL, flags);
}

/**
 * Test a hash algorithm using a message consisting of given repeated character.
 * Report error if calculated hash doesn't coincide with expected value.
 *
 * @param hash_id id of the algorithm to test
 * @param ch the character the message is filled with
 * @param msg_size the size of message in bytes
 * @param expected_hash the expected hash value
 * @param flags bit flags to control the hashing process
 */
static void assert_rep_hash(unsigned hash_id, char ch, size_t msg_size, const char* hash, unsigned flags)
{
	char ALIGN_ATTR(64) msg_chunk[8192]; /* 8 KiB */
	char msg_name[20];
	memset(msg_chunk, ch, 8192);
	if (ch >= 32)
		sprintf(msg_name, "\"%c\"x%d", ch, (int)msg_size);
	else
		sprintf(msg_name, "\"\\%o\"x%d", (unsigned)(unsigned char)ch, (int)msg_size);
	assert_hash_long_msg(hash_id, msg_chunk, 8192, msg_size, hash, msg_name, flags | CHDT_REPEAT_SMALL_CHUNK);
}

/*=========================================================================*
 *                             Test functions                              *
 *=========================================================================*/

/**
 * Test a hash algorithm on array of known short messages.
 *
 * @param hash_id id of the algorithm to test
 * @param ptr pointer to array of pairs <message,expected-hash>
 * @param flags bit flags to control the hashing process
 */
static void test_known_strings_pairs(unsigned hash_id, const char** ptr, unsigned flags)
{
	for (; ptr[0] && ptr[1]; ptr += 2) {
		assert_hash(hash_id, ptr[0], ptr[1], flags);
	}
}

/**
 * Test a hash algorithm on known short messages.
 *
 * @param hash_id id of the algorithm to test
 */
static void test_known_strings(unsigned hash_id)
{
	int i;
	dbg("test known strings\n");
	for (i = 0; short_test_vectors[i].tests != 0; i++) {
		if (hash_id == short_test_vectors[i].hash_id) {
			unsigned flags = (short_test_vectors[i].tests == btih_with_filename_tests ? CHDT_SET_FILENAME : CHDT_NO_FLAGS);
			test_known_strings_pairs(hash_id, short_test_vectors[i].tests, flags);
			break;
		}
	}
}

/**
 * Verify hash algorithms by testing them against known short messages.
 */
static void test_all_known_strings(void)
{
	int i;
	dbg("test all known strings\n");
	for (i = 0; short_test_vectors[i].tests != 0; i++) {
		unsigned flags = (short_test_vectors[i].tests == btih_with_filename_tests ? CHDT_SET_FILENAME : CHDT_NO_FLAGS);
		test_known_strings_pairs(short_test_vectors[i].hash_id, short_test_vectors[i].tests, flags);
	}
}

/**
 * A pair <algorithm-id, expected-hash-value>.
 */
typedef struct id_to_hash_t {
	int hash_id;
	const char* expected_hash;
} id_to_hash_t;

/**
 * Verify hash algorithms by testing them on long messages, like
 * 1,000,000 charaters of 'a'.
 */
static void test_long_strings(void)
{
	unsigned count;

	struct id_to_hash_t tests[] = {
		{ RHASH_CRC32, "DC25BFBC" }, /* verified with cksfv */
		{ RHASH_CRC32C, "436FE240" },
		{ RHASH_MD4, "BBCE80CC6BB65E5C6745E30D4EECA9A4" }, /* checked by md4sum */
		{ RHASH_MD5, "7707D6AE4E027C70EEA2A935C2296F21" }, /* checked by md5sum */
		{ RHASH_SHA1, "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F" }, /* checked by sha1sum */
		{ RHASH_ED2K, "BBCE80CC6BB65E5C6745E30D4EECA9A4" }, /* checked by eMule' Link Creator (uses eMule algorithm) */
		{ RHASH_AICH, "KSYPATEV3KP26FJYUEEBCPL5LQJ5FGUK" }, /* checked by eMule' Link Creator */
		{ RHASH_TIGER, "6DB0E2729CBEAD93D715C6A7D36302E9B3CEE0D2BC314B41" }, /* from Tiger author's page (NESSIE test vector) */
		{ RHASH_TTH, "KEPTIGT4CQKF7S5EUVNJZSXXIPNMB3XSOAAQS4Y" }, /* verified with Strong DC++ */
		{ RHASH_WHIRLPOOL, "0C99005BEB57EFF50A7CF005560DDF5D29057FD86B20BFD62DECA0F1CCEA4AF51FC15490EDDC47AF32BB2B66C34FF9AD8C6008AD677F77126953B226E4ED8B01" }, /* taken from the algorithm reference */
		{ RHASH_RIPEMD160, "52783243C1697BDBE16D37F97F68F08325DC1528" }, /* taken from the algorithm reference */
		{ RHASH_GOST94_CRYPTOPRO, "8693287AA62F9478F7CB312EC0866B6C4E4A0F11160441E8F4FFCD2715DD554F" }, /* verified with openssl */
		{ RHASH_GOST94, "5C00CCC2734CDD3332D3D4749576E3C1A7DBAF0E7EA74E9FA602413C90A129FA" }, /* verified with openssl */
		{ RHASH_HAS160, "D6AD6F0608B878DA9B87999C2525CC84F4C9F18D" }, /* verified against jacksum implementation */
		{ RHASH_SNEFRU128, "5071F647BC51CFD48F9A8F2D2ED84829" }, /* verified by mhash */
		{ RHASH_SNEFRU256, "4A02811F28C121F2162ABB251A01A2A58E6CFC27534AAB10EA6AF0A8DF17FFBF" }, /* verified by mhash */
		{ RHASH_SHA224, "20794655980C91D8BBB4C1EA97618A4BF03F42581948B2EE4EE7AD67" }, /* verified against jacksum implementation */
		{ RHASH_SHA256, "CDC76E5C9914FB9281A1C7E284D73E67F1809A48A497200E046D39CCC7112CD0" }, /* from NESSIE test vectors */
		{ RHASH_SHA384, "9D0E1809716474CB086E834E310A4A1CED149E9C00F248527972CEC5704C2A5B07B8B3DC38ECC4EBAE97DDD87F3D8985" }, /* from NESSIE test vectors */
		{ RHASH_SHA512, "E718483D0CE769644E2E42C7BC15B4638E1F98B13B2044285632A803AFA973EBDE0FF244877EA60A4CB0432CE577C31BEB009C5C2C49AA2E4EADB217AD8CC09B" }, /* from NESSIE test vectors */
		{ RHASH_SHA3_224, "D69335B93325192E516A912E6D19A15CB51C6ED5C15243E7A7FD653C" },
		{ RHASH_SHA3_256, "5C8875AE474A3634BA4FD55EC85BFFD661F32ACA75C6D699D0CDCB6C115891C1" },
		{ RHASH_SHA3_384, "EEE9E24D78C1855337983451DF97C8AD9EEDF256C6334F8E948D252D5E0E76847AA0774DDB90A842190D2C558B4B8340" },
		{ RHASH_SHA3_512, "3C3A876DA14034AB60627C077BB98F7E120A2A5370212DFFB3385A18D4F38859ED311D0A9D5141CE9CC5C66EE689B266A8AA18ACE8282A0E0DB596C90B0A7B87" },
		{ RHASH_EDONR256, "56F4B8DC0A41C8EA0A6A42C949883CD5DC25DF8CF4E43AD474FD4492A7A07966" }, /* verified by eBASH SUPERCOP implementation */
		{ RHASH_EDONR512, "B4A5A255D67869C990FE79B5FCBDA69958794B8003F01FD11E90FEFEC35F22BD84FFA2E248E8B3C1ACD9B7EFAC5BC66616E234A6E938D3526DEE26BD0DE9C562" }, /* verified by eBASH SUPERCOP implementation */
		{ RHASH_BLAKE2S, "BEC0C0E6CDE5B67ACB73B81F79A67A4079AE1C60DAC9D2661AF18E9F8B50DFA5" }, /* verified by b2sum utility */
		{ RHASH_BLAKE2B, "98FB3EFB7206FD19EBF69B6F312CF7B64E3B94DBE1A17107913975A793F177E1D077609D7FBA363CBBA00D05F7AA4E4FA8715D6428104C0A75643B0FF3FD3EAF" }, /* verified by b2sum utility */
		{ RHASH_BLAKE3, "616F575A1B58D4C9797D4217B9730AE5E6EB319D76EDEF6549B46F4EFE31FF8B" }, /* verified by b3sum utility */
		{ RHASH_GOST12_256, "841AF1A0B2F92A800FB1B7E4AABC8E48763153C448A0FC57C90BA830E130F152" },
		{ RHASH_GOST12_512, "D396A40B126B1F324465BFA7AA159859AB33FAC02DCDD4515AD231206396A266D0102367E4C544EF47D2294064E1A25342D0CD25AE3D904B45ABB1425AE41095" },
#ifdef USE_KECCAK
		{ RHASH_KECCAK_224, "19F9167BE2A04C43ABD0ED554788101B9C339031ACC8E1468531303F" }, /* verified by reference implementation */
		{ RHASH_KECCAK_256, "FADAE6B49F129BBB812BE8407B7B2894F34AECF6DBD1F9B0F0C7E9853098FC96" }, /* verified by reference implementation */
		{ RHASH_KECCAK_384, "0C8324E1EBC182822C5E2A086CAC07C2FE00E3BCE61D01BA8AD6B71780E2DEC5FB89E5AE90CB593E57BC6258FDD94E17" }, /* verified by reference implementation */
		{ RHASH_KECCAK_512, "5CF53F2E556BE5A624425EDE23D0E8B2C7814B4BA0E4E09CBBF3C2FAC7056F61E048FC341262875EBC58A5183FEA651447124370C1EBF4D6C89BC9A7731063BB" }, /* verified by reference implementation */
#endif
		{ RHASH_BTIH, "90AE73EE72A12B5A3A39DCA4C5E24BE1F39B6A1B" } /* BTIH with filename="test.txt", verified using uTorrent */
	};
	dbg("test long strings\n");

	/* test all algorithms on 1,000,000 characters of 'a' */
	for (count = 0; count < RHASH_COUNTOF(tests); count++) {
		unsigned flags = (tests[count].hash_id == RHASH_BTIH ? CHDT_SET_FILENAME : CHDT_NO_FLAGS);
		assert_rep_hash(tests[count].hash_id, 'a', 1000000, tests[count].expected_hash, flags);
	}

	/* BTIH calculated without a filename. The hash value can't be verified by torrent tools */
	assert_rep_hash(RHASH_BTIH, 'a', 1000000, "24742F9AE1BD416CF0A6916F2849FE7ABFAC405E", 0);

	/* now we verify some specific cases, which caused problems in many libraries */
	assert_rep_hash(RHASH_GOST94, '\xFF', 64, "13416C4EC74A63C3EC90CB1748FD462C7572C6C6B41844E48CC1184D1E916098", 0);
	assert_rep_hash(RHASH_GOST94_CRYPTOPRO, '\xFF', 64, "58504D26B3677E756BA3F4A9FD2F14B3BA5457066A4AA1D700659B90DCDDD3C6", 0);

	/* these messages verified by eMule LinkCreator (which uses eMule variant of ED2K hash) */
	assert_rep_hash(RHASH_ED2K, 0, 9728000, "FC21D9AF828F92A8DF64BEAC3357425D", 0);
	assert_rep_hash(RHASH_ED2K, 0, 9728000 - 1, "AC44B93FC9AFF773AB0005C911F8396F", 0);
	assert_rep_hash(RHASH_ED2K, 0, 9728000 + 1, "06329E9DBA1373512C06386FE29E3C65", 0); /* msg with: 9728000 < size <= 9732096 */
	assert_rep_hash(RHASH_AICH, 0, 9728000, "5D3N4HQHIUMQ7IU7A5QLPLI6RHSWOR7B", 0);
	assert_rep_hash(RHASH_AICH, 0, 9728000 - 1, "L6SPMD2CM6PRZBGRQ6UFC4HJFFOATRA4", 0);
	assert_rep_hash(RHASH_AICH, 0, 9728000 + 1, "HL3TFXORIUEPXUWFPY3JLR7SMKGTO4IH", 0);
#if 0
	assert_rep_hash(RHASH_ED2K, 0, 9728000 * 5, "3B613901DABA54F6C0671793E28A1205", 0);
	assert_rep_hash(RHASH_AICH, 0, 9728000 * 5, "EZCO3XF2RJ4FERRDEXGOSSRGL5NA5BBM", 0);
#endif
}

/**
 * Verify for all algorithms, that rhash_final() returns the same result as
 * rhash_print().
 */
static void test_results_consistency(void)
{
	const char* msg = "a";
	size_t msg_size = strlen(msg);

	size_t digest_size;
	struct rhash_context* ctx;
	unsigned char res1[70];
	char res2[70];
	unsigned i, hash_id;
	dbg("test results consistency\n");

	for (i = 0, hash_id = 1; (hash_id & RHASH_ALL_HASHES); hash_id <<= 1, i++) {
		digest_size = rhash_get_digest_size(hash_id);
		REQUIRE_TRUE(digest_size < 70, "too big digest size\n");

		ctx = rhash_init(hash_id);

#ifndef USE_BTIH_WITH_TEST_FILENAME
		if ((hash_id & RHASH_BTIH) != 0) {
			rhash_torrent_add_file(ctx, "test.txt", (unsigned long long)msg_size);
		}
#endif

		rhash_update(ctx, msg, msg_size);
		rhash_final(ctx, res1);
		rhash_print(res2, ctx, hash_id, RHPR_RAW);
		rhash_free(ctx);

		if (memcmp(res1, res2, digest_size) != 0) {
			log_error2("inconsistent %s(\"%s\") hash results\n", rhash_get_name(hash_id), msg);
		}
	}
}

/**
 * Verify that calculated hash doesn't depend on message alignment.
 */
static void test_unaligned_messages_consistency(void)
{
	int start, alignment_size;
	unsigned all_hash_ids[RHASH_HASH_COUNT];
	size_t count = rhash_get_all_algorithms(RHASH_HASH_COUNT, all_hash_ids);
	size_t i;
	dbg("test unaligned messages consistency\n");
	REQUIRE_NE(RHASH_ERROR, count, "failed to get all algorithms\n");

	/* loop by hash algorithms */
	for (i = 0; i < count; i++) {
		unsigned hash_id = all_hash_ids[i];
		char expected_hash[130];
		REQUIRE_NE(0, hash_id, "bad hash_id == 0\n");
		REQUIRE_TRUE(rhash_get_digest_size(hash_id) < (int)sizeof(expected_hash), "too big digest size\n");

		alignment_size = (hash_id & (RHASH_TTH | RHASH_TIGER | RHASH_WHIRLPOOL | RHASH_SHA512) ? 8 : 4);

		/* start message with different alignment */
		for (start = 0; start < alignment_size; start++) {
			char message[30];
			int j, msg_length = 11 + alignment_size;

			/* fill the buffer with the shifted letter sequence */
			for (j = 0; j < msg_length; j++)
				message[start + j] = 'a' + j;
			message[start + j] = 0;

			if (start == 0) {
				strcpy(expected_hash, hash_message(hash_id, message + start, 0)); /* save hash value */
			} else {
				assert_hash(hash_id, message + start, expected_hash, 0); /* verify hash value */
			}
		}
	}
}

/**
 * Test that a message digest of a data block is independent on a chunk size.
 */
static void test_chunk_size_consistency(void)
{
	char buffer[8192];
	unsigned all_hash_ids[RHASH_HASH_COUNT];
	size_t count = rhash_get_all_algorithms(RHASH_HASH_COUNT, all_hash_ids);
	size_t i , j;
	dbg("test chunk size consistency\n");
	REQUIRE_NE(RHASH_ERROR, count, "failed to get all algorithms\n");

	for (i = 0; i < sizeof(buffer); i++)
		buffer[i] = (char)(unsigned char)(i % 255);

	/* loop by hash algorithms */
	for (j = 0; j < count; j++) {
		unsigned hash_id = all_hash_ids[j];
		char expected_hash[130];
		strcpy(expected_hash, hash_data(hash_id, buffer, sizeof(buffer), 0)); /* save hash value */
		for (i = 0; i < 2; i++) {
			int chunk_size = (i == 0 ? 512 : 3);
			char msg_name[40];
			sprintf(msg_name, "8k buffer by chunks of %d bytes", chunk_size);
			assert_hash_long_msg(hash_id, buffer, chunk_size, sizeof(buffer), expected_hash, msg_name, CHDT_NO_FLAGS);
		}
	}
}

/**
 * Verify alignment of a hash function context, which is located inside of rhash context.
 */
static void test_context_alignment(void)
{
	const size_t aligner = 63;
	int i;
	unsigned hash_ids[32];
	dbg("test context alignment\n");

	for (i = 0; i < 32; i++)
	{
		size_t count = i + 1;
		rhash ctx;
		char* context_ptr;
		hash_ids[i] = RHASH_MD5;
		ctx = rhash_init_multi(count, hash_ids);
		REQUIRE_TRUE(ctx, "NULL rhash context\n");
		rhash_get_context(ctx, RHASH_MD5, &context_ptr);
		if (((context_ptr - (char*)0) & aligner) != 0) {
			log_error4("wrong aligment %d of pointer %p for the %2d-th context (rhash = %p)\n",
				(int)((context_ptr - (char*)0) & aligner), context_ptr, i, ctx);
		}
		rhash_free(ctx);
		hash_ids[i] = RHASH_CRC32;
	}
}

/**
 * Verify processor endianness detected at compile time
 * against the actual CPU endianness.
 */
static void test_endianness(void)
{
	unsigned tmp = 1;
	CHECK_EQ(*(char*)&tmp, IS_LITTLE_ENDIAN, "wrong endianness detected at compile time\n");
}

static void test_version_sanity(void)
{
	unsigned version = rhash_get_version();
	if (!(version & 0xff000000) || (version & 0xf0e0e0e0))
		log_error1("wrong librhash version: %x\n", version);
}

/**
 * Run various simple tests.
 */
static void test_generic_assumptions(void)
{
	dbg("test generic assumptions\n");
	if (RHASH_HASH_COUNT < rhash_popcount(RHASH_LOW_HASHES_MASK)) {
		log_error2("wrong algorithms count %d for low mask 0x%x\n", RHASH_HASH_COUNT, RHASH_LOW_HASHES_MASK);
	}
	CHECK_TRUE(RHASH_EXTENDED_BIT & RHASH_ALL_HASHES, "bad RHASH_ALL_HASHES value");
	CHECK_TRUE(!(RHASH_EXTENDED_BIT & RHASH_LOW_HASHES_MASK), "bad RHASH_LOW_HASHES_MASK value");
	test_endianness();
	test_version_sanity();
}

static void check_openssl_ids(size_t count, unsigned* hash_ids)
{
	size_t i;
	for (i = 0; i < count; i++) {
		unsigned id = hash_ids[i];
		if ((id & (id - 1)) != 0 || (id & 0x000f060e) == 0)
			log_error2("got bad openssl algorithm id = %08x (%s)\n", id, rhash_get_name(id));
	}
}

/**
 * Test getting of ids.
 */
static void test_openssl_getters(void)
{
	const size_t total = (size_t)RHASH_HASH_COUNT;
	unsigned hash_ids[RHASH_HASH_COUNT];
	size_t scount, acount, ecount;

	dbg2("- test openssl calls\n");
	scount = rhash_get_openssl_supported(0, NULL);
	REQUIRE_TRUE(scount <= total, "openssl: supported count is greater than the total slgorithm count\n");
	CHECK_EQ(scount, rhash_get_openssl_supported(0, hash_ids), "wrong openssl algorithms count\n");
	CHECK_EQ(scount, rhash_get_openssl_supported(total, NULL), "wrong openssl algorithms count\n");
	if (scount > 1)
		CHECK_EQ(RHASH_ERROR, rhash_get_openssl_supported(1, hash_ids), "RHASH_ERROR expected\n");
	CHECK_EQ(scount, rhash_get_openssl_supported(scount, hash_ids), "wrong openssl algorithms count\n");
	check_openssl_ids(scount, hash_ids);
	acount = rhash_get_openssl_available(0, NULL);
	CHECK_TRUE(acount <= scount, "openssl: available count is greater than supported count\n");
	CHECK_EQ(acount, rhash_get_openssl_available(0, hash_ids), "wrong openssl algorithms count\n");
	CHECK_EQ(acount, rhash_get_openssl_available(total, NULL), "wrong openssl algorithms count\n");
	CHECK_EQ(acount, rhash_get_openssl_available(acount, hash_ids), "wrong openssl algorithms count\n");
	check_openssl_ids(acount, hash_ids);
	ecount = rhash_get_openssl_enabled(0, NULL);
	CHECK_TRUE(ecount <= acount, "openssl: enabled count is greater than available count\n");
	CHECK_EQ(ecount, rhash_get_openssl_enabled(0, hash_ids), "wrong openssl algorithms count\n");
	CHECK_EQ(ecount, rhash_get_openssl_enabled(total, NULL), "wrong openssl algorithms count\n");
	CHECK_EQ(ecount, rhash_get_openssl_enabled(ecount, hash_ids), "wrong openssl algorithms count\n");
	check_openssl_ids(ecount, hash_ids);
	/* test changing the list of enabled openssl algorithms */
	CHECK_EQ(RHASH_ERROR, rhash_set_openssl_enabled(total, NULL), "RHASH_ERROR expected\n");
	CHECK_EQ(ecount, rhash_get_openssl_enabled(0, NULL), "enabled algorithms changed\n");
	REQUIRE_EQ(total, rhash_get_all_algorithms(total, hash_ids), "failed to get all algorithms\n");
	CHECK_EQ(0, rhash_set_openssl_enabled(total, hash_ids), "failed to enable all openssl algorithms\n");
	CHECK_EQ(acount, rhash_get_openssl_enabled(0, NULL), "all available algorithms must be enabled\n");
	CHECK_EQ(0, rhash_set_openssl_enabled(0, NULL), "disabling algorithms must work without ids array\n");
	CHECK_EQ(0, rhash_get_openssl_enabled(0, NULL), "openssl algorithms were not disabled\n");
	REQUIRE_EQ(acount, rhash_get_openssl_available(acount, hash_ids), "failed to get available algorithms\n");
	CHECK_EQ(0, rhash_set_openssl_enabled(acount, hash_ids), "failed to enable all openssl algorithms\n");
	CHECK_EQ(acount, rhash_get_openssl_enabled(0, NULL), "not all available algorithms were anbled\n");
	CHECK_EQ(0, rhash_set_openssl_enabled(0, hash_ids), "failed to disable openssl algorithms\n");
	CHECK_EQ(0, rhash_get_openssl_enabled(0, NULL), "openssl algorithms were not disabled\n");
}

/**
 * Test getting of ids.
 */
static void test_id_getters(void)
{
	const size_t count = (size_t)RHASH_HASH_COUNT;
	unsigned hash_ids[RHASH_HASH_COUNT];
	unsigned hash_ids2[RHASH_HASH_COUNT];
	rhash ctx;
	dbg("test algorithms getters\n");
	CHECK_EQ(count, rhash_get_all_algorithms(0, hash_ids), "incorrect number of supported algorithms\n");
	CHECK_EQ(count, rhash_get_all_algorithms(1, 0), "incorrect number of supported algorithms\n");
	CHECK_EQ(RHASH_ERROR, rhash_get_all_algorithms(1, hash_ids), "RHASH_ERROR expected for small buffer\n");
	CHECK_EQ(RHASH_ERROR, rhash_get_all_algorithms(count - 1, hash_ids), "RHASH_ERROR expected for small buffer\n");
	REQUIRE_EQ(count, rhash_get_all_algorithms(count, hash_ids), "failed to get ids of supported algorithms\n");
	ctx = rhash_init_multi(count, hash_ids);
	REQUIRE_NE(0, ctx, "got invalid context\n");
	CHECK_EQ(count, rhash_get_ctx_algorithms(ctx, 0, hash_ids2), "incorrect number of context algorithms\n");
	CHECK_EQ(count, rhash_get_ctx_algorithms(ctx, 1, 0), "incorrect number of context algorithms\n");
	CHECK_EQ(RHASH_ERROR, rhash_get_ctx_algorithms(ctx, 1, hash_ids2), "RHASH_ERROR expected for small buffer\n");
	CHECK_EQ(RHASH_ERROR, rhash_get_ctx_algorithms(ctx, count - 1, hash_ids2), "RHASH_ERROR expected for small buffer\n");
	REQUIRE_EQ(count, rhash_get_ctx_algorithms(ctx, count, hash_ids2), "failed to get ids of context algorithms\n");
	CHECK_EQ(0, memcmp(hash_ids, hash_ids2, sizeof(hash_ids)), "unexpected algorithms ids\n");
	rhash_free(ctx);

#if defined(USE_OPENSSL) || defined(OPENSSL_RUNTIME)
	REQUIRE_NE(0, rhash_is_openssl_supported(), "openssl must be on\n");
#else
	REQUIRE_EQ(0, rhash_is_openssl_supported(), "openssl must be off\n");
#endif
	test_openssl_getters();
}

static void test_get_context(void)
{
	unsigned hash_ids[2] = { RHASH_CRC32, RHASH_HAS160 };
	void* context_ptr = NULL;
	rhash ctx = rhash_init_multi(2, hash_ids);
	REQUIRE_TRUE(ctx, "NULL rhash context\n");
	CHECK_EQ(RHASH_ERROR, rhash_get_context(ctx, RHASH_MD5, &context_ptr), "RHASH_ERROR expected\n");
	CHECK_EQ(RHASH_ERROR, rhash_get_context(ctx, RHASH_SHA1, &context_ptr), "RHASH_ERROR expected\n");
	CHECK_EQ(0, rhash_get_context(ctx, RHASH_CRC32, &context_ptr), "failed to get context pointer\n");
	REQUIRE_NE(NULL, context_ptr, "NULL context pointer\n");
	CHECK_EQ(0, *(uint32_t*)context_ptr, "wrong crc32 value\n");
	rhash_update(ctx, "abcd", 4);
	CHECK_EQ(0, rhash_get_context(ctx, RHASH_HAS160, &context_ptr), "failed to get context pointer\n");
	REQUIRE_NE(NULL, context_ptr, "NULL context pointer\n");
	CHECK_EQ(0, memcmp("abcd", context_ptr, 4), "wrong message buffer content in hash context\n");
	CHECK_EQ(4, *((uint64_t*)context_ptr + 8), "wrong message length in hash context\n");
	rhash_free(ctx);
}

static void test_import_export(void)
{
#if !defined(NO_IMPORT_EXPORT)
	unsigned export_id = RHASH_ALL_HASHES;
	uint8_t data[241];
	size_t i;
	size_t min_sizes[3] = { 0, 1024, 8192 };
	dbg("test import/export\n");
	for (i = 0; i < sizeof(data); i++)
		data[i] = (uint8_t)i;
	for(i = 0; i < 3; i++) {
		size_t min_size = min_sizes[i];
		size_t size = 0;
		size_t required_size;
		size_t exported_size;
		void* exported_data;
		unsigned imported_ids[RHASH_HASH_COUNT];
		size_t imported_ids_count;
		size_t j;
		rhash ctx = rhash_init(export_id);
		rhash imported_ctx;
		size_t exported_ids_count = rhash_get_ctx_algorithms(ctx, 0, 0);
		CHECK_TRUE(exported_ids_count > 0, "wrong number of exported algorithms");

		for (; size < min_size; size += sizeof(data))
			rhash_update(ctx, data, sizeof(data));
		if (export_id == RHASH_BTIH || export_id == RHASH_ALL_HASHES) {
			rhash_torrent_set_program_name(ctx, "test");
			rhash_torrent_add_announce(ctx, "url1");
			rhash_torrent_add_announce(ctx, "url2");
			rhash_torrent_add_file(ctx, "file1", 1);
			rhash_torrent_add_file(ctx, "file2", 22);
			rhash_torrent_set_options(ctx, RHASH_TORRENT_OPT_PRIVATE);
			if ((i & 1) != 0)
				rhash_final(ctx, 0);
		}
		dbg2("- call rhash_export NULL\n");
		required_size = rhash_export(ctx, NULL, 0);
		if (!required_size) {
			log_error1("rhash_export failed for block size=%u\n", (unsigned)size);
			return;
		}
		dbg2("- call rhash_export ctx\n");
		exported_data = malloc(required_size);
		exported_size = rhash_export(ctx, exported_data, required_size);
		if (exported_size != required_size) {
			log_error2("rhash_export failed: %u != %u\n", (unsigned)exported_size, (unsigned)required_size);
			return;
		}
		dbg2("- call rhash_import\n");
		imported_ctx = rhash_import(exported_data, required_size);
		if (!imported_ctx) {
			log_error1("rhash_import failed for block size=%u\n", (unsigned)size);
			return;
		}
		free(exported_data);
		dbg2("- call rhash_final ctx\n");
		rhash_final(ctx, 0);
		dbg2("- call rhash_final imported_ctx\n");
		rhash_final(imported_ctx, 0);
		exported_data = NULL;
		imported_ids_count = rhash_get_ctx_algorithms(ctx, RHASH_HASH_COUNT, imported_ids);
		CHECK_EQ(exported_ids_count, imported_ids_count, "wrong number of imported algorithms");
		dbg2("- verify results\n");
		for (j = 0; j < imported_ids_count; j++) {
			unsigned hash_id = imported_ids[j];
			static char out1[240], out2[240];
			REQUIRE_NE(0, rhash_print(out1, ctx, hash_id, RHPR_UPPERCASE), "rhash_print failed");
			REQUIRE_NE(0, rhash_print(out2, imported_ctx, hash_id, RHPR_UPPERCASE), "rhash_print failed");
			if (strcmp(out1, out2) != 0) {
				const char* hash_name = rhash_get_name(hash_id);
				log_error4("import failed, wrong hash %s != %s for %s,  block size=%u\n",
					out1, out2, hash_name, (unsigned)size);
				return;
			}
		}
		rhash_free(ctx);
		rhash_free(imported_ctx);
	}
#endif /* !defined(NO_IMPORT_EXPORT) */
}

static uint64_t make_hash_mask(size_t count, unsigned hash_ids[])
{
	uint64_t hash_mask = 0;
	size_t i;
	if (count == 1 && hash_ids[0] == RHASH_ALL_HASHES)
		return RHASH_LOW_HASHES_MASK;
	for (i = 0; i < count; i++) {
		if ((RHASH_EXTENDED_BIT & hash_ids[i]) != 0)
			hash_mask |= I64(1) << (unsigned)(hash_ids[i] & ~RHASH_EXTENDED_BIT);
		else
			hash_mask |= hash_ids[i];
	}
	return hash_mask;
}

#define TEST_PATH 0x4000000

/**
 * Verify a magnet link.
 */
static void test_magnet(const char* expected,
	rhash ctx, int flags, size_t count, unsigned hash_ids[])
{
	static char out[2800];
	const char* path = (flags & TEST_PATH ? "test.txt" : NULL);
	uint64_t hash_mask = make_hash_mask(count, hash_ids);
	size_t size_calculated = rhash_print_magnet_multi(NULL, 0, path, ctx, flags, count, hash_ids);
	size_t size_calculated_legacy = (hash_mask < (uint64_t)RHASH_EXTENDED_BIT ?
		rhash_print_magnet(NULL, path, ctx, (unsigned)hash_mask, flags) : 0);
	size_t size_printed, size_printed_legacy;

	REQUIRE_TRUE(size_calculated < sizeof(out), "too small buffer for magnet link\n");
	CHECK_NE(0, size_calculated, "non zero buffer size expected to be returned\n");
	if (size_calculated_legacy)
		CHECK_EQ(size_calculated, size_calculated_legacy, "wrong size_calculated_legacy\n");

	flags &= ~TEST_PATH;
	if (size_calculated > 0) {
		size_printed = rhash_print_magnet_multi(out, size_calculated - 1, path, ctx, flags, count, hash_ids);
		CHECK_EQ(0, size_printed, "too small buffer error expected, but not occurred\n");
	}
	size_printed = rhash_print_magnet_multi(out, size_calculated, path, ctx, flags, count, hash_ids);
	if (size_calculated_legacy)
	{
		size_printed_legacy = rhash_print_magnet(out, path, ctx, (unsigned)hash_mask, flags);
		CHECK_EQ(size_printed, size_printed_legacy, "wrong size_printed_legacy\n");
	}

	if (expected && strcmp(expected, out) != 0) {
		log_error2("\"%s\" != \"%s\"\n", expected, out);
	} else {
		size_t length = strlen(out) + 1;
		if (size_printed != length) {
			log_error3("rhash_print_magnet returns wrong length %u != %u for \"%s\"\n",
				(unsigned)size_printed, (unsigned)length, out);
		} else if (size_printed != size_calculated) {
			log_error3("rhash_print_magnet(NULL, ...) returns wrong length %u != %u for \"%s\"\n",
				(unsigned)size_calculated, (unsigned)size_printed, out);
		}
	}
}

/**
 * Test printing of magnet links.
 */
static void test_magnet_links(void)
{
	unsigned hash_ids_all[RHASH_HASH_COUNT];
	unsigned hash_ids_tth[] = { RHASH_TTH };
	unsigned hash_ids_md5[] = { RHASH_MD5 };
	unsigned hash_ids_special[] = { RHASH_ED2K, RHASH_AICH, RHASH_SHA1, RHASH_BTIH };
	unsigned hash_ids_crc32_tth[2] = { RHASH_CRC32, RHASH_TTH };
	unsigned hash_id_all_hashes = RHASH_ALL_HASHES;
	size_t count = rhash_get_all_algorithms(RHASH_HASH_COUNT, hash_ids_all);
	size_t i;
	rhash ctx;
	dbg("test magnet links\n");
	ctx	= rhash_init_multi(count, hash_ids_all);
	rhash_update(ctx, "a", 1);
	rhash_final(ctx, 0);

	dbg2("- test specific magnet links\n");
	test_magnet("magnet:?xl=1&dn=test.txt&xt=urn:tree:tiger:czquwh3iyxbf5l3bgyugzhassmxu647ip2ike4y",
		ctx, RHPR_FILESIZE | TEST_PATH, RHASH_COUNTOF(hash_ids_tth), hash_ids_tth);
	test_magnet("magnet:?xl=1&xt=urn:md5:0CC175B9C0F1B6A831C399E269772661",
		ctx, RHPR_FILESIZE | RHPR_UPPERCASE, RHASH_COUNTOF(hash_ids_md5), hash_ids_md5);
	test_magnet(
		"xt=urn:ed2k:bde52cb31de33e46245e05fbdbd6fb24&"
		"xt=urn:aich:q336in72uwt7zyk5dxolt2xk5i3xmz5y&"
		"xt=urn:sha1:q336in72uwt7zyk5dxolt2xk5i3xmz5y&"
		"xt=urn:btih:827cd89846fc132e2e67e29c2784c65443bb4dc1",
		ctx, RHPR_NO_MAGNET, RHASH_COUNTOF(hash_ids_special), hash_ids_special);

	/* verify length calculation for all hashes */
	dbg2("- test magnet link length for all hash ids\n");
	for (i = 0; i < count; i++) {
		unsigned hash_id = hash_ids_all[i];
		test_magnet(NULL, ctx, RHPR_FILESIZE | RHPR_NO_MAGNET, 1, &hash_id);
	}
	test_magnet(NULL, ctx, RHPR_FILESIZE | RHPR_NO_MAGNET, 1, &hash_id_all_hashes);
	rhash_free(ctx);

	/* test with two hash functions */
	dbg2("- test magnet link with two hash functions\n");
	ctx	= rhash_init_multi(RHASH_COUNTOF(hash_ids_crc32_tth), hash_ids_crc32_tth);
	rhash_update(ctx, "abc", 3);
	rhash_final(ctx, 0);
	test_magnet(
		"magnet:?xl=3&xt=urn:crc32:352441c2&"
		"xt=urn:tree:tiger:asd4ujseh5m47pdyb46kbtsqtsgdklbhyxomuia",
		ctx, RHPR_FILESIZE, 1, &hash_id_all_hashes);
	rhash_free(ctx);
}

/*=========================================================================*
 *                           Test file functions                           *
 *=========================================================================*/

/**
 * Create temporary file and return its path.
 *
 * @param filename the name of the file
 * @param content the content of the file
 * @return the path of the file
 */
static const char* write_temp_file(const char* filename, const char* content)
{
	static char file_path[1024];
	const char *tmp_dir = NULL;
	FILE *fd;
	size_t content_length = strlen(content);
	size_t length;
	int count;
#ifdef _WIN32
# define TEST_PATH_SEPARATOR '\\'
    tmp_dir = getenv("TEMP");
    if (!tmp_dir) tmp_dir = getenv("TMP");
#else
# define TEST_PATH_SEPARATOR '/'
    tmp_dir = getenv("TMPDIR");
# ifdef P_tmpdir
    if (!tmp_dir) tmp_dir = P_tmpdir;
# endif
    if (!tmp_dir) tmp_dir = "/tmp";
#endif
	if (!tmp_dir) {
		printf("%s:%d: warning: can't detect temp dir\n", __FILE__, __LINE__);
		return NULL;
	}
	length = strlen(tmp_dir);
	if ((length + 1) >= sizeof(file_path))
		return NULL;
	memcpy(file_path, tmp_dir, length);
	while (length > 0 && file_path[length - 1] == TEST_PATH_SEPARATOR)
		file_path[--length] = '\0';
	assert(length < sizeof(file_path));
	count = snprintf(file_path + length, sizeof(file_path) - length,
		"%c%s", TEST_PATH_SEPARATOR, filename);
	if (count >= (int)(sizeof(file_path)))
		return NULL;

	fd = fopen(file_path, "w");
	if (fd != NULL) {
		length = fwrite(content, 1, content_length, fd);
		fclose(fd);
		if (length == content_length)
			return file_path;
		log_error1("failed to write to file: %s\n", file_path);
	} else {
		log_error1("failed to open file for writing: %s\n", file_path);
	}
	return NULL;
}

struct file_test_ctx {
	FILE* file_fd;
	int int_fd;
	rhash rctx;
	const char* path;
};

/**
 * Test hashing of a file using rhash_file_update().
 * Report error if calculated hash doesn't coincide with expected value.
 *
 * @param fctx the file test context
 * @param offset the file offset to start hashing from
 * @param expected the expected hash value
 */
static void test_update_by_file(struct file_test_ctx* fctx, size_t offset, const char* expected)
{
	static char result[130];
	assert(fctx->file_fd && fctx->rctx);
	rhash_reset(fctx->rctx);
	fseek(fctx->file_fd, (long)offset, SEEK_SET);
	rhash_file_update(fctx->rctx, fctx->file_fd);
	rhash_final(fctx->rctx, 0);
	rhash_print(result, fctx->rctx, 0, RHPR_UPPERCASE);
	if (strcmp(result, expected) != 0)
		log_error4("MD5(%s:%d) = %s, expected %s\n", fctx->path, (int)offset, result, expected);
}

/**
 * Test hashing of a file using rhash_update_fd().
 * Report error if calculated hash doesn't coincide with expected value.
 *
 * @param fctx the file test context
 * @param offset the file offset to start hashing from
 * @param data_size the number of bytes to hash
 * @param expected the expected hash value
 */
static void test_update_by_fd(struct file_test_ctx* fctx, size_t offset, unsigned long long data_size, const char* expected)
{
	static char result[130];
	rhash_reset(fctx->rctx);
	assert(fctx->int_fd >= 0 && fctx->rctx);
	lseek(fctx->int_fd, (off_t)offset, SEEK_SET);
	rhash_update_fd(fctx->rctx, fctx->int_fd, data_size);
	rhash_final(fctx->rctx, 0);
	rhash_print(result, fctx->rctx, 0, RHPR_UPPERCASE);
	if (strcmp(result, expected) != 0)
		log_error5("MD5(%s:%d:%llu) = %s, expected %s\n", fctx->path, (int)offset, data_size, result, expected);
}

/**
 * Test rhash_file_update() and rhash_update_fd().
 */
static void test_file_update(void)
{
	const char* test_file_content = "012abc";
	struct file_test_ctx fctx;
	memset(&fctx, 0 , sizeof(fctx));
	if (!(fctx.rctx = rhash_init(RHASH_MD5))) {
		log_error("got NULL context for MD5\n");
		return;
	}
	if (!(fctx.path = write_temp_file("test_lib.txt", test_file_content)))
		return;
	fctx.file_fd = fopen(fctx.path, "r");
	if (fctx.file_fd) {
		test_update_by_file(&fctx, 0, "CF31AB6B6F7CA8250BB701ADAB94B579");
		test_update_by_file(&fctx, 3, "900150983CD24FB0D6963F7D28E17F72");
		test_update_by_file(&fctx, 5, "4A8A08F09D37B73795649038408B5F33");
		test_update_by_file(&fctx, 6, "D41D8CD98F00B204E9800998ECF8427E");
		fclose(fctx.file_fd);
	}
	fctx.int_fd = open(fctx.path, O_RDONLY);
	if (fctx.int_fd > 0) {
		test_update_by_fd(&fctx, 0, RHASH_MAX_FILE_SIZE, "CF31AB6B6F7CA8250BB701ADAB94B579");
		test_update_by_fd(&fctx, 3, RHASH_MAX_FILE_SIZE, "900150983CD24FB0D6963F7D28E17F72");
		test_update_by_fd(&fctx, 3, 1, "0CC175B9C0F1B6A831C399E269772661");
		test_update_by_fd(&fctx, 3, 2, "187EF4436122D1CC2F40DC2B92F0EBA0");
		test_update_by_fd(&fctx, 4, 1, "92EB5FFEE6AE2FEC3AD71C777531578F");
		close(fctx.int_fd);
	}
	unlink(fctx.path);
	rhash_free(fctx.rctx);
}

/**
 * Find a hash function id by its name.
 *
 * @param name hash algorithm name
 * @return algorithm id
 */
static unsigned find_hash(const char* name)
{
	unsigned hash_ids_all[RHASH_HASH_COUNT];
	char buf[32];
	size_t count = rhash_get_all_algorithms(RHASH_HASH_COUNT, hash_ids_all);
	size_t i;

	if (strlen(name) > (sizeof(buf) - 1)) return 0;
	for (i = 0; name[i]; i++) buf[i] = toupper(name[i]);
	buf[i] = 0;

	for (i = 0; i < count; i++)
		if (strcmp(buf, rhash_get_name(hash_ids_all[i])) == 0)
			return hash_ids_all[i];
	return 0;
}

/**
 * Detect and print CPU features.
 */
static void print_cpu_features(void)
{
#if !defined(NO_HAS_CPU_FEATURE)
	printf("CPU Features:%s%s\n",
		(rhash_has_cpu_feature(CPU_FEATURE_SSE4_2) ? " SSE_4.2" : ""),
		(rhash_has_cpu_feature(CPU_FEATURE_SHANI) ? " SHANI" : ""));
#endif
}

/**
 * Print status of OpenSSL plugin.
 */
static void print_openssl_status(void)
{
	unsigned available[RHASH_HASH_COUNT];
	unsigned supported[RHASH_HASH_COUNT];
	size_t available_count, supported_count;

	if (!rhash_is_openssl_supported()) {
		printf("OpenSSL is not supported\n");
		return;
	}
	supported_count = rhash_get_openssl_supported(RHASH_HASH_COUNT, supported);
	available_count = rhash_get_openssl_available(RHASH_HASH_COUNT, available);
	REQUIRE_NE(RHASH_ERROR, supported_count, "failed to load supported openssl algorithms\n");
	REQUIRE_NE(RHASH_ERROR, available_count, "failed to load available openssl algorithms\n");

	printf("OpenSSL is supported, %s",
		(available_count ? "loaded:" : "not loaded\n"));
	if (available_count > 0) {
		size_t i, j;
		for (i = 0; i < available_count; i++)
			printf(" %s", rhash_get_name(available[i]));
		for (i = 0; i < supported_count; i++) {
			unsigned id = supported[i];
			for (j = 0; j < available_count && available[j] != id; j++);
			if (j == available_count)
				printf(" -%s", rhash_get_name(id));
		}
		printf("\n");
	}
}

/**
 * The program entry point.
 *
 * @param argc number of arguments including the program name
 * @param argv program arguments including the program name
 * @return program exit code
 */
int main(int argc, char* argv[])
{
	int test_speed = 0, print_info = 0;
	unsigned hash_id = RHASH_SHA1;
	int i;

	for (i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "--info") || !strcmp(argv[i], "-i")) {
			print_info = 1;
		}
		else if (!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) {
			g_verbose++;
		}
		else if (!strcmp(argv[i], "--speed") || !strcmp(argv[i], "-s")) {
			test_speed = 1;
			if ((i + 1) < argc && argv[i + 1][0] != '-') {
				hash_id = find_hash(argv[i + 1]);
				if (hash_id == 0) {
					fprintf(stderr, "error: unknown hash_id: %s\n", argv[i + 1]);
					return 1;
				}
				i++;
			}
		}
		else {
			printf("Options:\n"
				"-h, --help Print help.\n"
				"-v, --verbose Be verbose.\n"
				"-i, --info Print library info\n"
				"-s, --speed [HASH_NAME] Benchmark given hash algorithm\n");
			return 1;
		}
	}

#ifndef USE_RHASH_DLL
	rhash_library_init();
#endif
	test_generic_assumptions();
	if (print_info) {
		printf("%s", compiler_flags);
		print_cpu_features();
		print_openssl_status();
	} else if (test_speed) {
		test_known_strings(hash_id);
		test_run_benchmark(hash_id, 0, stdout);
	} else {
		test_all_known_strings();
		test_long_strings();
		test_results_consistency();
		test_unaligned_messages_consistency();
		test_chunk_size_consistency();
		test_context_alignment();
		test_id_getters();
		test_get_context();
		test_import_export();
		test_magnet_links();
		test_file_update();
		if (g_errors_count == 0)
			printf("All sums are working properly!\n");
		fflush(stdout);
	}
	if (g_errors_count > 0)
		printf("%s", compiler_flags);
	return (g_errors_count == 0 ? 0 : 1);
}
