diff options
Diffstat (limited to 'pjlib-util/src/pjlib-util-test')
-rw-r--r-- | pjlib-util/src/pjlib-util-test/encryption.c | 766 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/http_client.c | 955 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/main.c | 63 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/main_rtems.c | 11 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/main_win32.c | 1 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/resolver_test.c | 1405 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/stun.c | 119 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/test.c | 112 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/test.h | 38 | ||||
-rw-r--r-- | pjlib-util/src/pjlib-util-test/xml.c | 145 |
10 files changed, 3615 insertions, 0 deletions
diff --git a/pjlib-util/src/pjlib-util-test/encryption.c b/pjlib-util/src/pjlib-util-test/encryption.c new file mode 100644 index 0000000..706a441 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/encryption.c @@ -0,0 +1,766 @@ +/* $Id: encryption.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * 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 i,j; + char *c = output; + + for (i = 0; i < PJ_SHA1_DIGEST_SIZE/4; i++) { + for (j = 0; j < 4; j++) { + sprintf(c,"%02X", digest[i*4+j]); + c += 2; + } + sprintf(c, " "); + c += 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); + + 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); + 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); + 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); + 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; + unsigned 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, + 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); +} + +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*, unsigned); + void (*final)(void*, void*); + pj_uint32_t t; + } algorithms[] = + { + { + "MD5 ", + (void (*)(void*))&pj_md5_init, + (void (*)(void*, const pj_uint8_t*, unsigned))&pj_md5_update, + (void (*)(void*, void*))&pj_md5_final + }, + { + "SHA1 ", + (void (*)(void*))&pj_sha1_init, + (void (*)(void*, const pj_uint8_t*, unsigned))&pj_sha1_update, + (void (*)(void*, void*))&pj_sha1_final + }, + { + "CRC32", + (void (*)(void*))&pj_crc32_init, + (void (*)(void*, const pj_uint8_t*, unsigned))&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 = 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, 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, 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)); + } + + return 0; +} + + + +#endif /* INCLUDE_ENCRYPTION_TEST */ + diff --git a/pjlib-util/src/pjlib-util-test/http_client.c b/pjlib-util/src/pjlib-util-test/http_client.c new file mode 100644 index 0000000..0e2e3f6 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/http_client.c @@ -0,0 +1,955 @@ +/* $Id: http_client.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * 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" + +#if INCLUDE_HTTP_CLIENT_TEST + +#define THIS_FILE "test_http" +//#define VERBOSE +#define STR_PREC(s) (int)s.slen, s.ptr +#define USE_LOCAL_SERVER + +#include <pjlib.h> +#include <pjlib-util.h> + +#define ACTION_REPLY 0 +#define ACTION_IGNORE -1 + +static struct server_t +{ + pj_sock_t sock; + pj_uint16_t port; + pj_thread_t *thread; + + /* Action: + * 0: reply with the response in resp. + * -1: ignore query (to simulate timeout). + * other: reply with that error + */ + int action; + pj_bool_t send_content_length; + unsigned data_size; + unsigned buf_size; +} g_server; + +static pj_bool_t thread_quit; +static pj_timer_heap_t *timer_heap; +static pj_ioqueue_t *ioqueue; +static pj_pool_t *pool; +static pj_http_req *http_req; +static pj_bool_t test_cancel = PJ_FALSE; +static pj_size_t total_size; +static pj_size_t send_size = 0; +static pj_status_t sstatus; +static pj_sockaddr_in addr; +static int counter = 0; + +static int server_thread(void *p) +{ + struct server_t *srv = (struct server_t*)p; + char *pkt = (char*)pj_pool_alloc(pool, srv->buf_size); + pj_sock_t newsock = PJ_INVALID_SOCKET; + + while (!thread_quit) { + pj_ssize_t pkt_len; + int rc; + pj_fd_set_t rset; + pj_time_val timeout = {0, 500}; + + while (!thread_quit) { + PJ_FD_ZERO(&rset); + PJ_FD_SET(srv->sock, &rset); + rc = pj_sock_select(srv->sock+1, &rset, NULL, NULL, &timeout); + if (rc != 1) { + continue; + } + + rc = pj_sock_accept(srv->sock, &newsock, NULL, NULL); + if (rc == PJ_SUCCESS) { + break; + } + } + + if (thread_quit) + break; + + while (!thread_quit) { + PJ_FD_ZERO(&rset); + PJ_FD_SET(newsock, &rset); + rc = pj_sock_select(newsock+1, &rset, NULL, NULL, &timeout); + if (rc != 1) { + PJ_LOG(3,("http test", "client timeout")); + continue; + } + + pkt_len = srv->buf_size; + rc = pj_sock_recv(newsock, pkt, &pkt_len, 0); + if (rc == PJ_SUCCESS) { + break; + } + } + + if (thread_quit) + break; + + /* Simulate network RTT */ + pj_thread_sleep(50); + + if (srv->action == ACTION_IGNORE) { + continue; + } else if (srv->action == ACTION_REPLY) { + unsigned send_size = 0, ctr = 0; + pj_ansi_sprintf(pkt, "HTTP/1.0 200 OK\r\n"); + if (srv->send_content_length) { + pj_ansi_sprintf(pkt + pj_ansi_strlen(pkt), + "Content-Length: %d\r\n", + srv->data_size); + } + pj_ansi_sprintf(pkt + pj_ansi_strlen(pkt), "\r\n"); + pkt_len = pj_ansi_strlen(pkt); + rc = pj_sock_send(newsock, pkt, &pkt_len, 0); + if (rc != PJ_SUCCESS) { + pj_sock_close(newsock); + continue; + } + while (send_size < srv->data_size) { + pkt_len = srv->data_size - send_size; + if (pkt_len > (signed)srv->buf_size) + pkt_len = srv->buf_size; + send_size += pkt_len; + pj_create_random_string(pkt, pkt_len); + pj_ansi_sprintf(pkt, "\nPacket: %d", ++ctr); + pkt[pj_ansi_strlen(pkt)] = '\n'; + rc = pj_sock_send(newsock, pkt, &pkt_len, 0); + if (rc != PJ_SUCCESS) + break; + } + pj_sock_close(newsock); + } + } + + return 0; +} + +static void on_data_read(pj_http_req *hreq, void *data, pj_size_t size) +{ + PJ_UNUSED_ARG(hreq); + PJ_UNUSED_ARG(data); + + PJ_LOG(5, (THIS_FILE, "\nData received: %d bytes", size)); + if (size > 0) { +#ifdef VERBOSE + printf("%.*s\n", (int)size, (char *)data); +#endif + } +} + +static void on_send_data(pj_http_req *hreq, + void **data, pj_size_t *size) +{ + char *sdata; + pj_size_t sendsz = 8397; + + PJ_UNUSED_ARG(hreq); + + if (send_size + sendsz > total_size) { + sendsz = total_size - send_size; + } + send_size += sendsz; + + sdata = (char*)pj_pool_alloc(pool, sendsz); + pj_create_random_string(sdata, sendsz); + pj_ansi_sprintf(sdata, "\nSegment #%d\n", ++counter); + *data = sdata; + *size = sendsz; + + PJ_LOG(5, (THIS_FILE, "\nSending data progress: %d out of %d bytes", + send_size, total_size)); +} + + +static void on_complete(pj_http_req *hreq, pj_status_t status, + const pj_http_resp *resp) +{ + PJ_UNUSED_ARG(hreq); + + if (status == PJ_ECANCELLED) { + PJ_LOG(5, (THIS_FILE, "Request cancelled")); + return; + } else if (status == PJ_ETIMEDOUT) { + PJ_LOG(5, (THIS_FILE, "Request timed out!")); + return; + } else if (status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, "Error %d", status)); + return; + } + PJ_LOG(5, (THIS_FILE, "\nData completed: %d bytes", resp->size)); + if (resp->size > 0 && resp->data) { +#ifdef VERBOSE + printf("%.*s\n", (int)resp->size, (char *)resp->data); +#endif + } +} + +static void on_response(pj_http_req *hreq, const pj_http_resp *resp) +{ + pj_size_t i; + + PJ_UNUSED_ARG(hreq); + PJ_UNUSED_ARG(resp); + PJ_UNUSED_ARG(i); + +#ifdef VERBOSE + printf("%.*s, %d, %.*s\n", STR_PREC(resp->version), + resp->status_code, STR_PREC(resp->reason)); + for (i = 0; i < resp->headers.count; i++) { + printf("%.*s : %.*s\n", + STR_PREC(resp->headers.header[i].name), + STR_PREC(resp->headers.header[i].value)); + } +#endif + + if (test_cancel) { + /* Need to delay closing the client socket here, otherwise the + * server will get SIGPIPE when sending response. + */ + pj_thread_sleep(100); + pj_http_req_cancel(hreq, PJ_TRUE); + test_cancel = PJ_FALSE; + } +} + + +pj_status_t parse_url(const char *url, pj_http_url *hurl) +{ + pj_str_t surl; + pj_status_t status; + + pj_cstr(&surl, url); + status = pj_http_req_parse_url(&surl, hurl); +#ifdef VERBOSE + if (!status) { + printf("URL: %s\nProtocol: %.*s\nHost: %.*s\nPort: %d\nPath: %.*s\n\n", + url, STR_PREC(hurl->protocol), STR_PREC(hurl->host), + hurl->port, STR_PREC(hurl->path)); + } else { + } +#endif + return status; +} + +static int parse_url_test() +{ + struct test_data + { + char *url; + pj_status_t result; + const char *username; + const char *passwd; + const char *host; + int port; + const char *path; + } test_data[] = + { + /* Simple URL without '/' in the end */ + {"http://www.pjsip.org", PJ_SUCCESS, "", "", "www.pjsip.org", 80, "/"}, + + /* Simple URL with port number but without '/' in the end */ + {"http://pjsip.org:8080", PJ_SUCCESS, "", "", "pjsip.org", 8080, "/"}, + + /* URL with path */ + {"http://127.0.0.1:280/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6", + PJ_SUCCESS, "", "", "127.0.0.1", 280, + "/Joomla/index.php?option=com_content&task=view&id=5&Itemid=6"}, + + /* URL with port and path */ + {"http://pjsip.org:81/about-us/", PJ_SUCCESS, "", "", "pjsip.org", 81, "/about-us/"}, + + /* unsupported protocol */ + {"ftp://www.pjsip.org", PJ_ENOTSUP, "", "", "", 80, ""}, + + /* invalid format */ + {"http:/pjsip.org/about-us/", PJLIB_UTIL_EHTTPINURL, "", "", "", 80, ""}, + + /* invalid port number */ + {"http://pjsip.org:xyz/", PJLIB_UTIL_EHTTPINPORT, "", "", "", 80, ""}, + + /* with username and password */ + {"http://user:pass@pjsip.org", PJ_SUCCESS, "user", "pass", "pjsip.org", 80, "/"}, + + /* password only*/ + {"http://:pass@pjsip.org", PJ_SUCCESS, "", "pass", "pjsip.org", 80, "/"}, + + /* user only*/ + {"http://user:@pjsip.org", PJ_SUCCESS, "user", "", "pjsip.org", 80, "/"}, + + /* empty username and passwd*/ + {"http://:@pjsip.org", PJ_SUCCESS, "", "", "pjsip.org", 80, "/"}, + + /* '@' character in username and path */ + {"http://user@pjsip.org/@", PJ_SUCCESS, "user", "", "pjsip.org", 80, "/@"}, + + /* '@' character in path */ + {"http://pjsip.org/@", PJ_SUCCESS, "", "", "pjsip.org", 80, "/@"}, + + /* '@' character in path */ + {"http://pjsip.org/one@", PJ_SUCCESS, "", "", "pjsip.org", 80, "/one@"}, + + /* Invalid URL */ + {"http://:", PJ_EINVAL, "", "", "", 0, ""}, + + /* Invalid URL */ + {"http://@", PJ_EINVAL, "", "", "", 0, ""}, + + /* Invalid URL */ + {"http", PJ_EINVAL, "", "", "", 0, ""}, + + /* Invalid URL */ + {"http:/", PJ_EINVAL, "", "", "", 0, ""}, + + /* Invalid URL */ + {"http://", PJ_EINVAL, "", "", "", 0, ""}, + + /* Invalid URL */ + {"http:///", PJ_EINVAL, "", "", "", 0, ""}, + + /* Invalid URL */ + {"http://@/", PJ_EINVAL, "", "", "", 0, ""}, + + /* Invalid URL */ + {"http:///@", PJ_EINVAL, "", "", "", 0, ""}, + + /* Invalid URL */ + {"http://:::", PJ_EINVAL, "", "", "", 0, ""}, + }; + unsigned i; + + for (i=0; i<PJ_ARRAY_SIZE(test_data); ++i) { + struct test_data *ptd; + pj_http_url hurl; + pj_status_t status; + + ptd = &test_data[i]; + + PJ_LOG(3, (THIS_FILE, ".. %s", ptd->url)); + status = parse_url(ptd->url, &hurl); + + if (status != ptd->result) { + PJ_LOG(3,(THIS_FILE, "%d", status)); + return -11; + } + if (status != PJ_SUCCESS) + continue; + if (pj_strcmp2(&hurl.username, ptd->username)) + return -12; + if (pj_strcmp2(&hurl.passwd, ptd->passwd)) + return -13; + if (pj_strcmp2(&hurl.host, ptd->host)) + return -14; + if (hurl.port != ptd->port) + return -15; + if (pj_strcmp2(&hurl.path, ptd->path)) + return -16; + } + + return 0; +} + +/* + * GET request scenario 1: using on_response() and on_data_read() + * Server replies with content-length. Application cancels the + * request upon receiving the response, then start it again. + */ +int http_client_test1() +{ + pj_str_t url; + pj_http_req_callback hcb; + pj_http_req_param param; + char urlbuf[80]; + + pj_bzero(&hcb, sizeof(hcb)); + hcb.on_complete = &on_complete; + hcb.on_data_read = &on_data_read; + hcb.on_response = &on_response; + pj_http_req_param_default(¶m); + + /* Create pool, timer, and ioqueue */ + pool = pj_pool_create(mem, NULL, 8192, 4096, NULL); + if (pj_timer_heap_create(pool, 16, &timer_heap)) + return -31; + if (pj_ioqueue_create(pool, 16, &ioqueue)) + return -32; + +#ifdef USE_LOCAL_SERVER + + thread_quit = PJ_FALSE; + g_server.action = ACTION_REPLY; + g_server.send_content_length = PJ_TRUE; + g_server.data_size = 2970; + g_server.buf_size = 1024; + + sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, + &g_server.sock); + if (sstatus != PJ_SUCCESS) + return -41; + + pj_sockaddr_in_init(&addr, NULL, 0); + + sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr)); + if (sstatus != PJ_SUCCESS) + return -43; + + { + pj_sockaddr_in addr; + int addr_len = sizeof(addr); + sstatus = pj_sock_getsockname(g_server.sock, &addr, &addr_len); + if (sstatus != PJ_SUCCESS) + return -44; + g_server.port = pj_sockaddr_in_get_port(&addr); + pj_ansi_snprintf(urlbuf, sizeof(urlbuf), + "http://127.0.0.1:%d/about-us/", + g_server.port); + url = pj_str(urlbuf); + } + + sstatus = pj_sock_listen(g_server.sock, 8); + if (sstatus != PJ_SUCCESS) + return -45; + + sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server, + 0, 0, &g_server.thread); + if (sstatus != PJ_SUCCESS) + return -47; + +#else + pj_cstr(&url, "http://www.teluu.com/about-us/"); +#endif + + if (pj_http_req_create(pool, &url, timer_heap, ioqueue, + ¶m, &hcb, &http_req)) + return -33; + + test_cancel = PJ_TRUE; + if (pj_http_req_start(http_req)) + return -35; + + while (pj_http_req_is_running(http_req)) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer_heap, NULL); + } + + if (pj_http_req_start(http_req)) + return -37; + + while (pj_http_req_is_running(http_req)) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer_heap, NULL); + } + +#ifdef USE_LOCAL_SERVER + thread_quit = PJ_TRUE; + pj_thread_join(g_server.thread); + pj_sock_close(g_server.sock); +#endif + + pj_http_req_destroy(http_req); + pj_ioqueue_destroy(ioqueue); + pj_timer_heap_destroy(timer_heap); + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* + * GET request scenario 2: using on_complete() to get the + * complete data. Server does not reply with content-length. + * Request timed out, application sets a longer timeout, then + * then restart the request. + */ +int http_client_test2() +{ + pj_str_t url; + pj_http_req_callback hcb; + pj_http_req_param param; + pj_time_val timeout; + char urlbuf[80]; + + pj_bzero(&hcb, sizeof(hcb)); + hcb.on_complete = &on_complete; + hcb.on_response = &on_response; + pj_http_req_param_default(¶m); + + /* Create pool, timer, and ioqueue */ + pool = pj_pool_create(mem, NULL, 8192, 4096, NULL); + if (pj_timer_heap_create(pool, 16, &timer_heap)) + return -41; + if (pj_ioqueue_create(pool, 16, &ioqueue)) + return -42; + +#ifdef USE_LOCAL_SERVER + + pj_cstr(&url, "http://127.0.0.1:380"); + param.timeout.sec = 0; + param.timeout.msec = 2000; + + thread_quit = PJ_FALSE; + g_server.action = ACTION_IGNORE; + g_server.send_content_length = PJ_FALSE; + g_server.data_size = 4173; + g_server.buf_size = 1024; + + sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, + &g_server.sock); + if (sstatus != PJ_SUCCESS) + return -41; + + pj_sockaddr_in_init(&addr, NULL, 0); + + sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr)); + if (sstatus != PJ_SUCCESS) + return -43; + + { + pj_sockaddr_in addr; + int addr_len = sizeof(addr); + sstatus = pj_sock_getsockname(g_server.sock, &addr, &addr_len); + if (sstatus != PJ_SUCCESS) + return -44; + g_server.port = pj_sockaddr_in_get_port(&addr); + pj_ansi_snprintf(urlbuf, sizeof(urlbuf), + "http://127.0.0.1:%d", + g_server.port); + url = pj_str(urlbuf); + } + + sstatus = pj_sock_listen(g_server.sock, 8); + if (sstatus != PJ_SUCCESS) + return -45; + + sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server, + 0, 0, &g_server.thread); + if (sstatus != PJ_SUCCESS) + return -47; + +#else + pj_cstr(&url, "http://www.google.com.sg"); + param.timeout.sec = 0; + param.timeout.msec = 50; +#endif + + pj_http_headers_add_elmt2(¶m.headers, (char*)"Accept", + (char*)"image/gif, image/x-xbitmap, image/jpeg, " + "image/pjpeg, application/x-ms-application," + " application/vnd.ms-xpsdocument, " + "application/xaml+xml, " + "application/x-ms-xbap, " + "application/x-shockwave-flash, " + "application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, " + "application/msword, */*"); + pj_http_headers_add_elmt2(¶m.headers, (char*)"Accept-Language", + (char*)"en-sg"); + pj_http_headers_add_elmt2(¶m.headers, (char*)"User-Agent", + (char*)"Mozilla/4.0 (compatible; MSIE 7.0; " + "Windows NT 6.0; SLCC1; " + ".NET CLR 2.0.50727; " + ".NET CLR 3.0.04506)"); + if (pj_http_req_create(pool, &url, timer_heap, ioqueue, + ¶m, &hcb, &http_req)) + return -43; + + if (pj_http_req_start(http_req)) + return -45; + + while (pj_http_req_is_running(http_req)) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer_heap, NULL); + } + +#ifdef USE_LOCAL_SERVER + g_server.action = ACTION_REPLY; +#endif + + timeout.sec = 0; timeout.msec = 10000; + pj_http_req_set_timeout(http_req, &timeout); + if (pj_http_req_start(http_req)) + return -47; + + while (pj_http_req_is_running(http_req)) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer_heap, NULL); + } + +#ifdef USE_LOCAL_SERVER + thread_quit = PJ_TRUE; + pj_thread_join(g_server.thread); + pj_sock_close(g_server.sock); +#endif + + pj_http_req_destroy(http_req); + pj_ioqueue_destroy(ioqueue); + pj_timer_heap_destroy(timer_heap); + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* + * PUT request scenario 1: sending the whole data at once + */ +int http_client_test_put1() +{ + pj_str_t url; + pj_http_req_callback hcb; + pj_http_req_param param; + char *data; + int length = 3875; + char urlbuf[80]; + + pj_bzero(&hcb, sizeof(hcb)); + hcb.on_complete = &on_complete; + hcb.on_data_read = &on_data_read; + hcb.on_response = &on_response; + + /* Create pool, timer, and ioqueue */ + pool = pj_pool_create(mem, NULL, 8192, 4096, NULL); + if (pj_timer_heap_create(pool, 16, &timer_heap)) + return -51; + if (pj_ioqueue_create(pool, 16, &ioqueue)) + return -52; + +#ifdef USE_LOCAL_SERVER + thread_quit = PJ_FALSE; + g_server.action = ACTION_REPLY; + g_server.send_content_length = PJ_TRUE; + g_server.data_size = 0; + g_server.buf_size = 4096; + + sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, + &g_server.sock); + if (sstatus != PJ_SUCCESS) + return -41; + + pj_sockaddr_in_init(&addr, NULL, 0); + + sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr)); + if (sstatus != PJ_SUCCESS) + return -43; + + { + pj_sockaddr_in addr; + int addr_len = sizeof(addr); + sstatus = pj_sock_getsockname(g_server.sock, &addr, &addr_len); + if (sstatus != PJ_SUCCESS) + return -44; + g_server.port = pj_sockaddr_in_get_port(&addr); + pj_ansi_snprintf(urlbuf, sizeof(urlbuf), + "http://127.0.0.1:%d/test/test.txt", + g_server.port); + url = pj_str(urlbuf); + } + + sstatus = pj_sock_listen(g_server.sock, 8); + if (sstatus != PJ_SUCCESS) + return -45; + + sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server, + 0, 0, &g_server.thread); + if (sstatus != PJ_SUCCESS) + return -47; + +#else + pj_cstr(&url, "http://127.0.0.1:280/test/test.txt"); + +#endif + + pj_http_req_param_default(¶m); + pj_strset2(¶m.method, (char*)"PUT"); + data = (char*)pj_pool_alloc(pool, length); + pj_create_random_string(data, length); + pj_ansi_sprintf(data, "PUT test\n"); + param.reqdata.data = data; + param.reqdata.size = length; + if (pj_http_req_create(pool, &url, timer_heap, ioqueue, + ¶m, &hcb, &http_req)) + return -53; + + if (pj_http_req_start(http_req)) + return -55; + + while (pj_http_req_is_running(http_req)) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer_heap, NULL); + } + +#ifdef USE_LOCAL_SERVER + thread_quit = PJ_TRUE; + pj_thread_join(g_server.thread); + pj_sock_close(g_server.sock); +#endif + + pj_http_req_destroy(http_req); + pj_ioqueue_destroy(ioqueue); + pj_timer_heap_destroy(timer_heap); + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* + * PUT request scenario 2: using on_send_data() callback to + * sending the data in chunks + */ +int http_client_test_put2() +{ + pj_str_t url; + pj_http_req_callback hcb; + pj_http_req_param param; + char urlbuf[80]; + + pj_bzero(&hcb, sizeof(hcb)); + hcb.on_complete = &on_complete; + hcb.on_send_data = &on_send_data; + hcb.on_data_read = &on_data_read; + hcb.on_response = &on_response; + + /* Create pool, timer, and ioqueue */ + pool = pj_pool_create(mem, NULL, 8192, 4096, NULL); + if (pj_timer_heap_create(pool, 16, &timer_heap)) + return -51; + if (pj_ioqueue_create(pool, 16, &ioqueue)) + return -52; + +#ifdef USE_LOCAL_SERVER + thread_quit = PJ_FALSE; + g_server.action = ACTION_REPLY; + g_server.send_content_length = PJ_TRUE; + g_server.data_size = 0; + g_server.buf_size = 16384; + + sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, + &g_server.sock); + if (sstatus != PJ_SUCCESS) + return -41; + + pj_sockaddr_in_init(&addr, NULL, 0); + + sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr)); + if (sstatus != PJ_SUCCESS) + return -43; + + { + pj_sockaddr_in addr; + int addr_len = sizeof(addr); + sstatus = pj_sock_getsockname(g_server.sock, &addr, &addr_len); + if (sstatus != PJ_SUCCESS) + return -44; + g_server.port = pj_sockaddr_in_get_port(&addr); + pj_ansi_snprintf(urlbuf, sizeof(urlbuf), + "http://127.0.0.1:%d/test/test2.txt", + g_server.port); + url = pj_str(urlbuf); + } + + sstatus = pj_sock_listen(g_server.sock, 8); + if (sstatus != PJ_SUCCESS) + return -45; + + sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server, + 0, 0, &g_server.thread); + if (sstatus != PJ_SUCCESS) + return -47; + +#else + pj_cstr(&url, "http://127.0.0.1:280/test/test2.txt"); + +#endif + + pj_http_req_param_default(¶m); + pj_strset2(¶m.method, (char*)"PUT"); + total_size = 15383; + send_size = 0; + param.reqdata.total_size = total_size; + if (pj_http_req_create(pool, &url, timer_heap, ioqueue, + ¶m, &hcb, &http_req)) + return -53; + + if (pj_http_req_start(http_req)) + return -55; + + while (pj_http_req_is_running(http_req)) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer_heap, NULL); + } + +#ifdef USE_LOCAL_SERVER + thread_quit = PJ_TRUE; + pj_thread_join(g_server.thread); + pj_sock_close(g_server.sock); +#endif + + pj_http_req_destroy(http_req); + pj_ioqueue_destroy(ioqueue); + pj_timer_heap_destroy(timer_heap); + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +int http_client_test_delete() +{ + pj_str_t url; + pj_http_req_callback hcb; + pj_http_req_param param; + char urlbuf[80]; + + pj_bzero(&hcb, sizeof(hcb)); + hcb.on_complete = &on_complete; + hcb.on_response = &on_response; + + /* Create pool, timer, and ioqueue */ + pool = pj_pool_create(mem, NULL, 8192, 4096, NULL); + if (pj_timer_heap_create(pool, 16, &timer_heap)) + return -61; + if (pj_ioqueue_create(pool, 16, &ioqueue)) + return -62; + +#ifdef USE_LOCAL_SERVER + thread_quit = PJ_FALSE; + g_server.action = ACTION_REPLY; + g_server.send_content_length = PJ_TRUE; + g_server.data_size = 0; + g_server.buf_size = 1024; + + sstatus = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, + &g_server.sock); + if (sstatus != PJ_SUCCESS) + return -41; + + pj_sockaddr_in_init(&addr, NULL, 0); + + sstatus = pj_sock_bind(g_server.sock, &addr, sizeof(addr)); + if (sstatus != PJ_SUCCESS) + return -43; + + { + pj_sockaddr_in addr; + int addr_len = sizeof(addr); + sstatus = pj_sock_getsockname(g_server.sock, &addr, &addr_len); + if (sstatus != PJ_SUCCESS) + return -44; + g_server.port = pj_sockaddr_in_get_port(&addr); + pj_ansi_snprintf(urlbuf, sizeof(urlbuf), + "http://127.0.0.1:%d/test/test2.txt", + g_server.port); + url = pj_str(urlbuf); + } + + sstatus = pj_sock_listen(g_server.sock, 8); + if (sstatus != PJ_SUCCESS) + return -45; + + sstatus = pj_thread_create(pool, NULL, &server_thread, &g_server, + 0, 0, &g_server.thread); + if (sstatus != PJ_SUCCESS) + return -47; + +#else + pj_cstr(&url, "http://127.0.0.1:280/test/test2.txt"); +#endif + + pj_http_req_param_default(¶m); + pj_strset2(¶m.method, (char*)"DELETE"); + if (pj_http_req_create(pool, &url, timer_heap, ioqueue, + ¶m, &hcb, &http_req)) + return -63; + + if (pj_http_req_start(http_req)) + return -65; + + while (pj_http_req_is_running(http_req)) { + pj_time_val delay = {0, 50}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer_heap, NULL); + } + +#ifdef USE_LOCAL_SERVER + thread_quit = PJ_TRUE; + pj_thread_join(g_server.thread); + pj_sock_close(g_server.sock); +#endif + + pj_http_req_destroy(http_req); + pj_ioqueue_destroy(ioqueue); + pj_timer_heap_destroy(timer_heap); + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +int http_client_test() +{ + int rc; + + PJ_LOG(3, (THIS_FILE, "..Testing URL parsing")); + rc = parse_url_test(); + if (rc) + return rc; + + PJ_LOG(3, (THIS_FILE, "..Testing GET request scenario 1")); + rc = http_client_test1(); + if (rc) + return rc; + + PJ_LOG(3, (THIS_FILE, "..Testing GET request scenario 2")); + rc = http_client_test2(); + if (rc) + return rc; + + PJ_LOG(3, (THIS_FILE, "..Testing PUT request scenario 1")); + rc = http_client_test_put1(); + if (rc) + return rc; + + PJ_LOG(3, (THIS_FILE, "..Testing PUT request scenario 2")); + rc = http_client_test_put2(); + if (rc) + return rc; + + PJ_LOG(3, (THIS_FILE, "..Testing DELETE request")); + rc = http_client_test_delete(); + if (rc) + return rc; + + return PJ_SUCCESS; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_http_client_test; +#endif /* INCLUDE_HTTP_CLIENT_TEST */ diff --git a/pjlib-util/src/pjlib-util-test/main.c b/pjlib-util/src/pjlib-util-test/main.c new file mode 100644 index 0000000..7cc9920 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/main.c @@ -0,0 +1,63 @@ +/* $Id$ */ +/* + * 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 <pj/string.h> + +#if defined(PJ_SUNOS) && PJ_SUNOS!=0 +#include <signal.h> +static void init_signals() +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = SIG_IGN; + + sigaction(SIGALRM, &act, NULL); +} + +#else +#define init_signals() +#endif + +#define boost() + +int main(int argc, char *argv[]) +{ + int rc; + + PJ_UNUSED_ARG(argc); + PJ_UNUSED_ARG(argv); + + boost(); + init_signals(); + + rc = test_main(); + + if (argc==2 && pj_ansi_strcmp(argv[1], "-i")==0) { + char s[10]; + + puts("Press ENTER to quit"); + if (fgets(s, sizeof(s), stdin) == NULL) + return rc; + } + + return rc; +} + diff --git a/pjlib-util/src/pjlib-util-test/main_rtems.c b/pjlib-util/src/pjlib-util-test/main_rtems.c new file mode 100644 index 0000000..4b7b58d --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/main_rtems.c @@ -0,0 +1,11 @@ + +/* + * !! OIY OIY !! + * + * The purpose of this file is only to get pjlib-util-test linked. I haven't + * actually tried to run pjlib-util-test on RTEMS!! + * + */ + + +#include "../../pjlib/src/pjlib-test/main_rtems.c" diff --git a/pjlib-util/src/pjlib-util-test/main_win32.c b/pjlib-util/src/pjlib-util-test/main_win32.c new file mode 100644 index 0000000..3043a39 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/main_win32.c @@ -0,0 +1 @@ +#include "../../pjlib/src/pjlib-test/main_win32.c" diff --git a/pjlib-util/src/pjlib-util-test/resolver_test.c b/pjlib-util/src/pjlib-util-test/resolver_test.c new file mode 100644 index 0000000..f08f8b4 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/resolver_test.c @@ -0,0 +1,1405 @@ +/* $Id: resolver_test.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * 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 <pjlib-util.h> +#include <pjlib.h> +#include "test.h" + + +#define THIS_FILE "srv_resolver_test.c" + +//////////////////////////////////////////////////////////////////////////// +/* + * TODO: create various invalid DNS packets. + */ + + +//////////////////////////////////////////////////////////////////////////// + + +#define ACTION_REPLY 0 +#define ACTION_IGNORE -1 +#define ACTION_CB -2 + +static struct server_t +{ + pj_sock_t sock; + pj_uint16_t port; + pj_thread_t *thread; + + /* Action: + * 0: reply with the response in resp. + * -1: ignore query (to simulate timeout). + * other: reply with that error + */ + int action; + + pj_dns_parsed_packet resp; + void (*action_cb)(const pj_dns_parsed_packet *pkt, + pj_dns_parsed_packet **p_res); + + unsigned pkt_count; + +} g_server[2]; + +static pj_pool_t *pool; +static pj_dns_resolver *resolver; +static pj_bool_t thread_quit; +static pj_timer_heap_t *timer_heap; +static pj_ioqueue_t *ioqueue; +static pj_thread_t *poll_thread; +static pj_sem_t *sem; +static pj_dns_settings set; + +#define MAX_LABEL 32 + +struct label_tab +{ + unsigned count; + + struct { + unsigned pos; + pj_str_t label; + } a[MAX_LABEL]; +}; + +static void write16(pj_uint8_t *p, pj_uint16_t val) +{ + p[0] = (pj_uint8_t)(val >> 8); + p[1] = (pj_uint8_t)(val & 0xFF); +} + +static void write32(pj_uint8_t *p, pj_uint32_t val) +{ + val = pj_htonl(val); + pj_memcpy(p, &val, 4); +} + +static int print_name(pj_uint8_t *pkt, int size, + pj_uint8_t *pos, const pj_str_t *name, + struct label_tab *tab) +{ + pj_uint8_t *p = pos; + const char *endlabel, *endname; + unsigned i; + pj_str_t label; + + /* Check if name is in the table */ + for (i=0; i<tab->count; ++i) { + if (pj_strcmp(&tab->a[i].label, name)==0) + break; + } + + if (i != tab->count) { + write16(p, (pj_uint16_t)(tab->a[i].pos | (0xc0 << 8))); + return 2; + } else { + if (tab->count < MAX_LABEL) { + tab->a[tab->count].pos = (p-pkt); + tab->a[tab->count].label.ptr = (char*)(p+1); + tab->a[tab->count].label.slen = name->slen; + ++tab->count; + } + } + + endlabel = name->ptr; + endname = name->ptr + name->slen; + + label.ptr = (char*)name->ptr; + + while (endlabel != endname) { + + while (endlabel != endname && *endlabel != '.') + ++endlabel; + + label.slen = (endlabel - label.ptr); + + if (size < label.slen+1) + return -1; + + *p = (pj_uint8_t)label.slen; + pj_memcpy(p+1, label.ptr, label.slen); + + size -= (label.slen+1); + p += (label.slen+1); + + if (endlabel != endname && *endlabel == '.') + ++endlabel; + label.ptr = (char*)endlabel; + } + + if (size == 0) + return -1; + + *p++ = '\0'; + + return p-pos; +} + +static int print_rr(pj_uint8_t *pkt, int size, pj_uint8_t *pos, + const pj_dns_parsed_rr *rr, struct label_tab *tab) +{ + pj_uint8_t *p = pos; + int len; + + len = print_name(pkt, size, pos, &rr->name, tab); + if (len < 0) + return -1; + + p += len; + size -= len; + + if (size < 8) + return -1; + + pj_assert(rr->dnsclass == 1); + + write16(p+0, (pj_uint16_t)rr->type); /* type */ + write16(p+2, (pj_uint16_t)rr->dnsclass); /* class */ + write32(p+4, rr->ttl); /* TTL */ + + p += 8; + size -= 8; + + if (rr->type == PJ_DNS_TYPE_A) { + + if (size < 6) + return -1; + + /* RDLEN is 4 */ + write16(p, 4); + + /* Address */ + pj_memcpy(p+2, &rr->rdata.a.ip_addr, 4); + + p += 6; + size -= 6; + + } else if (rr->type == PJ_DNS_TYPE_CNAME || + rr->type == PJ_DNS_TYPE_NS || + rr->type == PJ_DNS_TYPE_PTR) { + + if (size < 4) + return -1; + + len = print_name(pkt, size-2, p+2, &rr->rdata.cname.name, tab); + if (len < 0) + return -1; + + write16(p, (pj_uint16_t)len); + + p += (len + 2); + size -= (len + 2); + + } else if (rr->type == PJ_DNS_TYPE_SRV) { + + if (size < 10) + return -1; + + write16(p+2, rr->rdata.srv.prio); /* Priority */ + write16(p+4, rr->rdata.srv.weight); /* Weight */ + write16(p+6, rr->rdata.srv.port); /* Port */ + + /* Target */ + len = print_name(pkt, size-8, p+8, &rr->rdata.srv.target, tab); + if (len < 0) + return -1; + + /* RDLEN */ + write16(p, (pj_uint16_t)(len + 6)); + + p += (len + 8); + size -= (len + 8); + + } else { + pj_assert(!"Not supported"); + return -1; + } + + return p-pos; +} + +static int print_packet(const pj_dns_parsed_packet *rec, pj_uint8_t *pkt, + int size) +{ + pj_uint8_t *p = pkt; + struct label_tab tab; + int i, len; + + tab.count = 0; + +#if 0 + pj_enter_critical_section(); + PJ_LOG(3,(THIS_FILE, "Sending response:")); + pj_dns_dump_packet(rec); + pj_leave_critical_section(); +#endif + + pj_assert(sizeof(pj_dns_hdr)==12); + if (size < (int)sizeof(pj_dns_hdr)) + return -1; + + /* Initialize header */ + write16(p+0, rec->hdr.id); + write16(p+2, rec->hdr.flags); + write16(p+4, rec->hdr.qdcount); + write16(p+6, rec->hdr.anscount); + write16(p+8, rec->hdr.nscount); + write16(p+10, rec->hdr.arcount); + + p = pkt + sizeof(pj_dns_hdr); + size -= sizeof(pj_dns_hdr); + + /* Print queries */ + for (i=0; i<rec->hdr.qdcount; ++i) { + + len = print_name(pkt, size, p, &rec->q[i].name, &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + + if (size < 4) + return -1; + + /* Set type */ + write16(p+0, (pj_uint16_t)rec->q[i].type); + + /* Set class (IN=1) */ + pj_assert(rec->q[i].dnsclass == 1); + write16(p+2, rec->q[i].dnsclass); + + p += 4; + } + + /* Print answers */ + for (i=0; i<rec->hdr.anscount; ++i) { + len = print_rr(pkt, size, p, &rec->ans[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + /* Print NS records */ + for (i=0; i<rec->hdr.nscount; ++i) { + len = print_rr(pkt, size, p, &rec->ns[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + /* Print additional records */ + for (i=0; i<rec->hdr.arcount; ++i) { + len = print_rr(pkt, size, p, &rec->arr[i], &tab); + if (len < 0) + return -1; + + p += len; + size -= len; + } + + return p - pkt; +} + + +static int server_thread(void *p) +{ + struct server_t *srv = (struct server_t*)p; + + while (!thread_quit) { + pj_fd_set_t rset; + pj_time_val timeout = {0, 500}; + pj_sockaddr_in src_addr; + pj_dns_parsed_packet *req; + char pkt[1024]; + pj_ssize_t pkt_len; + int rc, src_len; + + PJ_FD_ZERO(&rset); + PJ_FD_SET(srv->sock, &rset); + + rc = pj_sock_select(srv->sock+1, &rset, NULL, NULL, &timeout); + if (rc != 1) + continue; + + src_len = sizeof(src_addr); + pkt_len = sizeof(pkt); + rc = pj_sock_recvfrom(srv->sock, pkt, &pkt_len, 0, + &src_addr, &src_len); + if (rc != 0) { + app_perror("Server error receiving packet", rc); + continue; + } + + PJ_LOG(5,(THIS_FILE, "Server %d processing packet", srv - &g_server[0])); + srv->pkt_count++; + + rc = pj_dns_parse_packet(pool, pkt, pkt_len, &req); + if (rc != PJ_SUCCESS) { + app_perror("server error parsing packet", rc); + continue; + } + + /* Verify packet */ + pj_assert(req->hdr.qdcount == 1); + pj_assert(req->q[0].dnsclass == 1); + + /* Simulate network RTT */ + pj_thread_sleep(50); + + if (srv->action == ACTION_IGNORE) { + continue; + } else if (srv->action == ACTION_REPLY) { + srv->resp.hdr.id = req->hdr.id; + pkt_len = print_packet(&srv->resp, (pj_uint8_t*)pkt, sizeof(pkt)); + pj_sock_sendto(srv->sock, pkt, &pkt_len, 0, &src_addr, src_len); + } else if (srv->action == ACTION_CB) { + pj_dns_parsed_packet *resp; + (*srv->action_cb)(req, &resp); + resp->hdr.id = req->hdr.id; + pkt_len = print_packet(resp, (pj_uint8_t*)pkt, sizeof(pkt)); + pj_sock_sendto(srv->sock, pkt, &pkt_len, 0, &src_addr, src_len); + } else if (srv->action > 0) { + req->hdr.flags |= PJ_DNS_SET_RCODE(srv->action); + pkt_len = print_packet(req, (pj_uint8_t*)pkt, sizeof(pkt)); + pj_sock_sendto(srv->sock, pkt, &pkt_len, 0, &src_addr, src_len); + } + } + + return 0; +} + +static int poll_worker_thread(void *p) +{ + PJ_UNUSED_ARG(p); + + while (!thread_quit) { + pj_time_val delay = {0, 100}; + pj_timer_heap_poll(timer_heap, NULL); + pj_ioqueue_poll(ioqueue, &delay); + } + + return 0; +} + +static void destroy(void); + +static int init(void) +{ + pj_status_t status; + pj_str_t nameservers[2]; + pj_uint16_t ports[2]; + int i; + + nameservers[0] = pj_str("127.0.0.1"); + ports[0] = 5553; + nameservers[1] = pj_str("127.0.0.1"); + ports[1] = 5554; + + g_server[0].port = ports[0]; + g_server[1].port = ports[1]; + + pool = pj_pool_create(mem, NULL, 2000, 2000, NULL); + + status = pj_sem_create(pool, NULL, 0, 2, &sem); + pj_assert(status == PJ_SUCCESS); + + for (i=0; i<2; ++i) { + pj_sockaddr_in addr; + + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &g_server[i].sock); + if (status != PJ_SUCCESS) + return -10; + + pj_sockaddr_in_init(&addr, NULL, (pj_uint16_t)g_server[i].port); + + status = pj_sock_bind(g_server[i].sock, &addr, sizeof(addr)); + if (status != PJ_SUCCESS) + return -20; + + status = pj_thread_create(pool, NULL, &server_thread, &g_server[i], + 0, 0, &g_server[i].thread); + if (status != PJ_SUCCESS) + return -30; + } + + status = pj_timer_heap_create(pool, 16, &timer_heap); + pj_assert(status == PJ_SUCCESS); + + status = pj_ioqueue_create(pool, 16, &ioqueue); + pj_assert(status == PJ_SUCCESS); + + status = pj_dns_resolver_create(mem, NULL, 0, timer_heap, ioqueue, &resolver); + if (status != PJ_SUCCESS) + return -40; + + pj_dns_resolver_get_settings(resolver, &set); + set.good_ns_ttl = 20; + set.bad_ns_ttl = 20; + pj_dns_resolver_set_settings(resolver, &set); + + status = pj_dns_resolver_set_ns(resolver, 2, nameservers, ports); + pj_assert(status == PJ_SUCCESS); + + status = pj_thread_create(pool, NULL, &poll_worker_thread, NULL, 0, 0, &poll_thread); + pj_assert(status == PJ_SUCCESS); + + return 0; +} + + +static void destroy(void) +{ + int i; + + thread_quit = PJ_TRUE; + + for (i=0; i<2; ++i) { + pj_thread_join(g_server[i].thread); + pj_sock_close(g_server[i].sock); + } + + pj_thread_join(poll_thread); + + pj_dns_resolver_destroy(resolver, PJ_FALSE); + pj_ioqueue_destroy(ioqueue); + pj_timer_heap_destroy(timer_heap); + + pj_sem_destroy(sem); + pj_pool_release(pool); +} + + +//////////////////////////////////////////////////////////////////////////// +/* DNS A parser tests */ +static int a_parser_test(void) +{ + pj_dns_parsed_packet pkt; + pj_dns_a_record rec; + pj_status_t rc; + + PJ_LOG(3,(THIS_FILE, " DNS A record parser tests")); + + pkt.q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query); + pkt.ans = (pj_dns_parsed_rr*) + pj_pool_calloc(pool, 32, sizeof(pj_dns_parsed_rr)); + + /* Simple answer with direct A record, but with addition of + * a CNAME and another A to confuse the parser. + */ + PJ_LOG(3,(THIS_FILE, " A RR with duplicate CNAME/A")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 3; + + /* This is the RR corresponding to the query */ + pkt.ans[0].name = pj_str("ahost"); + pkt.ans[0].type = PJ_DNS_TYPE_A; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.a.ip_addr.s_addr = 0x01020304; + + /* CNAME to confuse the parser */ + pkt.ans[1].name = pj_str("ahost"); + pkt.ans[1].type = PJ_DNS_TYPE_CNAME; + pkt.ans[1].dnsclass = 1; + pkt.ans[1].ttl = 1; + pkt.ans[1].rdata.cname.name = pj_str("bhost"); + + /* DNS A RR to confuse the parser */ + pkt.ans[2].name = pj_str("bhost"); + pkt.ans[2].type = PJ_DNS_TYPE_A; + pkt.ans[2].dnsclass = 1; + pkt.ans[2].ttl = 1; + pkt.ans[2].rdata.a.ip_addr.s_addr = 0x0203; + + + rc = pj_dns_parse_a_response(&pkt, &rec); + pj_assert(rc == PJ_SUCCESS); + pj_assert(pj_strcmp2(&rec.name, "ahost")==0); + pj_assert(rec.alias.slen == 0); + pj_assert(rec.addr_count == 1); + pj_assert(rec.addr[0].s_addr == 0x01020304); + + /* Answer with the target corresponds to a CNAME entry, but not + * as the first record, and with additions of some CNAME and A + * entries to confuse the parser. + */ + PJ_LOG(3,(THIS_FILE, " CNAME RR with duplicate CNAME/A")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 4; + + /* This is the DNS A record for the alias */ + pkt.ans[0].name = pj_str("ahostalias"); + pkt.ans[0].type = PJ_DNS_TYPE_A; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.a.ip_addr.s_addr = 0x02020202; + + /* CNAME entry corresponding to the query */ + pkt.ans[1].name = pj_str("ahost"); + pkt.ans[1].type = PJ_DNS_TYPE_CNAME; + pkt.ans[1].dnsclass = 1; + pkt.ans[1].ttl = 1; + pkt.ans[1].rdata.cname.name = pj_str("ahostalias"); + + /* Another CNAME to confuse the parser */ + pkt.ans[2].name = pj_str("ahost"); + pkt.ans[2].type = PJ_DNS_TYPE_CNAME; + pkt.ans[2].dnsclass = 1; + pkt.ans[2].ttl = 1; + pkt.ans[2].rdata.cname.name = pj_str("ahostalias2"); + + /* Another DNS A to confuse the parser */ + pkt.ans[3].name = pj_str("ahostalias2"); + pkt.ans[3].type = PJ_DNS_TYPE_A; + pkt.ans[3].dnsclass = 1; + pkt.ans[3].ttl = 1; + pkt.ans[3].rdata.a.ip_addr.s_addr = 0x03030303; + + rc = pj_dns_parse_a_response(&pkt, &rec); + pj_assert(rc == PJ_SUCCESS); + pj_assert(pj_strcmp2(&rec.name, "ahost")==0); + pj_assert(pj_strcmp2(&rec.alias, "ahostalias")==0); + pj_assert(rec.addr_count == 1); + pj_assert(rec.addr[0].s_addr == 0x02020202); + + /* + * No query section. + */ + PJ_LOG(3,(THIS_FILE, " No query section")); + pkt.hdr.qdcount = 0; + pkt.hdr.anscount = 0; + + rc = pj_dns_parse_a_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSINANSWER); + + /* + * No answer section. + */ + PJ_LOG(3,(THIS_FILE, " No answer section")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 0; + + rc = pj_dns_parse_a_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC); + + /* + * Answer doesn't match query. + */ + PJ_LOG(3,(THIS_FILE, " Answer doesn't match query")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 1; + + /* An answer that doesn't match the query */ + pkt.ans[0].name = pj_str("ahostalias"); + pkt.ans[0].type = PJ_DNS_TYPE_A; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.a.ip_addr.s_addr = 0x02020202; + + rc = pj_dns_parse_a_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC); + + + /* + * DNS CNAME that doesn't have corresponding DNS A. + */ + PJ_LOG(3,(THIS_FILE, " CNAME with no matching DNS A RR (1)")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 1; + + /* The CNAME */ + pkt.ans[0].name = pj_str("ahost"); + pkt.ans[0].type = PJ_DNS_TYPE_CNAME; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.cname.name = pj_str("ahostalias"); + + rc = pj_dns_parse_a_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC); + + + /* + * DNS CNAME that doesn't have corresponding DNS A. + */ + PJ_LOG(3,(THIS_FILE, " CNAME with no matching DNS A RR (2)")); + pkt.hdr.flags = 0; + pkt.hdr.qdcount = 1; + pkt.q[0].type = PJ_DNS_TYPE_A; + pkt.q[0].dnsclass = 1; + pkt.q[0].name = pj_str("ahost"); + pkt.hdr.anscount = 2; + + /* The CNAME */ + pkt.ans[0].name = pj_str("ahost"); + pkt.ans[0].type = PJ_DNS_TYPE_CNAME; + pkt.ans[0].dnsclass = 1; + pkt.ans[0].ttl = 1; + pkt.ans[0].rdata.cname.name = pj_str("ahostalias"); + + /* DNS A record, but the name doesn't match */ + pkt.ans[1].name = pj_str("ahost"); + pkt.ans[1].type = PJ_DNS_TYPE_A; + pkt.ans[1].dnsclass = 1; + pkt.ans[1].ttl = 1; + pkt.ans[1].rdata.a.ip_addr.s_addr = 0x01020304; + + rc = pj_dns_parse_a_response(&pkt, &rec); + pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC); + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////// +/* Simple DNS test */ +#define IP_ADDR0 0x00010203 + +static void dns_callback(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *resp) +{ + PJ_UNUSED_ARG(user_data); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return); + PJ_ASSERT_ON_FAIL(resp, return); + PJ_ASSERT_ON_FAIL(resp->hdr.anscount == 1, return); + PJ_ASSERT_ON_FAIL(resp->ans[0].type == PJ_DNS_TYPE_A, return); + PJ_ASSERT_ON_FAIL(resp->ans[0].rdata.a.ip_addr.s_addr == IP_ADDR0, return); + +} + + +static int simple_test(void) +{ + pj_str_t name = pj_str("helloworld"); + pj_dns_parsed_packet *r; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " simple successful test")); + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + g_server[0].action = ACTION_REPLY; + r = &g_server[0].resp; + r->hdr.qdcount = 1; + r->hdr.anscount = 1; + r->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query); + r->q[0].type = PJ_DNS_TYPE_A; + r->q[0].dnsclass = 1; + r->q[0].name = name; + r->ans = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_rr); + r->ans[0].type = PJ_DNS_TYPE_A; + r->ans[0].dnsclass = 1; + r->ans[0].name = name; + r->ans[0].rdata.a.ip_addr.s_addr = IP_ADDR0; + + g_server[1].action = ACTION_REPLY; + r = &g_server[1].resp; + r->hdr.qdcount = 1; + r->hdr.anscount = 1; + r->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query); + r->q[0].type = PJ_DNS_TYPE_A; + r->q[0].dnsclass = 1; + r->q[0].name = name; + r->ans = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_rr); + r->ans[0].type = PJ_DNS_TYPE_A; + r->ans[0].dnsclass = 1; + r->ans[0].name = name; + r->ans[0].rdata.a.ip_addr.s_addr = IP_ADDR0; + + status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0, + &dns_callback, NULL, NULL); + if (status != PJ_SUCCESS) + return -1000; + + pj_sem_wait(sem); + pj_thread_sleep(1000); + + + /* Both servers must get packet */ + pj_assert(g_server[0].pkt_count == 1); + pj_assert(g_server[1].pkt_count == 1); + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////// +/* DNS nameserver fail-over test */ + +static void dns_callback_1b(void *user_data, + pj_status_t status, + pj_dns_parsed_packet *resp) +{ + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(resp); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status==PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_NXDOMAIN), + return); +} + + + + +/* DNS test */ +static int dns_test(void) +{ + pj_str_t name = pj_str("name00"); + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " simple error response test")); + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + g_server[0].action = PJ_DNS_RCODE_NXDOMAIN; + g_server[1].action = PJ_DNS_RCODE_NXDOMAIN; + + status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0, + &dns_callback_1b, NULL, NULL); + if (status != PJ_SUCCESS) + return -1000; + + pj_sem_wait(sem); + pj_thread_sleep(1000); + + /* Now only server 0 should get packet, since both servers are + * in STATE_ACTIVE state + */ + pj_assert((g_server[0].pkt_count == 1 && g_server[1].pkt_count == 0) || + (g_server[1].pkt_count == 1 && g_server[0].pkt_count == 0)); + + /* Wait to allow probing period to complete */ + PJ_LOG(3,(THIS_FILE, " waiting for active NS to expire (%d sec)", + set.good_ns_ttl)); + pj_thread_sleep(set.good_ns_ttl * 1000); + + /* + * Fail-over test + */ + PJ_LOG(3,(THIS_FILE, " failing server0")); + g_server[0].action = ACTION_IGNORE; + g_server[1].action = PJ_DNS_RCODE_NXDOMAIN; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + name = pj_str("name01"); + status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0, + &dns_callback_1b, NULL, NULL); + if (status != PJ_SUCCESS) + return -1000; + + pj_sem_wait(sem); + + /* + * Check that both servers still receive requests, since they are + * in probing state. + */ + PJ_LOG(3,(THIS_FILE, " checking both NS during probing period")); + g_server[0].action = ACTION_IGNORE; + g_server[1].action = PJ_DNS_RCODE_NXDOMAIN; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + name = pj_str("name02"); + status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0, + &dns_callback_1b, NULL, NULL); + if (status != PJ_SUCCESS) + return -1000; + + pj_sem_wait(sem); + pj_thread_sleep(set.qretr_delay * set.qretr_count); + + /* Both servers must get requests */ + pj_assert(g_server[0].pkt_count >= 1); + pj_assert(g_server[1].pkt_count == 1); + + /* Wait to allow probing period to complete */ + PJ_LOG(3,(THIS_FILE, " waiting for probing state to end (%d sec)", + set.qretr_delay * + (set.qretr_count+2) / 1000)); + pj_thread_sleep(set.qretr_delay * (set.qretr_count + 2)); + + + /* + * Now only server 1 should get requests. + */ + PJ_LOG(3,(THIS_FILE, " verifying only good NS is used")); + g_server[0].action = PJ_DNS_RCODE_NXDOMAIN; + g_server[1].action = PJ_DNS_RCODE_NXDOMAIN; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + name = pj_str("name03"); + status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0, + &dns_callback_1b, NULL, NULL); + if (status != PJ_SUCCESS) + return -1000; + + pj_sem_wait(sem); + pj_thread_sleep(1000); + + /* Both servers must get requests */ + pj_assert(g_server[0].pkt_count == 0); + pj_assert(g_server[1].pkt_count == 1); + + /* Wait to allow probing period to complete */ + PJ_LOG(3,(THIS_FILE, " waiting for active NS to expire (%d sec)", + set.good_ns_ttl)); + pj_thread_sleep(set.good_ns_ttl * 1000); + + /* + * Now fail server 1 to switch to server 0 + */ + g_server[0].action = PJ_DNS_RCODE_NXDOMAIN; + g_server[1].action = ACTION_IGNORE; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + name = pj_str("name04"); + status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0, + &dns_callback_1b, NULL, NULL); + if (status != PJ_SUCCESS) + return -1000; + + pj_sem_wait(sem); + + /* Wait to allow probing period to complete */ + PJ_LOG(3,(THIS_FILE, " waiting for probing state (%d sec)", + set.qretr_delay * (set.qretr_count+2) / 1000)); + pj_thread_sleep(set.qretr_delay * (set.qretr_count + 2)); + + /* + * Now only server 0 should get requests. + */ + PJ_LOG(3,(THIS_FILE, " verifying good NS")); + g_server[0].action = PJ_DNS_RCODE_NXDOMAIN; + g_server[1].action = ACTION_IGNORE; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + name = pj_str("name05"); + status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0, + &dns_callback_1b, NULL, NULL); + if (status != PJ_SUCCESS) + return -1000; + + pj_sem_wait(sem); + pj_thread_sleep(1000); + + /* Only good NS should get request */ + pj_assert(g_server[0].pkt_count == 1); + pj_assert(g_server[1].pkt_count == 0); + + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////// +/* Resolver test, normal, with CNAME */ +#define IP_ADDR1 0x02030405 +#define PORT1 50061 + +static void action1_1(const pj_dns_parsed_packet *pkt, + pj_dns_parsed_packet **p_res) +{ + pj_dns_parsed_packet *res; + char *target = "sip.somedomain.com"; + + res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet); + + if (res->q == NULL) { + res->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query); + } + if (res->ans == NULL) { + res->ans = (pj_dns_parsed_rr*) + pj_pool_calloc(pool, 4, sizeof(pj_dns_parsed_rr)); + } + + res->hdr.qdcount = 1; + res->q[0].type = pkt->q[0].type; + res->q[0].dnsclass = pkt->q[0].dnsclass; + res->q[0].name = pkt->q[0].name; + + if (pkt->q[0].type == PJ_DNS_TYPE_SRV) { + + pj_assert(pj_strcmp2(&pkt->q[0].name, "_sip._udp.somedomain.com")==0); + + res->hdr.anscount = 1; + res->ans[0].type = PJ_DNS_TYPE_SRV; + res->ans[0].dnsclass = 1; + res->ans[0].name = res->q[0].name; + res->ans[0].ttl = 1; + res->ans[0].rdata.srv.prio = 1; + res->ans[0].rdata.srv.weight = 2; + res->ans[0].rdata.srv.port = PORT1; + res->ans[0].rdata.srv.target = pj_str(target); + + } else if (pkt->q[0].type == PJ_DNS_TYPE_A) { + char *alias = "sipalias.somedomain.com"; + + pj_assert(pj_strcmp2(&res->q[0].name, target)==0); + + res->hdr.anscount = 2; + res->ans[0].type = PJ_DNS_TYPE_CNAME; + res->ans[0].dnsclass = 1; + res->ans[0].ttl = 1000; /* resolver should select minimum TTL */ + res->ans[0].name = res->q[0].name; + res->ans[0].rdata.cname.name = pj_str(alias); + + res->ans[1].type = PJ_DNS_TYPE_A; + res->ans[1].dnsclass = 1; + res->ans[1].ttl = 1; + res->ans[1].name = pj_str(alias); + res->ans[1].rdata.a.ip_addr.s_addr = IP_ADDR1; + } + + *p_res = res; +} + +static void srv_cb_1(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + PJ_UNUSED_ARG(user_data); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return); + PJ_ASSERT_ON_FAIL(rec->count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 2, return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, "sip.somedomain.com")==0, + return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias.somedomain.com")==0, + return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].s_addr == IP_ADDR1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT1, return); + + +} + +static void srv_cb_1b(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + PJ_UNUSED_ARG(user_data); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status==PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_NXDOMAIN), + return); + PJ_ASSERT_ON_FAIL(rec->count == 0, return); +} + +static int srv_resolver_test(void) +{ + pj_status_t status; + pj_str_t domain = pj_str("somedomain.com"); + pj_str_t res_name = pj_str("_sip._udp."); + + /* Successful scenario */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): success scenario")); + + g_server[0].action = ACTION_CB; + g_server[0].action_cb = &action1_1; + g_server[1].action = ACTION_CB; + g_server[1].action_cb = &action1_1; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, PJ_TRUE, + NULL, &srv_cb_1, NULL); + pj_assert(status == PJ_SUCCESS); + + pj_sem_wait(sem); + + /* Because of previous tests, only NS 1 should get the request */ + pj_assert(g_server[0].pkt_count == 2); /* 2 because of SRV and A resolution */ + pj_assert(g_server[1].pkt_count == 0); + + + /* Wait until cache expires and nameserver state moves out from STATE_PROBING */ + PJ_LOG(3,(THIS_FILE, " waiting for cache to expire (~15 secs)..")); + pj_thread_sleep(1000 + + ((set.qretr_count + 2) * set.qretr_delay)); + + /* Successful scenario */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): parallel queries")); + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, PJ_TRUE, + NULL, &srv_cb_1, NULL); + pj_assert(status == PJ_SUCCESS); + + + status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, PJ_TRUE, + NULL, &srv_cb_1, NULL); + pj_assert(status == PJ_SUCCESS); + + pj_sem_wait(sem); + pj_sem_wait(sem); + + /* Only server one should get a query */ + pj_assert(g_server[0].pkt_count == 2); /* 2 because of SRV and A resolution */ + pj_assert(g_server[1].pkt_count == 0); + + /* Since TTL is one, subsequent queries should fail */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): cache expires scenario")); + + + pj_thread_sleep(1000); + + g_server[0].action = PJ_DNS_RCODE_NXDOMAIN; + g_server[1].action = PJ_DNS_RCODE_NXDOMAIN; + + status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, PJ_TRUE, + NULL, &srv_cb_1b, NULL); + pj_assert(status == PJ_SUCCESS); + + pj_sem_wait(sem); + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////// +/* Fallback because there's no SRV in answer */ +#define TARGET "domain2.com" +#define IP_ADDR2 0x02030405 +#define PORT2 50062 + +static void action2_1(const pj_dns_parsed_packet *pkt, + pj_dns_parsed_packet **p_res) +{ + pj_dns_parsed_packet *res; + + res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet); + + res->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query); + res->ans = (pj_dns_parsed_rr*) + pj_pool_calloc(pool, 4, sizeof(pj_dns_parsed_rr)); + + res->hdr.qdcount = 1; + res->q[0].type = pkt->q[0].type; + res->q[0].dnsclass = pkt->q[0].dnsclass; + res->q[0].name = pkt->q[0].name; + + if (pkt->q[0].type == PJ_DNS_TYPE_SRV) { + + pj_assert(pj_strcmp2(&pkt->q[0].name, "_sip._udp." TARGET)==0); + + res->hdr.anscount = 1; + res->ans[0].type = PJ_DNS_TYPE_A; // <-- this will cause the fallback + res->ans[0].dnsclass = 1; + res->ans[0].name = res->q[0].name; + res->ans[0].ttl = 1; + res->ans[0].rdata.srv.prio = 1; + res->ans[0].rdata.srv.weight = 2; + res->ans[0].rdata.srv.port = PORT2; + res->ans[0].rdata.srv.target = pj_str("sip01." TARGET); + + } else if (pkt->q[0].type == PJ_DNS_TYPE_A) { + char *alias = "sipalias01." TARGET; + + pj_assert(pj_strcmp2(&res->q[0].name, TARGET)==0); + + res->hdr.anscount = 2; + res->ans[0].type = PJ_DNS_TYPE_CNAME; + res->ans[0].dnsclass = 1; + res->ans[0].name = res->q[0].name; + res->ans[0].ttl = 1; + res->ans[0].rdata.cname.name = pj_str(alias); + + res->ans[1].type = PJ_DNS_TYPE_A; + res->ans[1].dnsclass = 1; + res->ans[1].name = pj_str(alias); + res->ans[1].ttl = 1; + res->ans[1].rdata.a.ip_addr.s_addr = IP_ADDR2; + } + + *p_res = res; +} + +static void srv_cb_2(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + PJ_UNUSED_ARG(user_data); + + pj_sem_post(sem); + + PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return); + PJ_ASSERT_ON_FAIL(rec->count == 1, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 0, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 0, return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, TARGET)==0, + return); + PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias01." TARGET)==0, + return); + PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].s_addr == IP_ADDR2, return); + PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT2, return); +} + +static int srv_resolver_fallback_test(void) +{ + pj_status_t status; + pj_str_t domain = pj_str(TARGET); + pj_str_t res_name = pj_str("_sip._udp."); + + PJ_LOG(3,(THIS_FILE, " srv_resolve(): fallback test")); + + g_server[0].action = ACTION_CB; + g_server[0].action_cb = &action2_1; + g_server[1].action = ACTION_CB; + g_server[1].action_cb = &action2_1; + + status = pj_dns_srv_resolve(&domain, &res_name, PORT2, pool, resolver, PJ_TRUE, + NULL, &srv_cb_2, NULL); + if (status != PJ_SUCCESS) { + app_perror(" srv_resolve error", status); + pj_assert(status == PJ_SUCCESS); + } + + pj_sem_wait(sem); + + /* Subsequent query should just get the response from the cache */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): cache test")); + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + status = pj_dns_srv_resolve(&domain, &res_name, PORT2, pool, resolver, PJ_TRUE, + NULL, &srv_cb_2, NULL); + if (status != PJ_SUCCESS) { + app_perror(" srv_resolve error", status); + pj_assert(status == PJ_SUCCESS); + } + + pj_sem_wait(sem); + + pj_assert(g_server[0].pkt_count == 0); + pj_assert(g_server[1].pkt_count == 0); + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////// +/* Too many SRV or A entries */ +#define DOMAIN3 "d3" +#define SRV_COUNT3 (PJ_DNS_SRV_MAX_ADDR+1) +#define A_COUNT3 (PJ_DNS_MAX_IP_IN_A_REC+1) +#define PORT3 50063 +#define IP_ADDR3 0x03030303 + +static void action3_1(const pj_dns_parsed_packet *pkt, + pj_dns_parsed_packet **p_res) +{ + pj_dns_parsed_packet *res; + unsigned i; + + res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet); + + if (res->q == NULL) { + res->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query); + } + + res->hdr.qdcount = 1; + res->q[0].type = pkt->q[0].type; + res->q[0].dnsclass = pkt->q[0].dnsclass; + res->q[0].name = pkt->q[0].name; + + if (pkt->q[0].type == PJ_DNS_TYPE_SRV) { + + pj_assert(pj_strcmp2(&pkt->q[0].name, "_sip._udp." DOMAIN3)==0); + + res->hdr.anscount = SRV_COUNT3; + res->ans = (pj_dns_parsed_rr*) + pj_pool_calloc(pool, SRV_COUNT3, sizeof(pj_dns_parsed_rr)); + + for (i=0; i<SRV_COUNT3; ++i) { + char *target; + + res->ans[i].type = PJ_DNS_TYPE_SRV; + res->ans[i].dnsclass = 1; + res->ans[i].name = res->q[0].name; + res->ans[i].ttl = 1; + res->ans[i].rdata.srv.prio = (pj_uint16_t)i; + res->ans[i].rdata.srv.weight = 2; + res->ans[i].rdata.srv.port = (pj_uint16_t)(PORT3+i); + + target = (char*)pj_pool_alloc(pool, 16); + sprintf(target, "sip%02d." DOMAIN3, i); + res->ans[i].rdata.srv.target = pj_str(target); + } + + } else if (pkt->q[0].type == PJ_DNS_TYPE_A) { + + //pj_assert(pj_strcmp2(&res->q[0].name, "sip." DOMAIN3)==0); + + res->hdr.anscount = A_COUNT3; + res->ans = (pj_dns_parsed_rr*) + pj_pool_calloc(pool, A_COUNT3, sizeof(pj_dns_parsed_rr)); + + for (i=0; i<A_COUNT3; ++i) { + res->ans[i].type = PJ_DNS_TYPE_A; + res->ans[i].dnsclass = 1; + res->ans[i].ttl = 1; + res->ans[i].name = res->q[0].name; + res->ans[i].rdata.a.ip_addr.s_addr = IP_ADDR3+i; + } + } + + *p_res = res; +} + +static void srv_cb_3(void *user_data, + pj_status_t status, + const pj_dns_srv_record *rec) +{ + unsigned i; + + PJ_UNUSED_ARG(user_data); + PJ_UNUSED_ARG(status); + PJ_UNUSED_ARG(rec); + + pj_assert(status == PJ_SUCCESS); + pj_assert(rec->count == PJ_DNS_SRV_MAX_ADDR); + + for (i=0; i<PJ_DNS_SRV_MAX_ADDR; ++i) { + unsigned j; + + pj_assert(rec->entry[i].priority == i); + pj_assert(rec->entry[i].weight == 2); + //pj_assert(pj_strcmp2(&rec->entry[i].server.name, "sip." DOMAIN3)==0); + pj_assert(rec->entry[i].server.alias.slen == 0); + pj_assert(rec->entry[i].port == PORT3+i); + + pj_assert(rec->entry[i].server.addr_count == PJ_DNS_MAX_IP_IN_A_REC); + + for (j=0; j<PJ_DNS_MAX_IP_IN_A_REC; ++j) { + pj_assert(rec->entry[i].server.addr[j].s_addr == IP_ADDR3+j); + } + } + + pj_sem_post(sem); +} + +static int srv_resolver_many_test(void) +{ + pj_status_t status; + pj_str_t domain = pj_str(DOMAIN3); + pj_str_t res_name = pj_str("_sip._udp."); + + /* Successful scenario */ + PJ_LOG(3,(THIS_FILE, " srv_resolve(): too many entries test")); + + g_server[0].action = ACTION_CB; + g_server[0].action_cb = &action3_1; + g_server[1].action = ACTION_CB; + g_server[1].action_cb = &action3_1; + + g_server[0].pkt_count = 0; + g_server[1].pkt_count = 0; + + status = pj_dns_srv_resolve(&domain, &res_name, 1, pool, resolver, PJ_TRUE, + NULL, &srv_cb_3, NULL); + pj_assert(status == PJ_SUCCESS); + + pj_sem_wait(sem); + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////// + + +int resolver_test(void) +{ + int rc; + + rc = init(); + if (rc != 0) + goto on_error; + + rc = a_parser_test(); + if (rc != 0) + goto on_error; + + rc = simple_test(); + if (rc != 0) + goto on_error; + + rc = dns_test(); + if (rc != 0) + goto on_error; + + srv_resolver_test(); + srv_resolver_fallback_test(); + srv_resolver_many_test(); + + destroy(); + return 0; + +on_error: + destroy(); + return rc; +} + + diff --git a/pjlib-util/src/pjlib-util-test/stun.c b/pjlib-util/src/pjlib-util-test/stun.c new file mode 100644 index 0000000..dbd2d17 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/stun.c @@ -0,0 +1,119 @@ +/* $Id: stun.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * 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 + */ + +static int decode_test(void) +{ + /* Invalid message type */ + + /* Short message */ + + /* Long, random message */ + + /* Message length in header is shorter */ + + /* Message length in header is longer */ + + /* Invalid magic */ + + /* Attribute length is not valid */ + + /* Unknown mandatory attribute type should generate error */ + + /* Unknown but non-mandatory should be okay */ + + /* String/binary attribute length is larger than the message */ + + /* Valid message with MESSAGE-INTEGRITY */ + + /* Valid message with FINGERPRINT */ + + /* Valid message with MESSAGE-INTEGRITY and FINGERPRINT */ + + /* Another attribute not FINGERPRINT exists after MESSAGE-INTEGRITY */ + + /* Another attribute exists after FINGERPRINT */ + + return 0; +} + +static int decode_verify(void) +{ + /* Decode all attribute types */ + return 0; +} + +static int auth_test(void) +{ + /* REALM and USERNAME is present, but MESSAGE-INTEGRITY is not present. + * For short term, must with reply 401 without REALM. + * For long term, must reply with 401 with REALM. + */ + + /* USERNAME is not present, server must respond with 432 (Missing + * Username). + */ + + /* If long term credential is wanted and REALM is not present, server + * must respond with 434 (Missing Realm) + */ + + /* If REALM doesn't match, server must respond with 434 (Missing Realm) + * too, containing REALM and NONCE attribute. + */ + + /* When long term authentication is wanted and NONCE is NOT present, + * server must respond with 435 (Missing Nonce), containing REALM and + * NONCE attribute. + */ + + /* Simulate 438 (Stale Nonce) */ + + /* Simulate 436 (Unknown Username) */ + + /* When server wants to use short term credential, but request has + * REALM, reject with .... ??? + */ + + /* Invalid HMAC */ + + /* Valid static short term, without NONCE */ + + /* Valid static short term, WITH NONCE */ + + /* Valid static long term (with NONCE */ + + /* Valid dynamic short term (without NONCE) */ + + /* Valid dynamic short term (with NONCE) */ + + /* Valid dynamic long term (with NONCE) */ + + return 0; +} + + +int stun_test(void) +{ + decode_verify(); + decode_test(); + auth_test(); + return 0; +} + diff --git a/pjlib-util/src/pjlib-util-test/test.c b/pjlib-util/src/pjlib-util-test/test.c new file mode 100644 index 0000000..feb2c01 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/test.c @@ -0,0 +1,112 @@ +/* $Id$ */ +/* + * 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.h> +#include <pjlib-util.h> + +void app_perror(const char *msg, pj_status_t rc) +{ + char errbuf[256]; + + PJ_CHECK_STACK(); + + pj_strerror(rc, errbuf, sizeof(errbuf)); + PJ_LOG(1,("test", "%s: [pj_status_t=%d] %s", msg, rc, errbuf)); +} + +#define DO_TEST(test) do { \ + PJ_LOG(3, ("test", "Running %s...", #test)); \ + rc = test; \ + PJ_LOG(3, ("test", \ + "%s(%d)", \ + (char*)(rc ? "..ERROR" : "..success"), rc)); \ + if (rc!=0) goto on_return; \ + } while (0) + + +pj_pool_factory *mem; + +int param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC; + +static int test_inner(void) +{ + pj_caching_pool caching_pool; + int rc = 0; + + mem = &caching_pool.factory; + + pj_log_set_level(3); + pj_log_set_decor(param_log_decor); + + rc = pj_init(); + if (rc != 0) { + app_perror("pj_init() error!!", rc); + return rc; + } + + rc = pjlib_util_init(); + pj_assert(rc == 0); + + pj_dump_config(); + pj_caching_pool_init( &caching_pool, &pj_pool_factory_default_policy, 0 ); + +#if INCLUDE_XML_TEST + DO_TEST(xml_test()); +#endif + +#if INCLUDE_ENCRYPTION_TEST + DO_TEST(encryption_test()); + DO_TEST(encryption_benchmark()); +#endif + +#if INCLUDE_STUN_TEST + DO_TEST(stun_test()); +#endif + +#if INCLUDE_RESOLVER_TEST + DO_TEST(resolver_test()); +#endif + +#if INCLUDE_HTTP_CLIENT_TEST + DO_TEST(http_client_test()); +#endif + +on_return: + return rc; +} + +int test_main(void) +{ + PJ_USE_EXCEPTION; + + PJ_TRY { + return test_inner(); + } + PJ_CATCH_ANY { + int id = PJ_GET_EXCEPTION(); + PJ_LOG(3,("test", "FATAL: unhandled exception id %d (%s)", + id, pj_exception_id_name(id))); + } + PJ_END; + + return -1; +} + diff --git a/pjlib-util/src/pjlib-util-test/test.h b/pjlib-util/src/pjlib-util-test/test.h new file mode 100644 index 0000000..37b1d90 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/test.h @@ -0,0 +1,38 @@ +/* $Id$ */ +/* + * 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 <pj/types.h> + +#define INCLUDE_XML_TEST 1 +#define INCLUDE_ENCRYPTION_TEST 1 +#define INCLUDE_STUN_TEST 1 +#define INCLUDE_RESOLVER_TEST 1 +#define INCLUDE_HTTP_CLIENT_TEST 1 + +extern int xml_test(void); +extern int encryption_test(); +extern int encryption_benchmark(); +extern int stun_test(); +extern int test_main(void); +extern int resolver_test(void); +extern int http_client_test(); + +extern void app_perror(const char *title, pj_status_t rc); +extern pj_pool_factory *mem; + diff --git a/pjlib-util/src/pjlib-util-test/xml.c b/pjlib-util/src/pjlib-util-test/xml.c new file mode 100644 index 0000000..19733a1 --- /dev/null +++ b/pjlib-util/src/pjlib-util-test/xml.c @@ -0,0 +1,145 @@ +/* $Id: xml.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * 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" + + +#if INCLUDE_XML_TEST + +#include <pjlib-util/xml.h> +#include <pjlib.h> + +#define THIS_FILE "xml_test" + +static const char *xml_doc[] = +{ +" <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +" <p:pidf-full xmlns=\"urn:ietf:params:xml:ns:pidf\"\n" +" xmlns:p=\"urn:ietf:params:xml:ns:pidf-diff\"\n" +" xmlns:r=\"urn:ietf:params:xml:ns:pidf:rpid\"\n" +" xmlns:c=\"urn:ietf:params:xml:ns:pidf:caps\"\n" +" entity=\"pres:someone@example.com\"\n" +" version=\"567\">\n" +"\n" +" <tuple id=\"sg89ae\">\n" +" <status>\n" +" <basic>open</basic>\n" +" <r:relationship>assistant</r:relationship>\n" +" </status>\n" +" <c:servcaps>\n" +" <c:audio>true</c:audio>\n" +" <c:video>false</c:video>\n" +" <c:message>true</c:message>\n" +" </c:servcaps>\n" +" <contact priority=\"0.8\">tel:09012345678</contact>\n" +" </tuple>\n" +"\n" +" <tuple id=\"cg231jcr\">\n" +" <status>\n" +" <basic>open</basic>\n" +" </status>\n" +" <contact priority=\"1.0\">im:pep@example.com</contact>\n" +" </tuple>\n" +"\n" +" <tuple id=\"r1230d\">\n" +" <status>\n" +" <basic>closed</basic>\n" +" <r:activity>meeting</r:activity>\n" +" </status>\n" +" <r:homepage>http://example.com/~pep/</r:homepage>\n" +" <r:icon>http://example.com/~pep/icon.gif</r:icon>\n" +" <r:card>http://example.com/~pep/card.vcd</r:card>\n" +" <contact priority=\"0.9\">sip:pep@example.com</contact>\n" +" </tuple>\n" +"\n" +" <note xml:lang=\"en\">Full state presence document</note>\n" +"\n" +" <r:person>\n" +" <r:status>\n" +" <r:activities>\n" +" <r:on-the-phone/>\n" +" <r:busy/>\n" +" </r:activities>\n" +" </r:status>\n" +" </r:person>\n" +"\n" +" <r:device id=\"urn:esn:600b40c7\">\n" +" <r:status>\n" +" <c:devcaps>\n" +" <c:mobility>\n" +" <c:supported>\n" +" <c:mobile/>\n" +" </c:supported>\n" +" </c:mobility>\n" +" </c:devcaps>\n" +" </r:status>\n" +" </r:device>\n" +"\n" +" </p:pidf-full>\n" +} +; + +static int xml_parse_print_test(const char *doc) +{ + pj_str_t msg; + pj_pool_t *pool; + pj_xml_node *root; + char *output; + int output_len; + + pool = pj_pool_create(mem, "xml", 4096, 1024, NULL); + pj_strdup2(pool, &msg, doc); + root = pj_xml_parse(pool, msg.ptr, msg.slen); + if (!root) { + PJ_LOG(1, (THIS_FILE, " Error: unable to parse XML")); + return -10; + } + + output = (char*)pj_pool_zalloc(pool, msg.slen + 512); + output_len = pj_xml_print(root, output, msg.slen+512, PJ_TRUE); + if (output_len < 1) { + PJ_LOG(1, (THIS_FILE, " Error: buffer too small to print XML file")); + return -20; + } + output[output_len] = '\0'; + + + pj_pool_release(pool); + return 0; +} + +int xml_test() +{ + unsigned i; + for (i=0; i<sizeof(xml_doc)/sizeof(xml_doc[0]); ++i) { + int status; + if ((status=xml_parse_print_test(xml_doc[i])) != 0) + return status; + } + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_xml_test; +#endif /* INCLUDE_XML_TEST */ + + |