/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#include "test.h"
#include <pjlib-util.h>
#include <pjlib.h>


#if INCLUDE_ENCRYPTION_TEST

/*
 * Encryption algorithm tests.
 */
#define THIS_FILE   "encryption.c"


/*
 * SHA1 test from the original sha1.c source file.
 */
static char *sha1_test_data[] = {
    "abc",
    "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
    "A million repetitions of 'a'"
};
static char *sha1_test_results[] = {
    "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D",
    "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1",
    "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F"
};


static void digest_to_hex(const pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE], 
                          char *output, int output_size)
{
    int i,j;
    char *c = output;
    
    for (i = 0; i < PJ_SHA1_DIGEST_SIZE/4 && output_size >= 2; i++) {
        for (j = 0; j < 4 && output_size >= 3; j++) {
            pj_ansi_snprintf(c, output_size, "%02X", digest[i*4+j] & 0xFF);
            c += 2;
            output_size -= 2;
        }
        pj_ansi_snprintf(c, output_size, " ");
        c += 1;
        output_size -= 1;
    }
    *(c - 1) = '\0';
}

static int sha1_test1(void)
{
    enum { MILLION = 1000000 };
    int k;
    pj_sha1_context context;
    pj_uint8_t digest[20];
    char output[80];
    pj_pool_t *pool;
    pj_uint8_t *block;

    PJ_LOG(3, (THIS_FILE, "  SHA1 test vector 1 from sha1.c.."));
    
    for (k = 0; k < 2; k++){ 
        pj_sha1_init(&context);
        pj_sha1_update(&context, (pj_uint8_t*)sha1_test_data[k], 
                       pj_ansi_strlen(sha1_test_data[k]));
        pj_sha1_final(&context, digest);
        digest_to_hex(digest, output, sizeof(output));

        if (pj_ansi_strcmp(output, sha1_test_results[k])) {
            PJ_LOG(3, (THIS_FILE, "    incorrect hash result on k=%d", k));
            return -10;
        }
    }

    /* million 'a' vector we feed separately */
    pj_sha1_init(&context);
    for (k = 0; k < MILLION; k++)
        pj_sha1_update(&context, (pj_uint8_t*)"a", 1);
    pj_sha1_final(&context, digest);
    digest_to_hex(digest, output, sizeof(output));
    if (strcmp(output, sha1_test_results[2])) {
        PJ_LOG(3, (THIS_FILE, "    incorrect hash result!"));
        return -20;
    }

    /* million 'a' test, using block */
    pool = pj_pool_create(mem, "sha1test", 256, 512, NULL);
    block = (pj_uint8_t*)pj_pool_alloc(pool, MILLION);
    pj_memset(block, 'a', MILLION);

    pj_sha1_init(&context);
    pj_sha1_update(&context, block, MILLION);
    pj_sha1_final(&context, digest);
    digest_to_hex(digest, output, sizeof(output));
    if (strcmp(output, sha1_test_results[2])) {
        pj_pool_release(pool);
        PJ_LOG(3, (THIS_FILE, "    incorrect hash result for block update!"));
        return -21;
    }

    /* verify that original buffer was not modified */
    for (k=0; k<MILLION; ++k) {
        if (block[k] != 'a') {
            pj_pool_release(pool);
            PJ_LOG(3, (THIS_FILE, "    block was modified!"));
            return -22;
        }
    }

    pj_pool_release(pool);

    /* success */
    return(0);
}


/*
 * SHA1 test from RFC 3174
 */
/*
 *  Define patterns for testing
 */
#define TEST1   "abc"
#define TEST2a  "abcdbcdecdefdefgefghfghighijhi"
#define TEST2b  "jkijkljklmklmnlmnomnopnopq"
#define TEST2   TEST2a TEST2b
#define TEST3   "a"
#define TEST4a  "01234567012345670123456701234567"
#define TEST4b  "01234567012345670123456701234567"
    /* an exact multiple of 512 bits */
#define TEST4   TEST4a TEST4b

static char *testarray[4] =
{
    TEST1,
    TEST2,
    TEST3,
    TEST4
};
static int repeatcount[4] = { 1, 1, 1000000, 10 };
static char *resultarray[4] =
{
    "A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D",
    "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1",
    "34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F",
    "DEA356A2 CDDD90C7 A7ECEDC5 EBB56393 4F460452"
};

static int sha1_test2(void)
{
    pj_sha1_context sha;
    int i;
    pj_uint8_t digest[20];
    char char_digest[64];

    PJ_LOG(3, (THIS_FILE, "  SHA1 test vector 2 from rfc 3174.."));

    for(i = 0; i < 4; ++i) {
        int j;

        pj_sha1_init(&sha);

        for(j = 0; j < repeatcount[i]; ++j) {
            pj_sha1_update(&sha,
                           (const pj_uint8_t *) testarray[i],
                           pj_ansi_strlen(testarray[i]));
        }

        pj_sha1_final(&sha, digest);

        digest_to_hex(digest, char_digest, sizeof(char_digest));
        if (pj_ansi_strcmp(char_digest, resultarray[i])) {
            PJ_LOG(3, (THIS_FILE, "    digest mismatch in test %d", i));
            return -40;
        }
    }

    return 0;
}


/*
 * HMAC-MD5 and HMAC-SHA1 test vectors from RFC 2202
 */
struct rfc2202_test
{
    char       *key;
    unsigned    key_len;
    char       *input;
    unsigned    input_len;
    char       *md5_digest;
    char       *sha1_digest;
};


struct rfc2202_test rfc2202_test_vector[] = 
{
    {
        "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
        16,
        "Hi There",
        8,
        "\x92\x94\x72\x7a\x36\x38\xbb\x1c\x13\xf4\x8e\xf8\x15\x8b\xfc\x9d",
        NULL
    },
    {
        "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
        "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
        20,
        "Hi There",
        8,
        NULL,
        "\xb6\x17\x31\x86\x55\x05\x72\x64\xe2\x8b\xc0\xb6\xfb\x37\x8c\x8e\xf1\x46\xbe\x00"
    },
    {
        "Jefe",
        4,
        "what do ya want for nothing?",
        28,
        "\x75\x0c\x78\x3e\x6a\xb0\xb5\x03\xea\xa8\x6e\x31\x0a\x5d\xb7\x38",
        "\xef\xfc\xdf\x6a\xe5\xeb\x2f\xa2\xd2\x74\x16\xd5\xf1\x84\xdf\x9c\x25\x9a\x7c\x79"
    },
    {
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa",
        16,
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd",
        50,
        "\x56\xbe\x34\x52\x1d\x14\x4c\x88\xdb\xb8\xc7\x33\xf0\xe8\xb3\xf6",
        NULL
    },
    {
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
        20,
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd"
        "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd",
        50,
        NULL,
        "\x12\x5d\x73\x42\xb9\xac\x11\xcd\x91\xa3\x9a\xf4\x8a\xa1\x7b\x4f\x63\xf1\x75\xd3"
    },
    {
        "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19",
        25,
        "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
        "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
        "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
        "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"
        "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd",
        50,
        "\x69\x7e\xaf\x0a\xca\x3a\x3a\xea\x3a\x75\x16\x47\x46\xff\xaa\x79",
        "\x4c\x90\x07\xf4\x02\x62\x50\xc6\xbc\x84\x14\xf9\xbf\x50\xc8\x6c\x2d\x72\x35\xda"
    },
    {
        "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
        "\x0c\x0c\x0c\x0c\x0c\x0c",
        16,
        "Test With Truncation",
        20,
        "\x56\x46\x1e\xf2\x34\x2e\xdc\x00\xf9\xba\xb9\x95\x69\x0e\xfd\x4c",
        NULL
    },
    {
        "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"
        "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c",
        20,
        "Test With Truncation",
        20,
        NULL,
        "\x4c\x1a\x03\x42\x4b\x55\xe0\x7f\xe7\xf2\x7b\xe1\xd5\x8b\xb9\x32\x4a\x9a\x5a\x04"
    },
    {
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
        80,
        "Test Using Larger Than Block-Size Key - Hash Key First",
        54,
        "\x6b\x1a\xb7\xfe\x4b\xd7\xbf\x8f\x0b\x62\xe6\xce\x61\xb9\xd0\xcd",
        "\xaa\x4a\xe5\xe1\x52\x72\xd0\x0e\x95\x70\x56\x37\xce\x8a\x3b\x55\xed\x40\x21\x12"
    },
    {
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"
        "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa",
        80,
        "Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
        73,
        "\x6f\x63\x0f\xad\x67\xcd\xa0\xee\x1f\xb1\xf5\x62\xdb\x3a\xa5\x3e",
        "\xe8\xe9\x9d\x0f\x45\x23\x7d\x78\x6d\x6b\xba\xa7\x96\x5c\x78\x08\xbb\xff\x1a\x91"
    }
};


static int rfc2202_test(void)
{
    unsigned i;

    PJ_LOG(3, (THIS_FILE, "  verifying test vectors from rfc 2202.."));

    /* Verify that test vectors are valid */
    for (i=0; i<PJ_ARRAY_SIZE(rfc2202_test_vector); ++i) {
        PJ_ASSERT_RETURN(pj_ansi_strlen(rfc2202_test_vector[i].input) == 
                            rfc2202_test_vector[i].input_len, -50);
        PJ_ASSERT_RETURN(pj_ansi_strlen(rfc2202_test_vector[i].key) == 
                            rfc2202_test_vector[i].key_len, -52);
        PJ_ASSERT_RETURN(rfc2202_test_vector[i].md5_digest==NULL ||
                            pj_ansi_strlen(rfc2202_test_vector[i].md5_digest)<=16,
                            -54);
        PJ_ASSERT_RETURN(rfc2202_test_vector[i].sha1_digest==NULL ||
                            pj_ansi_strlen(rfc2202_test_vector[i].sha1_digest)<=20,
                            -56);
    }

    /* Test HMAC-MD5 */
    PJ_LOG(3, (THIS_FILE, "  HMAC-MD5 test vectors from rfc 2202.."));
    for (i=0; i<PJ_ARRAY_SIZE(rfc2202_test_vector); ++i) {
        pj_uint8_t digest_buf[18], *digest;

        if (rfc2202_test_vector[i].md5_digest == NULL)
            continue;

        digest_buf[0] = '\0';
        digest_buf[17] = '\0';

        digest = digest_buf+1;

        pj_hmac_md5((pj_uint8_t*)rfc2202_test_vector[i].input, 
                    rfc2202_test_vector[i].input_len,
                    (pj_uint8_t*)rfc2202_test_vector[i].key, 
                    rfc2202_test_vector[i].key_len,
                    digest);

        /* Check for overwrites */
        if (digest_buf[0] != '\0' || digest_buf[17] != '\0') {
            PJ_LOG(3, (THIS_FILE, "    error: overwriting outside buffer on test %d", i));
            return -60;
        }

        /* Compare digest */
        if (pj_memcmp(rfc2202_test_vector[i].md5_digest, digest, 16)) {
            PJ_LOG(3, (THIS_FILE, "    error: digest mismatch on test %d", i));
            return -65;
        }
    }

    /* Test HMAC-SHA1 */
    PJ_LOG(3, (THIS_FILE, "  HMAC-SHA1 test vectors from rfc 2202.."));
    for (i=0; i<PJ_ARRAY_SIZE(rfc2202_test_vector); ++i) {
        pj_uint8_t digest_buf[22], *digest;

        if (rfc2202_test_vector[i].sha1_digest == NULL)
            continue;

        digest_buf[0] = '\0';
        digest_buf[21] = '\0';

        digest = digest_buf+1;

        pj_hmac_sha1((pj_uint8_t*)rfc2202_test_vector[i].input, 
                     rfc2202_test_vector[i].input_len,
                     (pj_uint8_t*)rfc2202_test_vector[i].key, 
                     rfc2202_test_vector[i].key_len,
                     digest);

        /* Check for overwrites */
        if (digest_buf[0] != '\0' || digest_buf[21] != '\0') {
            PJ_LOG(3, (THIS_FILE, "    error: overwriting outside buffer on test %d", i));
            return -70;
        }

        /* Compare digest */
        if (pj_memcmp(rfc2202_test_vector[i].sha1_digest, digest, 20)) {
            PJ_LOG(3, (THIS_FILE, "    error: digest mismatch on test %d", i));
            return -75;
        }
    }


    /* Success */
    return 0;
}

/* CRC32 test data, generated from crc32 test on a Linux box */
struct crc32_test_t
{
    char            *input;
    pj_uint32_t      crc;
} crc32_test_data[] = 
{
    {
        "",
        0x0
    },
    {
        "Hello World",
        0x4a17b156
    },
    {
        /* Something read from /dev/random */
        "\x21\x21\x98\x10\x62\x59\xbc\x58\x42\x24\xe5\xf3\x92\x0a\x68\x3c\xa7\x67\x73\xc3",
        0x506693be
    },
    {
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        0xcab11777
    },
    {
        "123456789",
        0xCBF43926
    }
};

/*
 * CRC32 test
 */
static int crc32_test(void)
{
    unsigned i;

    PJ_LOG(3, (THIS_FILE, "  crc32 test.."));

    /* testing pj_crc32_calc */
    for (i=0; i<PJ_ARRAY_SIZE(crc32_test_data); ++i) {
        pj_uint32_t crc;

        crc = pj_crc32_calc((pj_uint8_t*)crc32_test_data[i].input,
                            pj_ansi_strlen(crc32_test_data[i].input));
        if (crc != crc32_test_data[i].crc) {
            PJ_LOG(3,(THIS_FILE, "    error: crc mismatch on test %d", i));
            return -80;
        }
    }

    /* testing incremental CRC32 calculation */
    for (i=0; i<PJ_ARRAY_SIZE(crc32_test_data); ++i) {
        pj_crc32_context ctx;
        pj_uint32_t crc0, crc1;
        pj_size_t len;

        len = pj_ansi_strlen(crc32_test_data[i].input);
        crc0 = pj_crc32_calc((pj_uint8_t*)crc32_test_data[i].input, len);

        pj_crc32_init(&ctx);
        pj_crc32_update(&ctx, (pj_uint8_t*)crc32_test_data[i].input,
                        len / 2);

        if (len/2 > 0) {
            pj_crc32_update(&ctx, (pj_uint8_t*)crc32_test_data[i].input + len/2,
                            len - len/2);
        }

        crc1 = pj_crc32_final(&ctx);

        if (crc0 != crc1) {
            PJ_LOG(3,(THIS_FILE, 
                      "    error: crc algorithm error on test %d", i));
            return -85;
        }

    }
    return 0;
}

enum
{
    ENCODE = 1,
    DECODE = 2,
    ENCODE_DECODE = 3
};

/*
 * Base64 test vectors (RFC 4648)
 */
static struct base64_test_vec
{
    const char *base256;
    const char *base64;
    unsigned flag;
} base64_test_vec[] = 
{
    {
        "",
        "",
        ENCODE_DECODE
    },
    {
        "f",
        "Zg==",
        ENCODE_DECODE
    },
    {
        "fo",
        "Zm8=",
        ENCODE_DECODE
    },
    {
        "foo",
        "Zm9v",
        ENCODE_DECODE
    },
    {
        "foob",
        "Zm9vYg==",
        ENCODE_DECODE
    },
    {
        "fooba",
        "Zm9vYmE=",
        ENCODE_DECODE
    },
    {
        "foobar",
        "Zm9vYmFy",
        ENCODE_DECODE
    },
    {
        "\x14\xfb\x9c\x03\xd9\x7e",
        "FPucA9l+",
        ENCODE_DECODE
    },
    {
        "\x14\xfb\x9c\x03\xd9",
        "FPucA9k=",
        ENCODE_DECODE
    },
    {
        "\x14\xfb\x9c\x03",
        "FPucAw==",
        ENCODE_DECODE
    },
    /* with whitespaces */
    {
        "foobar",
        "Zm9v\r\nYmFy",
        DECODE
    },
    {
        "foobar",
        "\nZ\r\nm 9\tv\nYm\nF\ny\n",
        DECODE
    },
};


static int base64_test(void)
{
    unsigned i;
    char output[80];
    pj_status_t rc;

    PJ_LOG(3, (THIS_FILE, "  base64 test.."));

    for (i=0; i<PJ_ARRAY_SIZE(base64_test_vec); ++i) {
        pj_str_t input;
        int out_len;

        /* Encode test */
        if (base64_test_vec[i].flag & ENCODE) {
            out_len = sizeof(output);
            rc = pj_base64_encode((pj_uint8_t*)base64_test_vec[i].base256,
                                  (int)strlen(base64_test_vec[i].base256),
                                  output, &out_len);
            if (rc != PJ_SUCCESS)
                return -90;

            if (out_len != (int)strlen(base64_test_vec[i].base64))
                return -91;

            output[out_len] = '\0';
            if (strcmp(output, base64_test_vec[i].base64) != 0)
                return -92;
        }

        /* Decode test */
        if (base64_test_vec[i].flag & DECODE) {
            out_len = sizeof(output);
            input.ptr = (char*)base64_test_vec[i].base64;
            input.slen = strlen(base64_test_vec[i].base64);
            rc = pj_base64_decode(&input, (pj_uint8_t*)output, &out_len);
            if (rc != PJ_SUCCESS)
                return -95;

            if (out_len != (int)strlen(base64_test_vec[i].base256))
                return -96;

            output[out_len] = '\0';

            if (strcmp(output, base64_test_vec[i].base256) != 0)
                return -97;
        }
    }

    return 0;
}


int encryption_test()
{
    int rc;

    rc = base64_test();
    if (rc != 0)
        return rc;

    rc = sha1_test1();
    if (rc != 0)
        return rc;

    rc = sha1_test2();
    if (rc != 0)
        return rc;

    rc = rfc2202_test();
    if (rc != 0)
        return rc;

    rc = crc32_test();
    if (rc != 0)
        return rc;

    return 0;
}

static void crc32_update(pj_crc32_context *c, const pj_uint8_t *data,
                         pj_size_t nbytes)
{
    pj_crc32_update(c, data, nbytes);
}

static void crc32_final(pj_crc32_context *ctx, pj_uint32_t *digest)
{
    *digest = pj_crc32_final(ctx);
}

/* pj_md5_update() has len argument in unsigned, while other xxx_update
 * are in pj_size_t, hence the wrapper
 */
static void md5_update_wrapper(pj_md5_context *ctx, 
                               unsigned char const *buf, pj_size_t len)
{
    pj_md5_update(ctx, buf, (unsigned)len);
}

int encryption_benchmark()
{
    pj_pool_t *pool;
    pj_uint8_t *input;
    union {
        pj_md5_context md5_context;
        pj_sha1_context sha1_context;
    } context;
    pj_uint8_t digest[32];
    pj_size_t input_len;
    struct algorithm
    {
        const char *name;
        void (*init_context)(void*);
        void (*update)(void*, const pj_uint8_t*, pj_size_t);
        void (*final)(void*, void*);
        pj_uint32_t t;
    } algorithms[] = 
    {
        {
            "MD5  ",
            (void (*)(void*))&pj_md5_init,
            (void (*)(void*, const pj_uint8_t*, pj_size_t))&md5_update_wrapper,
            (void (*)(void*, void*))&pj_md5_final
        },
        {
            "SHA1 ",
            (void (*)(void*))&pj_sha1_init,
            (void (*)(void*, const pj_uint8_t*, pj_size_t))&pj_sha1_update,
            (void (*)(void*, void*))&pj_sha1_final
        },
        {
            "CRC32",
            (void (*)(void*))&pj_crc32_init,
            (void (*)(void*, const pj_uint8_t*, pj_size_t))&crc32_update,
            (void (*)(void*, void*))&crc32_final
        }
    };
#if defined(PJ_DEBUG) && PJ_DEBUG!=0
    enum { LOOP = 1000 };
#else
    enum { LOOP = 10000 };
#endif
    unsigned i;
    double total_len;

    input_len = 2048;
    total_len = (unsigned)input_len * LOOP;
    pool = pj_pool_create(mem, "enc", input_len+256, 0, NULL);
    if (!pool)
        return PJ_ENOMEM;

    input = (pj_uint8_t*)pj_pool_alloc(pool, input_len);
    pj_memset(input, '\xaa', input_len);
    
    PJ_LOG(3, (THIS_FILE, "  feeding %d Mbytes of data",
               (unsigned)(total_len/1024/1024)));

    /* Dry run */
    for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
        algorithms[i].init_context(&context);
        algorithms[i].update(&context, input, (unsigned)input_len);
        algorithms[i].final(&context, digest);
    }

    /* Run */
    for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
        int j;
        pj_timestamp t1, t2;

        pj_get_timestamp(&t1);
        algorithms[i].init_context(&context);
        for (j=0; j<LOOP; ++j) {
            algorithms[i].update(&context, input, (unsigned)input_len);
        }
        algorithms[i].final(&context, digest);
        pj_get_timestamp(&t2);

        algorithms[i].t = pj_elapsed_usec(&t1, &t2);
    }

    /* Results */
    for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
        double bytes;

        bytes = (total_len * 1000000 / algorithms[i].t);
        PJ_LOG(3, (THIS_FILE, "    %s:%8d usec (%3d.%03d Mbytes/sec)",
                   algorithms[i].name, algorithms[i].t,
                   (unsigned)(bytes / 1024 / 1024),
                   ((unsigned)(bytes) % (1024 * 1024)) / 1024));
    }

    pj_pool_release(pool);

    return 0;
}



#endif /* INCLUDE_ENCRYPTION_TEST */

