diff options
Diffstat (limited to 'pjlib/src/pjlib-test')
43 files changed, 12127 insertions, 0 deletions
diff --git a/pjlib/src/pjlib-test/activesock.c b/pjlib/src/pjlib-test/activesock.c new file mode 100644 index 0000000..399cc29 --- /dev/null +++ b/pjlib/src/pjlib-test/activesock.c @@ -0,0 +1,521 @@ +/* $Id: activesock.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.h> + +/** + * \page page_pjlib_activesock_test Test: Active Socket + * + * This file is <b>pjlib-test/activesock.c</b> + * + * \include pjlib-test/activesock.c + */ + +#if INCLUDE_ACTIVESOCK_TEST + +#define THIS_FILE "activesock.c" + + +/******************************************************************* + * Simple UDP echo server. + */ +struct udp_echo_srv +{ + pj_activesock_t *asock; + pj_bool_t echo_enabled; + pj_uint16_t port; + pj_ioqueue_op_key_t send_key; + pj_status_t status; + unsigned rx_cnt; + unsigned rx_err_cnt, tx_err_cnt; +}; + +static void udp_echo_err(const char *title, pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(3,(THIS_FILE, " error: %s: %s", title, errmsg)); +} + +static pj_bool_t udp_echo_srv_on_data_recvfrom(pj_activesock_t *asock, + void *data, + pj_size_t size, + const pj_sockaddr_t *src_addr, + int addr_len, + pj_status_t status) +{ + struct udp_echo_srv *srv; + pj_ssize_t sent; + + + srv = (struct udp_echo_srv*) pj_activesock_get_user_data(asock); + + if (status != PJ_SUCCESS) { + srv->status = status; + srv->rx_err_cnt++; + udp_echo_err("recvfrom() callback", status); + return PJ_TRUE; + } + + srv->rx_cnt++; + + /* Send back if echo is enabled */ + if (srv->echo_enabled) { + sent = size; + srv->status = pj_activesock_sendto(asock, &srv->send_key, data, + &sent, 0, + src_addr, addr_len); + if (srv->status != PJ_SUCCESS) { + srv->tx_err_cnt++; + udp_echo_err("sendto()", status); + } + } + + return PJ_TRUE; +} + + +static pj_status_t udp_echo_srv_create(pj_pool_t *pool, + pj_ioqueue_t *ioqueue, + pj_bool_t enable_echo, + struct udp_echo_srv **p_srv) +{ + struct udp_echo_srv *srv; + pj_sock_t sock_fd = PJ_INVALID_SOCKET; + pj_sockaddr addr; + int addr_len; + pj_activesock_cb activesock_cb; + pj_status_t status; + + srv = PJ_POOL_ZALLOC_T(pool, struct udp_echo_srv); + srv->echo_enabled = enable_echo; + + pj_sockaddr_in_init(&addr.ipv4, NULL, 0); + addr_len = sizeof(addr); + + pj_bzero(&activesock_cb, sizeof(activesock_cb)); + activesock_cb.on_data_recvfrom = &udp_echo_srv_on_data_recvfrom; + + status = pj_activesock_create_udp(pool, &addr, NULL, ioqueue, &activesock_cb, + srv, &srv->asock, &addr); + if (status != PJ_SUCCESS) { + pj_sock_close(sock_fd); + udp_echo_err("pj_activesock_create()", status); + return status; + } + + srv->port = pj_ntohs(addr.ipv4.sin_port); + + pj_ioqueue_op_key_init(&srv->send_key, sizeof(srv->send_key)); + + status = pj_activesock_start_recvfrom(srv->asock, pool, 32, 0); + if (status != PJ_SUCCESS) { + pj_activesock_close(srv->asock); + udp_echo_err("pj_activesock_start_recvfrom()", status); + return status; + } + + + *p_srv = srv; + return PJ_SUCCESS; +} + +static void udp_echo_srv_destroy(struct udp_echo_srv *srv) +{ + pj_activesock_close(srv->asock); +} + +/******************************************************************* + * UDP ping pong test (send packet back and forth between two UDP echo + * servers. + */ +static int udp_ping_pong_test(void) +{ + pj_ioqueue_t *ioqueue = NULL; + pj_pool_t *pool = NULL; + struct udp_echo_srv *srv1=NULL, *srv2=NULL; + pj_bool_t need_send = PJ_TRUE; + unsigned data = 0; + int count, ret; + pj_status_t status; + + pool = pj_pool_create(mem, "pingpong", 512, 512, NULL); + if (!pool) + return -10; + + status = pj_ioqueue_create(pool, 4, &ioqueue); + if (status != PJ_SUCCESS) { + ret = -20; + udp_echo_err("pj_ioqueue_create()", status); + goto on_return; + } + + status = udp_echo_srv_create(pool, ioqueue, PJ_TRUE, &srv1); + if (status != PJ_SUCCESS) { + ret = -30; + goto on_return; + } + + status = udp_echo_srv_create(pool, ioqueue, PJ_TRUE, &srv2); + if (status != PJ_SUCCESS) { + ret = -40; + goto on_return; + } + + /* initiate the first send */ + for (count=0; count<1000; ++count) { + unsigned last_rx1, last_rx2; + unsigned i; + + if (need_send) { + pj_str_t loopback; + pj_sockaddr_in addr; + pj_ssize_t sent; + + ++data; + + sent = sizeof(data); + loopback = pj_str("127.0.0.1"); + pj_sockaddr_in_init(&addr, &loopback, srv2->port); + status = pj_activesock_sendto(srv1->asock, &srv1->send_key, + &data, &sent, 0, + &addr, sizeof(addr)); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + ret = -50; + udp_echo_err("sendto()", status); + goto on_return; + } + + need_send = PJ_FALSE; + } + + last_rx1 = srv1->rx_cnt; + last_rx2 = srv2->rx_cnt; + + for (i=0; i<10 && last_rx1 == srv1->rx_cnt && last_rx2 == srv2->rx_cnt; ++i) { + pj_time_val delay = {0, 10}; +#ifdef PJ_SYMBIAN + pj_symbianos_poll(-1, 100); +#else + pj_ioqueue_poll(ioqueue, &delay); +#endif + } + + if (srv1->rx_err_cnt+srv1->tx_err_cnt != 0 || + srv2->rx_err_cnt+srv2->tx_err_cnt != 0) + { + /* Got error */ + ret = -60; + goto on_return; + } + + if (last_rx1 == srv1->rx_cnt && last_rx2 == srv2->rx_cnt) { + /* Packet lost */ + ret = -70; + udp_echo_err("packets have been lost", PJ_ETIMEDOUT); + goto on_return; + } + } + + ret = 0; + +on_return: + if (srv2) + udp_echo_srv_destroy(srv2); + if (srv1) + udp_echo_srv_destroy(srv1); + if (ioqueue) + pj_ioqueue_destroy(ioqueue); + if (pool) + pj_pool_release(pool); + + return ret; +} + + + +#define SIGNATURE 0xdeadbeef +struct tcp_pkt +{ + pj_uint32_t signature; + pj_uint32_t seq; + char fill[513]; +}; + +struct tcp_state +{ + pj_bool_t err; + pj_bool_t sent; + pj_uint32_t next_recv_seq; + pj_uint8_t pkt[600]; +}; + +struct send_key +{ + pj_ioqueue_op_key_t op_key; +}; + + +static pj_bool_t tcp_on_data_read(pj_activesock_t *asock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder) +{ + struct tcp_state *st = (struct tcp_state*) pj_activesock_get_user_data(asock); + char *next = (char*) data; + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + PJ_LOG(1,("", " err: status=%d", status)); + st->err = PJ_TRUE; + return PJ_FALSE; + } + + while (size >= sizeof(struct tcp_pkt)) { + struct tcp_pkt *tcp_pkt = (struct tcp_pkt*) next; + + if (tcp_pkt->signature != SIGNATURE) { + PJ_LOG(1,("", " err: invalid signature at seq=%d", + st->next_recv_seq)); + st->err = PJ_TRUE; + return PJ_FALSE; + } + if (tcp_pkt->seq != st->next_recv_seq) { + PJ_LOG(1,("", " err: wrong sequence")); + st->err = PJ_TRUE; + return PJ_FALSE; + } + + st->next_recv_seq++; + next += sizeof(struct tcp_pkt); + size -= sizeof(struct tcp_pkt); + } + + if (size) { + pj_memmove(data, next, size); + *remainder = size; + } + + return PJ_TRUE; +} + +static pj_bool_t tcp_on_data_sent(pj_activesock_t *asock, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t sent) +{ + struct tcp_state *st=(struct tcp_state*)pj_activesock_get_user_data(asock); + + PJ_UNUSED_ARG(op_key); + + st->sent = 1; + + if (sent < 1) { + st->err = PJ_TRUE; + return PJ_FALSE; + } + + return PJ_TRUE; +} + +static int tcp_perf_test(void) +{ + enum { COUNT=10000 }; + pj_pool_t *pool = NULL; + pj_ioqueue_t *ioqueue = NULL; + pj_sock_t sock1=PJ_INVALID_SOCKET, sock2=PJ_INVALID_SOCKET; + pj_activesock_t *asock1 = NULL, *asock2 = NULL; + pj_activesock_cb cb; + struct tcp_state *state1, *state2; + unsigned i; + pj_status_t status; + + pool = pj_pool_create(mem, "tcpperf", 256, 256, NULL); + + status = app_socketpair(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock1, + &sock2); + if (status != PJ_SUCCESS) { + status = -100; + goto on_return; + } + + status = pj_ioqueue_create(pool, 4, &ioqueue); + if (status != PJ_SUCCESS) { + status = -110; + goto on_return; + } + + pj_bzero(&cb, sizeof(cb)); + cb.on_data_read = &tcp_on_data_read; + cb.on_data_sent = &tcp_on_data_sent; + + state1 = PJ_POOL_ZALLOC_T(pool, struct tcp_state); + status = pj_activesock_create(pool, sock1, pj_SOCK_STREAM(), NULL, ioqueue, + &cb, state1, &asock1); + if (status != PJ_SUCCESS) { + status = -120; + goto on_return; + } + + state2 = PJ_POOL_ZALLOC_T(pool, struct tcp_state); + status = pj_activesock_create(pool, sock2, pj_SOCK_STREAM(), NULL, ioqueue, + &cb, state2, &asock2); + if (status != PJ_SUCCESS) { + status = -130; + goto on_return; + } + + status = pj_activesock_start_read(asock1, pool, 1000, 0); + if (status != PJ_SUCCESS) { + status = -140; + goto on_return; + } + + /* Send packet as quickly as possible */ + for (i=0; i<COUNT && !state1->err && !state2->err; ++i) { + struct tcp_pkt *pkt; + struct send_key send_key[2], *op_key; + pj_ssize_t len; + + pkt = (struct tcp_pkt*)state2->pkt; + pkt->signature = SIGNATURE; + pkt->seq = i; + pj_memset(pkt->fill, 'a', sizeof(pkt->fill)); + + op_key = &send_key[i%2]; + pj_ioqueue_op_key_init(&op_key->op_key, sizeof(*op_key)); + + state2->sent = PJ_FALSE; + len = sizeof(*pkt); + status = pj_activesock_send(asock2, &op_key->op_key, pkt, &len, 0); + if (status == PJ_EPENDING) { + do { +#if PJ_SYMBIAN + pj_symbianos_poll(-1, -1); +#else + pj_ioqueue_poll(ioqueue, NULL); +#endif + } while (!state2->sent); + } else { +#if PJ_SYMBIAN + /* The Symbian socket always returns PJ_SUCCESS for TCP send, + * eventhough the remote end hasn't received the data yet. + * If we continue sending, eventually send() will block, + * possibly because the send buffer is full. So we need to + * poll the ioqueue periodically, to let receiver gets the + * data. + */ + pj_symbianos_poll(-1, 0); +#endif + if (status != PJ_SUCCESS) { + PJ_LOG(1,("", " err: send status=%d", status)); + status = -180; + break; + } else if (status == PJ_SUCCESS) { + if (len != sizeof(*pkt)) { + PJ_LOG(1,("", " err: shouldn't report partial sent")); + status = -190; + break; + } + } + } + +#ifndef PJ_SYMBIAN + for (;;) { + pj_time_val timeout = {0, 10}; + if (pj_ioqueue_poll(ioqueue, &timeout) < 1) + break; + } +#endif + + } + + /* Wait until everything has been sent/received */ + if (state1->next_recv_seq < COUNT) { +#ifdef PJ_SYMBIAN + while (pj_symbianos_poll(-1, 1000) == PJ_TRUE) + ; +#else + pj_time_val delay = {0, 100}; + while (pj_ioqueue_poll(ioqueue, &delay) > 0) + ; +#endif + } + + if (status == PJ_EPENDING) + status = PJ_SUCCESS; + + if (status != 0) + goto on_return; + + if (state1->err) { + status = -183; + goto on_return; + } + if (state2->err) { + status = -186; + goto on_return; + } + if (state1->next_recv_seq != COUNT) { + PJ_LOG(3,("", " err: only %u packets received, expecting %u", + state1->next_recv_seq, COUNT)); + status = -195; + goto on_return; + } + +on_return: + if (asock2) + pj_activesock_close(asock2); + if (asock1) + pj_activesock_close(asock1); + if (ioqueue) + pj_ioqueue_destroy(ioqueue); + if (pool) + pj_pool_release(pool); + + return status; +} + + + +int activesock_test(void) +{ + int ret; + + PJ_LOG(3,("", "..udp ping/pong test")); + ret = udp_ping_pong_test(); + if (ret != 0) + return ret; + + PJ_LOG(3,("", "..tcp perf test")); + ret = tcp_perf_test(); + if (ret != 0) + return ret; + + return 0; +} + +#else /* INCLUDE_ACTIVESOCK_TEST */ +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_active_sock_test; +#endif /* INCLUDE_ACTIVESOCK_TEST */ + diff --git a/pjlib/src/pjlib-test/atomic.c b/pjlib/src/pjlib-test/atomic.c new file mode 100644 index 0000000..f9cddf4 --- /dev/null +++ b/pjlib/src/pjlib-test/atomic.c @@ -0,0 +1,109 @@ +/* $Id: atomic.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.h> + +/** + * \page page_pjlib_atomic_test Test: Atomic Variable + * + * This file provides implementation of \b atomic_test(). It tests the + * functionality of the atomic variable API. + * + * \section atomic_test_sec Scope of the Test + * + * API tested: + * - pj_atomic_create() + * - pj_atomic_get() + * - pj_atomic_inc() + * - pj_atomic_dec() + * - pj_atomic_set() + * - pj_atomic_destroy() + * + * + * This file is <b>pjlib-test/atomic.c</b> + * + * \include pjlib-test/atomic.c + */ + + +#if INCLUDE_ATOMIC_TEST + +int atomic_test(void) +{ + pj_pool_t *pool; + pj_atomic_t *atomic_var; + pj_status_t rc; + + pool = pj_pool_create(mem, NULL, 4096, 0, NULL); + if (!pool) + return -10; + + /* create() */ + rc = pj_atomic_create(pool, 111, &atomic_var); + if (rc != 0) { + return -20; + } + + /* get: check the value. */ + if (pj_atomic_get(atomic_var) != 111) + return -30; + + /* increment. */ + pj_atomic_inc(atomic_var); + if (pj_atomic_get(atomic_var) != 112) + return -40; + + /* decrement. */ + pj_atomic_dec(atomic_var); + if (pj_atomic_get(atomic_var) != 111) + return -50; + + /* set */ + pj_atomic_set(atomic_var, 211); + if (pj_atomic_get(atomic_var) != 211) + return -60; + + /* add */ + pj_atomic_add(atomic_var, 10); + if (pj_atomic_get(atomic_var) != 221) + return -60; + + /* check the value again. */ + if (pj_atomic_get(atomic_var) != 221) + return -70; + + /* destroy */ + rc = pj_atomic_destroy(atomic_var); + if (rc != 0) + return -80; + + pj_pool_release(pool); + + return 0; +} + + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_atomic_test; +#endif /* INCLUDE_ATOMIC_TEST */ + diff --git a/pjlib/src/pjlib-test/echo_clt.c b/pjlib/src/pjlib-test/echo_clt.c new file mode 100644 index 0000000..0ce27dc --- /dev/null +++ b/pjlib/src/pjlib-test/echo_clt.c @@ -0,0 +1,269 @@ +/* $Id: echo_clt.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.h> + +#if INCLUDE_ECHO_CLIENT + +enum { BUF_SIZE = 512 }; + +struct client +{ + int sock_type; + const char *server; + int port; +}; + +static pj_atomic_t *totalBytes; +static pj_atomic_t *timeout_counter; +static pj_atomic_t *invalid_counter; + +#define MSEC_PRINT_DURATION 1000 + +static int wait_socket(pj_sock_t sock, unsigned msec_timeout) +{ + pj_fd_set_t fdset; + pj_time_val timeout; + + timeout.sec = 0; + timeout.msec = msec_timeout; + pj_time_val_normalize(&timeout); + + PJ_FD_ZERO(&fdset); + PJ_FD_SET(sock, &fdset); + + return pj_sock_select(FD_SETSIZE, &fdset, NULL, NULL, &timeout); +} + +static int echo_client_thread(void *arg) +{ + pj_sock_t sock; + char send_buf[BUF_SIZE]; + char recv_buf[BUF_SIZE]; + pj_sockaddr_in addr; + pj_str_t s; + pj_status_t rc; + pj_uint32_t buffer_id; + pj_uint32_t buffer_counter; + struct client *client = arg; + pj_status_t last_recv_err = PJ_SUCCESS, last_send_err = PJ_SUCCESS; + unsigned counter = 0; + + rc = app_socket(pj_AF_INET(), client->sock_type, 0, -1, &sock); + if (rc != PJ_SUCCESS) { + app_perror("...unable to create socket", rc); + return -10; + } + + rc = pj_sockaddr_in_init( &addr, pj_cstr(&s, client->server), + (pj_uint16_t)client->port); + if (rc != PJ_SUCCESS) { + app_perror("...unable to resolve server", rc); + return -15; + } + + rc = pj_sock_connect(sock, &addr, sizeof(addr)); + if (rc != PJ_SUCCESS) { + app_perror("...connect() error", rc); + pj_sock_close(sock); + return -20; + } + + PJ_LOG(3,("", "...socket connected to %s:%d", + pj_inet_ntoa(addr.sin_addr), + pj_ntohs(addr.sin_port))); + + pj_memset(send_buf, 'A', BUF_SIZE); + send_buf[BUF_SIZE-1]='\0'; + + /* Give other thread chance to initialize themselves! */ + pj_thread_sleep(200); + + //PJ_LOG(3,("", "...thread %p running", pj_thread_this())); + + buffer_id = (pj_uint32_t) pj_thread_this(); + buffer_counter = 0; + + *(pj_uint32_t*)send_buf = buffer_id; + + for (;;) { + int rc; + pj_ssize_t bytes; + + ++counter; + + //while (wait_socket(sock,0) > 0) + // ; + + /* Send a packet. */ + bytes = BUF_SIZE; + *(pj_uint32_t*)(send_buf+4) = ++buffer_counter; + rc = pj_sock_send(sock, send_buf, &bytes, 0); + if (rc != PJ_SUCCESS || bytes != BUF_SIZE) { + if (rc != last_send_err) { + app_perror("...send() error", rc); + PJ_LOG(3,("", "...ignoring subsequent error..")); + last_send_err = rc; + pj_thread_sleep(100); + } + continue; + } + + rc = wait_socket(sock, 500); + if (rc == 0) { + PJ_LOG(3,("", "...timeout")); + bytes = 0; + pj_atomic_inc(timeout_counter); + } else if (rc < 0) { + rc = pj_get_netos_error(); + app_perror("...select() error", rc); + break; + } else { + /* Receive back the original packet. */ + bytes = 0; + do { + pj_ssize_t received = BUF_SIZE - bytes; + rc = pj_sock_recv(sock, recv_buf+bytes, &received, 0); + if (rc != PJ_SUCCESS || received == 0) { + if (rc != last_recv_err) { + app_perror("...recv() error", rc); + PJ_LOG(3,("", "...ignoring subsequent error..")); + last_recv_err = rc; + pj_thread_sleep(100); + } + bytes = 0; + received = 0; + break; + } + bytes += received; + } while (bytes != BUF_SIZE && bytes != 0); + } + + if (bytes == 0) + continue; + + if (pj_memcmp(send_buf, recv_buf, BUF_SIZE) != 0) { + recv_buf[BUF_SIZE-1] = '\0'; + PJ_LOG(3,("", "...error: buffer %u has changed!\n" + "send_buf=%s\n" + "recv_buf=%s\n", + counter, send_buf, recv_buf)); + pj_atomic_inc(invalid_counter); + } + + /* Accumulate total received. */ + pj_atomic_add(totalBytes, bytes); + } + + pj_sock_close(sock); + return 0; +} + +int echo_client(int sock_type, const char *server, int port) +{ + pj_pool_t *pool; + pj_thread_t *thread[ECHO_CLIENT_MAX_THREADS]; + pj_status_t rc; + struct client client; + int i; + pj_atomic_value_t last_received; + pj_timestamp last_report; + + client.sock_type = sock_type; + client.server = server; + client.port = port; + + pool = pj_pool_create( mem, NULL, 4000, 4000, NULL ); + + rc = pj_atomic_create(pool, 0, &totalBytes); + if (rc != PJ_SUCCESS) { + PJ_LOG(3,("", "...error: unable to create atomic variable", rc)); + return -30; + } + rc = pj_atomic_create(pool, 0, &invalid_counter); + rc = pj_atomic_create(pool, 0, &timeout_counter); + + PJ_LOG(3,("", "Echo client started")); + PJ_LOG(3,("", " Destination: %s:%d", + ECHO_SERVER_ADDRESS, ECHO_SERVER_START_PORT)); + PJ_LOG(3,("", " Press Ctrl-C to exit")); + + for (i=0; i<ECHO_CLIENT_MAX_THREADS; ++i) { + rc = pj_thread_create( pool, NULL, &echo_client_thread, &client, + PJ_THREAD_DEFAULT_STACK_SIZE, 0, + &thread[i]); + if (rc != PJ_SUCCESS) { + app_perror("...error: unable to create thread", rc); + return -10; + } + } + + last_received = 0; + pj_get_timestamp(&last_report); + + for (;;) { + pj_timestamp now; + unsigned long received, cur_received; + unsigned msec; + pj_highprec_t bw; + pj_time_val elapsed; + unsigned bw32; + pj_uint32_t timeout, invalid; + + pj_thread_sleep(1000); + + pj_get_timestamp(&now); + elapsed = pj_elapsed_time(&last_report, &now); + msec = PJ_TIME_VAL_MSEC(elapsed); + + received = pj_atomic_get(totalBytes); + cur_received = received - last_received; + + bw = cur_received; + pj_highprec_mul(bw, 1000); + pj_highprec_div(bw, msec); + + bw32 = (unsigned)bw; + + last_report = now; + last_received = received; + + timeout = pj_atomic_get(timeout_counter); + invalid = pj_atomic_get(invalid_counter); + + PJ_LOG(3,("", + "...%d threads, total bandwidth: %d KB/s, " + "timeout=%d, invalid=%d", + ECHO_CLIENT_MAX_THREADS, bw32/1000, + timeout, invalid)); + } + + for (i=0; i<ECHO_CLIENT_MAX_THREADS; ++i) { + pj_thread_join( thread[i] ); + } + + pj_pool_release(pool); + return 0; +} + + +#else +int dummy_echo_client; +#endif /* INCLUDE_ECHO_CLIENT */ diff --git a/pjlib/src/pjlib-test/errno.c b/pjlib/src/pjlib-test/errno.c new file mode 100644 index 0000000..c06ad5b --- /dev/null +++ b/pjlib/src/pjlib-test/errno.c @@ -0,0 +1,171 @@ +/* $Id: errno.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 <pj/errno.h> +#include <pj/log.h> +#include <pj/ctype.h> +#include <pj/compat/socket.h> +#include <pj/string.h> + +#if INCLUDE_ERRNO_TEST + +#define THIS_FILE "errno" + +#if defined(PJ_WIN32) && PJ_WIN32 != 0 +# include <windows.h> +#endif + +#if defined(PJ_HAS_ERRNO_H) && PJ_HAS_ERRNO_H != 0 +# include <errno.h> +#endif + +static void trim_newlines(char *s) +{ + while (*s) { + if (*s == '\r' || *s == '\n') + *s = ' '; + ++s; + } +} + +int my_strncasecmp(const char *s1, const char *s2, int max_len) +{ + while (*s1 && *s2 && max_len > 0) { + if (pj_tolower(*s1) != pj_tolower(*s2)) + return -1; + ++s1; + ++s2; + --max_len; + } + return 0; +} + +const char *my_stristr(const char *whole, const char *part) +{ + int part_len = strlen(part); + while (*whole) { + if (my_strncasecmp(whole, part, part_len) == 0) + return whole; + ++whole; + } + return NULL; +} + +int errno_test(void) +{ + enum { CUT = 6 }; + pj_status_t rc = 0; + char errbuf[256]; + + PJ_LOG(3,(THIS_FILE, "...errno test: check the msg carefully")); + + PJ_UNUSED_ARG(rc); + + /* + * Windows platform error. + */ +# ifdef ERROR_INVALID_DATA + rc = PJ_STATUS_FROM_OS(ERROR_INVALID_DATA); + pj_set_os_error(rc); + + /* Whole */ + pj_strerror(rc, errbuf, sizeof(errbuf)); + trim_newlines(errbuf); + PJ_LOG(3,(THIS_FILE, "...msg for rc=ERROR_INVALID_DATA: '%s'", errbuf)); + if (my_stristr(errbuf, "invalid") == NULL) { + PJ_LOG(3, (THIS_FILE, + "...error: expecting \"invalid\" string in the msg")); +#ifndef PJ_WIN32_WINCE + return -20; +#endif + } + + /* Cut version. */ + pj_strerror(rc, errbuf, CUT); + PJ_LOG(3,(THIS_FILE, "...msg for rc=ERROR_INVALID_DATA (cut): '%s'", errbuf)); +# endif + + /* + * Unix errors + */ +# if defined(EINVAL) && !defined(PJ_SYMBIAN) + rc = PJ_STATUS_FROM_OS(EINVAL); + pj_set_os_error(rc); + + /* Whole */ + pj_strerror(rc, errbuf, sizeof(errbuf)); + trim_newlines(errbuf); + PJ_LOG(3,(THIS_FILE, "...msg for rc=EINVAL: '%s'", errbuf)); + if (my_stristr(errbuf, "invalid") == NULL) { + PJ_LOG(3, (THIS_FILE, + "...error: expecting \"invalid\" string in the msg")); + return -30; + } + + /* Cut */ + pj_strerror(rc, errbuf, CUT); + PJ_LOG(3,(THIS_FILE, "...msg for rc=EINVAL (cut): '%s'", errbuf)); +# endif + + /* + * Windows WSA errors + */ +# ifdef WSAEINVAL + rc = PJ_STATUS_FROM_OS(WSAEINVAL); + pj_set_os_error(rc); + + /* Whole */ + pj_strerror(rc, errbuf, sizeof(errbuf)); + trim_newlines(errbuf); + PJ_LOG(3,(THIS_FILE, "...msg for rc=WSAEINVAL: '%s'", errbuf)); + if (my_stristr(errbuf, "invalid") == NULL) { + PJ_LOG(3, (THIS_FILE, + "...error: expecting \"invalid\" string in the msg")); + return -40; + } + + /* Cut */ + pj_strerror(rc, errbuf, CUT); + PJ_LOG(3,(THIS_FILE, "...msg for rc=WSAEINVAL (cut): '%s'", errbuf)); +# endif + + pj_strerror(PJ_EBUG, errbuf, sizeof(errbuf)); + PJ_LOG(3,(THIS_FILE, "...msg for rc=PJ_EBUG: '%s'", errbuf)); + if (my_stristr(errbuf, "BUG") == NULL) { + PJ_LOG(3, (THIS_FILE, + "...error: expecting \"BUG\" string in the msg")); + return -20; + } + + pj_strerror(PJ_EBUG, errbuf, CUT); + PJ_LOG(3,(THIS_FILE, "...msg for rc=PJ_EBUG, cut at %d chars: '%s'", + CUT, errbuf)); + + /* Perror */ + pj_perror(3, THIS_FILE, PJ_SUCCESS, "...testing %s", "pj_perror"); + PJ_PERROR(3,(THIS_FILE, PJ_SUCCESS, "...testing %s", "PJ_PERROR")); + + return 0; +} + + +#endif /* INCLUDE_ERRNO_TEST */ + + diff --git a/pjlib/src/pjlib-test/exception.c b/pjlib/src/pjlib-test/exception.c new file mode 100644 index 0000000..4d4b1cd --- /dev/null +++ b/pjlib/src/pjlib-test/exception.c @@ -0,0 +1,282 @@ +/* $Id: exception.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" + + +/** + * \page page_pjlib_exception_test Test: Exception Handling + * + * This file provides implementation of \b exception_test(). It tests the + * functionality of the exception handling API. + * + * @note This test use static ID not acquired through proper registration. + * This is not recommended, since it may create ID collissions. + * + * \section exception_test_sec Scope of the Test + * + * Some scenarios tested: + * - no exception situation + * - basic TRY/CATCH + * - multiple exception handlers + * - default handlers + * + * + * This file is <b>pjlib-test/exception.c</b> + * + * \include pjlib-test/exception.c + */ + + +#if INCLUDE_EXCEPTION_TEST + +#include <pjlib.h> + +#define ID_1 1 +#define ID_2 2 + +static int throw_id_1(void) +{ + PJ_THROW( ID_1 ); + PJ_UNREACHED(return -1;) +} + +static int throw_id_2(void) +{ + PJ_THROW( ID_2 ); + PJ_UNREACHED(return -1;) +} + +static int try_catch_test(void) +{ + PJ_USE_EXCEPTION; + int rc = -200; + + PJ_TRY { + PJ_THROW(ID_1); + } + PJ_CATCH_ANY { + rc = 0; + } + PJ_END; + return rc; +} + +static int throw_in_handler(void) +{ + PJ_USE_EXCEPTION; + int rc = 0; + + PJ_TRY { + PJ_THROW(ID_1); + } + PJ_CATCH_ANY { + if (PJ_GET_EXCEPTION() != ID_1) + rc = -300; + else + PJ_THROW(ID_2); + } + PJ_END; + return rc; +} + +static int return_in_handler(void) +{ + PJ_USE_EXCEPTION; + + PJ_TRY { + PJ_THROW(ID_1); + } + PJ_CATCH_ANY { + return 0; + } + PJ_END; + return -400; +} + + +static int test(void) +{ + int rc = 0; + PJ_USE_EXCEPTION; + + /* + * No exception situation. + */ + PJ_TRY { + rc = rc; + } + PJ_CATCH_ANY { + rc = -3; + } + PJ_END; + + if (rc != 0) + return rc; + + + /* + * Basic TRY/CATCH + */ + PJ_TRY { + rc = throw_id_1(); + + // should not reach here. + rc = -10; + } + PJ_CATCH_ANY { + int id = PJ_GET_EXCEPTION(); + if (id != ID_1) { + PJ_LOG(3,("", "...error: got unexpected exception %d (%s)", + id, pj_exception_id_name(id))); + if (!rc) rc = -20; + } + } + PJ_END; + + if (rc != 0) + return rc; + + /* + * Multiple exceptions handlers + */ + PJ_TRY { + rc = throw_id_2(); + // should not reach here. + rc = -25; + } + PJ_CATCH_ANY { + switch (PJ_GET_EXCEPTION()) { + case ID_1: + if (!rc) rc = -30; break; + case ID_2: + if (!rc) rc = 0; break; + default: + if (!rc) rc = -40; + break; + } + } + PJ_END; + + if (rc != 0) + return rc; + + /* + * Test default handler. + */ + PJ_TRY { + rc = throw_id_1(); + // should not reach here + rc = -50; + } + PJ_CATCH_ANY { + switch (PJ_GET_EXCEPTION()) { + case ID_1: + if (!rc) rc = 0; + break; + default: + if (!rc) rc = -60; + break; + } + } + PJ_END; + + if (rc != 0) + return rc; + + /* + * Nested handlers + */ + PJ_TRY { + rc = try_catch_test(); + } + PJ_CATCH_ANY { + rc = -70; + } + PJ_END; + + if (rc != 0) + return rc; + + /* + * Throwing exception inside handler + */ + rc = -80; + PJ_TRY { + int rc2; + rc2 = throw_in_handler(); + if (rc2) + rc = rc2; + } + PJ_CATCH_ANY { + if (PJ_GET_EXCEPTION() == ID_2) { + rc = 0; + } else { + rc = -90; + } + } + PJ_END; + + if (rc != 0) + return rc; + + + /* + * Return from handler. Returning from the function inside a handler + * should be okay (though returning from the function inside the + * PJ_TRY block IS NOT OKAY!!). We want to test to see if handler + * is cleaned up properly, but not sure how to do this. + */ + PJ_TRY { + int rc2; + rc2 = return_in_handler(); + if (rc2) + rc = rc2; + } + PJ_CATCH_ANY { + rc = -100; + } + PJ_END; + + + return 0; +} + +int exception_test(void) +{ + int i, rc; + enum { LOOP = 10 }; + + for (i=0; i<LOOP; ++i) { + if ((rc=test()) != 0) { + PJ_LOG(3,("", "...failed at i=%d (rc=%d)", i, rc)); + return rc; + } + } + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_exception_test; +#endif /* INCLUDE_EXCEPTION_TEST */ + + diff --git a/pjlib/src/pjlib-test/exception_wrap.cpp b/pjlib/src/pjlib-test/exception_wrap.cpp new file mode 100644 index 0000000..9c5b31b --- /dev/null +++ b/pjlib/src/pjlib-test/exception_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: exception_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "exception.c" diff --git a/pjlib/src/pjlib-test/fifobuf.c b/pjlib/src/pjlib-test/fifobuf.c new file mode 100644 index 0000000..89c3ee2 --- /dev/null +++ b/pjlib/src/pjlib-test/fifobuf.c @@ -0,0 +1,117 @@ +/* $Id: fifobuf.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" + +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_fifobuf_test; + +#if INCLUDE_FIFOBUF_TEST + +#include <pjlib.h> + +int fifobuf_test() +{ + enum { SIZE = 1024, MAX_ENTRIES = 128, + MIN_SIZE = 4, MAX_SIZE = 64, + LOOP=10000 }; + pj_pool_t *pool; + pj_fifobuf_t fifo; + unsigned available = SIZE; + void *entries[MAX_ENTRIES]; + void *buffer; + int i; + + pool = pj_pool_create(mem, NULL, SIZE+256, 0, NULL); + if (!pool) + return -10; + + buffer = pj_pool_alloc(pool, SIZE); + if (!buffer) + return -20; + + pj_fifobuf_init (&fifo, buffer, SIZE); + + // Test 1 + for (i=0; i<LOOP*MAX_ENTRIES; ++i) { + int size; + int c, f; + c = i%2; + f = (i+1)%2; + do { + size = MIN_SIZE+(pj_rand() % MAX_SIZE); + entries[c] = pj_fifobuf_alloc (&fifo, size); + } while (entries[c] == 0); + if ( i!=0) { + pj_fifobuf_free(&fifo, entries[f]); + } + } + if (entries[(i+1)%2]) + pj_fifobuf_free(&fifo, entries[(i+1)%2]); + + if (pj_fifobuf_max_size(&fifo) < SIZE-4) { + pj_assert(0); + return -1; + } + + // Test 2 + entries[0] = pj_fifobuf_alloc (&fifo, MIN_SIZE); + if (!entries[0]) return -1; + for (i=0; i<LOOP*MAX_ENTRIES; ++i) { + int size = MIN_SIZE+(pj_rand() % MAX_SIZE); + entries[1] = pj_fifobuf_alloc (&fifo, size); + if (entries[1]) + pj_fifobuf_unalloc(&fifo, entries[1]); + } + pj_fifobuf_unalloc(&fifo, entries[0]); + if (pj_fifobuf_max_size(&fifo) < SIZE-4) { + pj_assert(0); + return -2; + } + + // Test 3 + for (i=0; i<LOOP; ++i) { + int count, j; + for (count=0; available>=MIN_SIZE+4 && count < MAX_ENTRIES;) { + int size = MIN_SIZE+(pj_rand() % MAX_SIZE); + entries[count] = pj_fifobuf_alloc (&fifo, size); + if (entries[count]) { + available -= (size+4); + ++count; + } + } + for (j=0; j<count; ++j) { + pj_fifobuf_free (&fifo, entries[j]); + } + available = SIZE; + } + + if (pj_fifobuf_max_size(&fifo) < SIZE-4) { + pj_assert(0); + return -3; + } + pj_pool_release(pool); + return 0; +} + +#endif /* INCLUDE_FIFOBUF_TEST */ + + diff --git a/pjlib/src/pjlib-test/file.c b/pjlib/src/pjlib-test/file.c new file mode 100644 index 0000000..df086e5 --- /dev/null +++ b/pjlib/src/pjlib-test/file.c @@ -0,0 +1,225 @@ +/* $Id: file.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.h> + +#if INCLUDE_FILE_TEST + +#define FILENAME "testfil1.txt" +#define NEWNAME "testfil2.txt" +#define INCLUDE_FILE_TIME_TEST 0 + +static char buffer[11] = {'H', 'e', 'l', 'l', 'o', ' ', + 'W', 'o', 'r', 'l', 'd' }; + +static int file_test_internal(void) +{ + enum { FILE_MAX_AGE = 1000 }; + pj_oshandle_t fd = 0; + pj_status_t status; + char readbuf[sizeof(buffer)+16]; + pj_file_stat stat; + pj_time_val start_time; + pj_ssize_t size; + pj_off_t pos; + + PJ_LOG(3,("", "..file io test..")); + + /* Get time. */ + pj_gettimeofday(&start_time); + + /* Delete original file if exists. */ + if (pj_file_exists(FILENAME)) + pj_file_delete(FILENAME); + + /* + * Write data to the file. + */ + status = pj_file_open(NULL, FILENAME, PJ_O_WRONLY, &fd); + if (status != PJ_SUCCESS) { + app_perror("...file_open() error", status); + return -10; + } + + size = sizeof(buffer); + status = pj_file_write(fd, buffer, &size); + if (status != PJ_SUCCESS) { + app_perror("...file_write() error", status); + pj_file_close(fd); + return -20; + } + if (size != sizeof(buffer)) + return -25; + + status = pj_file_close(fd); + if (status != PJ_SUCCESS) { + app_perror("...file_close() error", status); + return -30; + } + + /* Check the file existance and size. */ + if (!pj_file_exists(FILENAME)) + return -40; + + if (pj_file_size(FILENAME) != sizeof(buffer)) + return -50; + + /* Get file stat. */ + status = pj_file_getstat(FILENAME, &stat); + if (status != PJ_SUCCESS) + return -60; + + /* Check stat size. */ + if (stat.size != sizeof(buffer)) + return -70; + +#if INCLUDE_FILE_TIME_TEST + /* Check file creation time >= start_time. */ + if (!PJ_TIME_VAL_GTE(stat.ctime, start_time)) + return -80; + /* Check file creation time is not much later. */ + PJ_TIME_VAL_SUB(stat.ctime, start_time); + if (stat.ctime.sec > FILE_MAX_AGE) + return -90; + + /* Check file modification time >= start_time. */ + if (!PJ_TIME_VAL_GTE(stat.mtime, start_time)) + return -80; + /* Check file modification time is not much later. */ + PJ_TIME_VAL_SUB(stat.mtime, start_time); + if (stat.mtime.sec > FILE_MAX_AGE) + return -90; + + /* Check file access time >= start_time. */ + if (!PJ_TIME_VAL_GTE(stat.atime, start_time)) + return -80; + /* Check file access time is not much later. */ + PJ_TIME_VAL_SUB(stat.atime, start_time); + if (stat.atime.sec > FILE_MAX_AGE) + return -90; +#endif + + /* + * Re-open the file and read data. + */ + status = pj_file_open(NULL, FILENAME, PJ_O_RDONLY, &fd); + if (status != PJ_SUCCESS) { + app_perror("...file_open() error", status); + return -100; + } + + size = 0; + while (size < (pj_ssize_t)sizeof(readbuf)) { + pj_ssize_t read; + read = 1; + status = pj_file_read(fd, &readbuf[size], &read); + if (status != PJ_SUCCESS) { + PJ_LOG(3,("", "...error reading file after %d bytes (error follows)", + size)); + app_perror("...error", status); + return -110; + } + if (read == 0) { + // EOF + break; + } + size += read; + } + + if (size != sizeof(buffer)) + return -120; + + /* + if (!pj_file_eof(fd, PJ_O_RDONLY)) + return -130; + */ + + if (pj_memcmp(readbuf, buffer, size) != 0) + return -140; + + /* Seek test. */ + status = pj_file_setpos(fd, 4, PJ_SEEK_SET); + if (status != PJ_SUCCESS) { + app_perror("...file_setpos() error", status); + return -141; + } + + /* getpos test. */ + status = pj_file_getpos(fd, &pos); + if (status != PJ_SUCCESS) { + app_perror("...file_getpos() error", status); + return -142; + } + if (pos != 4) + return -143; + + status = pj_file_close(fd); + if (status != PJ_SUCCESS) { + app_perror("...file_close() error", status); + return -150; + } + + /* + * Rename test. + */ + status = pj_file_move(FILENAME, NEWNAME); + if (status != PJ_SUCCESS) { + app_perror("...file_move() error", status); + return -160; + } + + if (pj_file_exists(FILENAME)) + return -170; + if (!pj_file_exists(NEWNAME)) + return -180; + + if (pj_file_size(NEWNAME) != sizeof(buffer)) + return -190; + + /* Delete test. */ + status = pj_file_delete(NEWNAME); + if (status != PJ_SUCCESS) { + app_perror("...file_delete() error", status); + return -200; + } + + if (pj_file_exists(NEWNAME)) + return -210; + + PJ_LOG(3,("", "...success")); + return PJ_SUCCESS; +} + + +int file_test(void) +{ + int rc = file_test_internal(); + + /* Delete test file if exists. */ + if (pj_file_exists(FILENAME)) + pj_file_delete(FILENAME); + + return rc; +} + +#else +int dummy_file_test; +#endif + diff --git a/pjlib/src/pjlib-test/hash_test.c b/pjlib/src/pjlib-test/hash_test.c new file mode 100644 index 0000000..088107d --- /dev/null +++ b/pjlib/src/pjlib-test/hash_test.c @@ -0,0 +1,163 @@ +/* $Id: hash_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 <pj/hash.h> +#include <pj/rand.h> +#include <pj/log.h> +#include <pj/pool.h> +#include "test.h" + +#if INCLUDE_HASH_TEST + +#define HASH_COUNT 31 + +static int hash_test_with_key(pj_pool_t *pool, unsigned char key) +{ + pj_hash_table_t *ht; + unsigned value = 0x12345; + pj_hash_iterator_t it_buf, *it; + unsigned *entry; + + ht = pj_hash_create(pool, HASH_COUNT); + if (!ht) + return -10; + + pj_hash_set(pool, ht, &key, sizeof(key), 0, &value); + + entry = (unsigned*) pj_hash_get(ht, &key, sizeof(key), NULL); + if (!entry) + return -20; + + if (*entry != value) + return -30; + + if (pj_hash_count(ht) != 1) + return -30; + + it = pj_hash_first(ht, &it_buf); + if (it == NULL) + return -40; + + entry = (unsigned*) pj_hash_this(ht, it); + if (!entry) + return -50; + + if (*entry != value) + return -60; + + it = pj_hash_next(ht, it); + if (it != NULL) + return -70; + + /* Erase item */ + + pj_hash_set(NULL, ht, &key, sizeof(key), 0, NULL); + + if (pj_hash_get(ht, &key, sizeof(key), NULL) != NULL) + return -80; + + if (pj_hash_count(ht) != 0) + return -90; + + it = pj_hash_first(ht, &it_buf); + if (it != NULL) + return -100; + + return 0; +} + + +static int hash_collision_test(pj_pool_t *pool) +{ + enum { + COUNT = HASH_COUNT * 4 + }; + pj_hash_table_t *ht; + pj_hash_iterator_t it_buf, *it; + unsigned char *values; + unsigned i; + + ht = pj_hash_create(pool, HASH_COUNT); + if (!ht) + return -200; + + values = (unsigned char*) pj_pool_alloc(pool, COUNT); + + for (i=0; i<COUNT; ++i) { + values[i] = (unsigned char)i; + pj_hash_set(pool, ht, &i, sizeof(i), 0, &values[i]); + } + + if (pj_hash_count(ht) != COUNT) + return -210; + + for (i=0; i<COUNT; ++i) { + unsigned char *entry; + entry = (unsigned char*) pj_hash_get(ht, &i, sizeof(i), NULL); + if (!entry) + return -220; + if (*entry != values[i]) + return -230; + } + + i = 0; + it = pj_hash_first(ht, &it_buf); + while (it) { + ++i; + it = pj_hash_next(ht, it); + } + + if (i != COUNT) + return -240; + + return 0; +} + + +/* + * Hash table test. + */ +int hash_test(void) +{ + pj_pool_t *pool = pj_pool_create(mem, "hash", 512, 512, NULL); + int rc; + unsigned i; + + /* Test to fill in each row in the table */ + for (i=0; i<=HASH_COUNT; ++i) { + rc = hash_test_with_key(pool, (unsigned char)i); + if (rc != 0) { + pj_pool_release(pool); + return rc; + } + } + + /* Collision test */ + rc = hash_collision_test(pool); + if (rc != 0) { + pj_pool_release(pool); + return rc; + } + + pj_pool_release(pool); + return 0; +} + +#endif /* INCLUDE_HASH_TEST */ + diff --git a/pjlib/src/pjlib-test/ioq_perf.c b/pjlib/src/pjlib-test/ioq_perf.c new file mode 100644 index 0000000..776be61 --- /dev/null +++ b/pjlib/src/pjlib-test/ioq_perf.c @@ -0,0 +1,572 @@ +/* $Id: ioq_perf.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.h> +#include <pj/compat/high_precision.h> + +/** + * \page page_pjlib_ioqueue_perf_test Test: I/O Queue Performance + * + * Test the performance of the I/O queue, using typical producer + * consumer test. The test should examine the effect of using multiple + * threads on the performance. + * + * This file is <b>pjlib-test/ioq_perf.c</b> + * + * \include pjlib-test/ioq_perf.c + */ + +#if INCLUDE_IOQUEUE_PERF_TEST + +#ifdef _MSC_VER +# pragma warning ( disable: 4204) // non-constant aggregate initializer +#endif + +#define THIS_FILE "ioq_perf" +//#define TRACE_(expr) PJ_LOG(3,expr) +#define TRACE_(expr) + + +static pj_bool_t thread_quit_flag; +static pj_status_t last_error; +static unsigned last_error_counter; + +/* Descriptor for each producer/consumer pair. */ +typedef struct test_item +{ + pj_sock_t server_fd, + client_fd; + pj_ioqueue_t *ioqueue; + pj_ioqueue_key_t *server_key, + *client_key; + pj_ioqueue_op_key_t recv_op, + send_op; + int has_pending_send; + pj_size_t buffer_size; + char *outgoing_buffer; + char *incoming_buffer; + pj_size_t bytes_sent, + bytes_recv; +} test_item; + +/* Callback when data has been read. + * Increment item->bytes_recv and ready to read the next data. + */ +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read) +{ + test_item *item = (test_item*)pj_ioqueue_get_user_data(key); + pj_status_t rc; + int data_is_available = 1; + + //TRACE_((THIS_FILE, " read complete, bytes_read=%d", bytes_read)); + + do { + if (thread_quit_flag) + return; + + if (bytes_read < 0) { + pj_status_t rc = -bytes_read; + char errmsg[PJ_ERR_MSG_SIZE]; + + if (rc != last_error) { + //last_error = rc; + pj_strerror(rc, errmsg, sizeof(errmsg)); + PJ_LOG(3,(THIS_FILE,"...error: read error, bytes_read=%d (%s)", + bytes_read, errmsg)); + PJ_LOG(3,(THIS_FILE, + ".....additional info: total read=%u, total sent=%u", + item->bytes_recv, item->bytes_sent)); + } else { + last_error_counter++; + } + bytes_read = 0; + + } else if (bytes_read == 0) { + PJ_LOG(3,(THIS_FILE, "...socket has closed!")); + } + + item->bytes_recv += bytes_read; + + /* To assure that the test quits, even if main thread + * doesn't have time to run. + */ + if (item->bytes_recv > item->buffer_size * 10000) + thread_quit_flag = 1; + + bytes_read = item->buffer_size; + rc = pj_ioqueue_recv( key, op_key, + item->incoming_buffer, &bytes_read, 0 ); + + if (rc == PJ_SUCCESS) { + data_is_available = 1; + } else if (rc == PJ_EPENDING) { + data_is_available = 0; + } else { + data_is_available = 0; + if (rc != last_error) { + last_error = rc; + app_perror("...error: read error(1)", rc); + } else { + last_error_counter++; + } + } + + if (!item->has_pending_send) { + pj_ssize_t sent = item->buffer_size; + rc = pj_ioqueue_send(item->client_key, &item->send_op, + item->outgoing_buffer, &sent, 0); + if (rc != PJ_SUCCESS && rc != PJ_EPENDING) { + app_perror("...error: write error", rc); + } + + item->has_pending_send = (rc==PJ_EPENDING); + } + + } while (data_is_available); +} + +/* Callback when data has been written. + * Increment item->bytes_sent and write the next data. + */ +static void on_write_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_sent) +{ + test_item *item = (test_item*) pj_ioqueue_get_user_data(key); + + //TRACE_((THIS_FILE, " write complete: sent = %d", bytes_sent)); + + if (thread_quit_flag) + return; + + item->has_pending_send = 0; + item->bytes_sent += bytes_sent; + + if (bytes_sent <= 0) { + PJ_LOG(3,(THIS_FILE, "...error: sending stopped. bytes_sent=%d", + bytes_sent)); + } + else { + pj_status_t rc; + + bytes_sent = item->buffer_size; + rc = pj_ioqueue_send( item->client_key, op_key, + item->outgoing_buffer, &bytes_sent, 0); + if (rc != PJ_SUCCESS && rc != PJ_EPENDING) { + app_perror("...error: write error", rc); + } + + item->has_pending_send = (rc==PJ_EPENDING); + } +} + +struct thread_arg +{ + int id; + pj_ioqueue_t *ioqueue; + unsigned counter; +}; + +/* The worker thread. */ +static int worker_thread(void *p) +{ + struct thread_arg *arg = (struct thread_arg*) p; + const pj_time_val timeout = {0, 100}; + int rc; + + while (!thread_quit_flag) { + + ++arg->counter; + rc = pj_ioqueue_poll(arg->ioqueue, &timeout); + //TRACE_((THIS_FILE, " thread: poll returned rc=%d", rc)); + if (rc < 0) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(-rc, errmsg, sizeof(errmsg)); + PJ_LOG(3, (THIS_FILE, + "...error in pj_ioqueue_poll() in thread %d " + "after %d loop: %s [pj_status_t=%d]", + arg->id, arg->counter, errmsg, -rc)); + //return -1; + } + } + return 0; +} + +/* Calculate the bandwidth for the specific test configuration. + * The test is simple: + * - create sockpair_cnt number of producer-consumer socket pair. + * - create thread_cnt number of worker threads. + * - each producer will send buffer_size bytes data as fast and + * as soon as it can. + * - each consumer will read buffer_size bytes of data as fast + * as it could. + * - measure the total bytes received by all consumers during a + * period of time. + */ +static int perform_test(pj_bool_t allow_concur, + int sock_type, const char *type_name, + unsigned thread_cnt, unsigned sockpair_cnt, + pj_size_t buffer_size, + pj_size_t *p_bandwidth) +{ + enum { MSEC_DURATION = 5000 }; + pj_pool_t *pool; + test_item *items; + pj_thread_t **thread; + pj_ioqueue_t *ioqueue; + pj_status_t rc; + pj_ioqueue_callback ioqueue_callback; + pj_uint32_t total_elapsed_usec, total_received; + pj_highprec_t bandwidth; + pj_timestamp start, stop; + unsigned i; + + TRACE_((THIS_FILE, " starting test..")); + + ioqueue_callback.on_read_complete = &on_read_complete; + ioqueue_callback.on_write_complete = &on_write_complete; + + thread_quit_flag = 0; + + pool = pj_pool_create(mem, NULL, 4096, 4096, NULL); + if (!pool) + return -10; + + items = (test_item*) pj_pool_alloc(pool, sockpair_cnt*sizeof(test_item)); + thread = (pj_thread_t**) + pj_pool_alloc(pool, thread_cnt*sizeof(pj_thread_t*)); + + TRACE_((THIS_FILE, " creating ioqueue..")); + rc = pj_ioqueue_create(pool, sockpair_cnt*2, &ioqueue); + if (rc != PJ_SUCCESS) { + app_perror("...error: unable to create ioqueue", rc); + return -15; + } + + rc = pj_ioqueue_set_default_concurrency(ioqueue, allow_concur); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_ioqueue_set_default_concurrency()", rc); + return -16; + } + + /* Initialize each producer-consumer pair. */ + for (i=0; i<sockpair_cnt; ++i) { + pj_ssize_t bytes; + + items[i].ioqueue = ioqueue; + items[i].buffer_size = buffer_size; + items[i].outgoing_buffer = (char*) pj_pool_alloc(pool, buffer_size); + items[i].incoming_buffer = (char*) pj_pool_alloc(pool, buffer_size); + items[i].bytes_recv = items[i].bytes_sent = 0; + + /* randomize outgoing buffer. */ + pj_create_random_string(items[i].outgoing_buffer, buffer_size); + + /* Create socket pair. */ + TRACE_((THIS_FILE, " calling socketpair..")); + rc = app_socketpair(pj_AF_INET(), sock_type, 0, + &items[i].server_fd, &items[i].client_fd); + if (rc != PJ_SUCCESS) { + app_perror("...error: unable to create socket pair", rc); + return -20; + } + + /* Register server socket to ioqueue. */ + TRACE_((THIS_FILE, " register(1)..")); + rc = pj_ioqueue_register_sock(pool, ioqueue, + items[i].server_fd, + &items[i], &ioqueue_callback, + &items[i].server_key); + if (rc != PJ_SUCCESS) { + app_perror("...error: registering server socket to ioqueue", rc); + return -60; + } + + /* Register client socket to ioqueue. */ + TRACE_((THIS_FILE, " register(2)..")); + rc = pj_ioqueue_register_sock(pool, ioqueue, + items[i].client_fd, + &items[i], &ioqueue_callback, + &items[i].client_key); + if (rc != PJ_SUCCESS) { + app_perror("...error: registering server socket to ioqueue", rc); + return -70; + } + + /* Start reading. */ + TRACE_((THIS_FILE, " pj_ioqueue_recv..")); + bytes = items[i].buffer_size; + rc = pj_ioqueue_recv(items[i].server_key, &items[i].recv_op, + items[i].incoming_buffer, &bytes, + 0); + if (rc != PJ_EPENDING) { + app_perror("...error: pj_ioqueue_recv", rc); + return -73; + } + + /* Start writing. */ + TRACE_((THIS_FILE, " pj_ioqueue_write..")); + bytes = items[i].buffer_size; + rc = pj_ioqueue_send(items[i].client_key, &items[i].send_op, + items[i].outgoing_buffer, &bytes, 0); + if (rc != PJ_SUCCESS && rc != PJ_EPENDING) { + app_perror("...error: pj_ioqueue_write", rc); + return -76; + } + + items[i].has_pending_send = (rc==PJ_EPENDING); + } + + /* Create the threads. */ + for (i=0; i<thread_cnt; ++i) { + struct thread_arg *arg; + + arg = (struct thread_arg*) pj_pool_zalloc(pool, sizeof(*arg)); + arg->id = i; + arg->ioqueue = ioqueue; + arg->counter = 0; + + rc = pj_thread_create( pool, NULL, + &worker_thread, + arg, + PJ_THREAD_DEFAULT_STACK_SIZE, + PJ_THREAD_SUSPENDED, &thread[i] ); + if (rc != PJ_SUCCESS) { + app_perror("...error: unable to create thread", rc); + return -80; + } + } + + /* Mark start time. */ + rc = pj_get_timestamp(&start); + if (rc != PJ_SUCCESS) + return -90; + + /* Start the thread. */ + TRACE_((THIS_FILE, " resuming all threads..")); + for (i=0; i<thread_cnt; ++i) { + rc = pj_thread_resume(thread[i]); + if (rc != 0) + return -100; + } + + /* Wait for MSEC_DURATION seconds. + * This should be as simple as pj_thread_sleep(MSEC_DURATION) actually, + * but unfortunately it doesn't work when system doesn't employ + * timeslicing for threads. + */ + TRACE_((THIS_FILE, " wait for few seconds..")); + do { + pj_thread_sleep(1); + + /* Mark end time. */ + rc = pj_get_timestamp(&stop); + + if (thread_quit_flag) { + TRACE_((THIS_FILE, " transfer limit reached..")); + break; + } + + if (pj_elapsed_usec(&start,&stop)<MSEC_DURATION * 1000) { + TRACE_((THIS_FILE, " time limit reached..")); + break; + } + + } while (1); + + /* Terminate all threads. */ + TRACE_((THIS_FILE, " terminating all threads..")); + thread_quit_flag = 1; + + for (i=0; i<thread_cnt; ++i) { + TRACE_((THIS_FILE, " join thread %d..", i)); + pj_thread_join(thread[i]); + } + + /* Close all sockets. */ + TRACE_((THIS_FILE, " closing all sockets..")); + for (i=0; i<sockpair_cnt; ++i) { + pj_ioqueue_unregister(items[i].server_key); + pj_ioqueue_unregister(items[i].client_key); + } + + /* Destroy threads */ + for (i=0; i<thread_cnt; ++i) { + pj_thread_destroy(thread[i]); + } + + /* Destroy ioqueue. */ + TRACE_((THIS_FILE, " destroying ioqueue..")); + pj_ioqueue_destroy(ioqueue); + + /* Calculate actual time in usec. */ + total_elapsed_usec = pj_elapsed_usec(&start, &stop); + + /* Calculate total bytes received. */ + total_received = 0; + for (i=0; i<sockpair_cnt; ++i) { + total_received = items[i].bytes_recv; + } + + /* bandwidth = total_received*1000/total_elapsed_usec */ + bandwidth = total_received; + pj_highprec_mul(bandwidth, 1000); + pj_highprec_div(bandwidth, total_elapsed_usec); + + *p_bandwidth = (pj_uint32_t)bandwidth; + + PJ_LOG(3,(THIS_FILE, " %.4s %2d %2d %8d KB/s", + type_name, thread_cnt, sockpair_cnt, + *p_bandwidth)); + + /* Done. */ + pj_pool_release(pool); + + TRACE_((THIS_FILE, " done..")); + return 0; +} + +static int ioqueue_perf_test_imp(pj_bool_t allow_concur) +{ + enum { BUF_SIZE = 512 }; + int i, rc; + struct { + int type; + const char *type_name; + int thread_cnt; + int sockpair_cnt; + } test_param[] = + { + { pj_SOCK_DGRAM(), "udp", 1, 1}, + { pj_SOCK_DGRAM(), "udp", 1, 2}, + { pj_SOCK_DGRAM(), "udp", 1, 4}, + { pj_SOCK_DGRAM(), "udp", 1, 8}, + { pj_SOCK_DGRAM(), "udp", 2, 1}, + { pj_SOCK_DGRAM(), "udp", 2, 2}, + { pj_SOCK_DGRAM(), "udp", 2, 4}, + { pj_SOCK_DGRAM(), "udp", 2, 8}, + { pj_SOCK_DGRAM(), "udp", 4, 1}, + { pj_SOCK_DGRAM(), "udp", 4, 2}, + { pj_SOCK_DGRAM(), "udp", 4, 4}, + { pj_SOCK_DGRAM(), "udp", 4, 8}, + { pj_SOCK_DGRAM(), "udp", 4, 16}, + { pj_SOCK_STREAM(), "tcp", 1, 1}, + { pj_SOCK_STREAM(), "tcp", 1, 2}, + { pj_SOCK_STREAM(), "tcp", 1, 4}, + { pj_SOCK_STREAM(), "tcp", 1, 8}, + { pj_SOCK_STREAM(), "tcp", 2, 1}, + { pj_SOCK_STREAM(), "tcp", 2, 2}, + { pj_SOCK_STREAM(), "tcp", 2, 4}, + { pj_SOCK_STREAM(), "tcp", 2, 8}, + { pj_SOCK_STREAM(), "tcp", 4, 1}, + { pj_SOCK_STREAM(), "tcp", 4, 2}, + { pj_SOCK_STREAM(), "tcp", 4, 4}, + { pj_SOCK_STREAM(), "tcp", 4, 8}, + { pj_SOCK_STREAM(), "tcp", 4, 16}, +/* + { pj_SOCK_DGRAM(), "udp", 32, 1}, + { pj_SOCK_DGRAM(), "udp", 32, 1}, + { pj_SOCK_DGRAM(), "udp", 32, 1}, + { pj_SOCK_DGRAM(), "udp", 32, 1}, + { pj_SOCK_DGRAM(), "udp", 1, 32}, + { pj_SOCK_DGRAM(), "udp", 1, 32}, + { pj_SOCK_DGRAM(), "udp", 1, 32}, + { pj_SOCK_DGRAM(), "udp", 1, 32}, + { pj_SOCK_STREAM(), "tcp", 32, 1}, + { pj_SOCK_STREAM(), "tcp", 32, 1}, + { pj_SOCK_STREAM(), "tcp", 32, 1}, + { pj_SOCK_STREAM(), "tcp", 32, 1}, + { pj_SOCK_STREAM(), "tcp", 1, 32}, + { pj_SOCK_STREAM(), "tcp", 1, 32}, + { pj_SOCK_STREAM(), "tcp", 1, 32}, + { pj_SOCK_STREAM(), "tcp", 1, 32}, +*/ + }; + pj_size_t best_bandwidth; + int best_index = 0; + + PJ_LOG(3,(THIS_FILE, " Benchmarking %s ioqueue:", pj_ioqueue_name())); + PJ_LOG(3,(THIS_FILE, " Testing with concurency=%d", allow_concur)); + PJ_LOG(3,(THIS_FILE, " =======================================")); + PJ_LOG(3,(THIS_FILE, " Type Threads Skt.Pairs Bandwidth")); + PJ_LOG(3,(THIS_FILE, " =======================================")); + + best_bandwidth = 0; + for (i=0; i<(int)(sizeof(test_param)/sizeof(test_param[0])); ++i) { + pj_size_t bandwidth; + + rc = perform_test(allow_concur, + test_param[i].type, + test_param[i].type_name, + test_param[i].thread_cnt, + test_param[i].sockpair_cnt, + BUF_SIZE, + &bandwidth); + if (rc != 0) + return rc; + + if (bandwidth > best_bandwidth) + best_bandwidth = bandwidth, best_index = i; + + /* Give it a rest before next test, to allow system to close the + * sockets properly. + */ + pj_thread_sleep(500); + } + + PJ_LOG(3,(THIS_FILE, + " Best: Type=%s Threads=%d, Skt.Pairs=%d, Bandwidth=%u KB/s", + test_param[best_index].type_name, + test_param[best_index].thread_cnt, + test_param[best_index].sockpair_cnt, + best_bandwidth)); + PJ_LOG(3,(THIS_FILE, " (Note: packet size=%d, total errors=%u)", + BUF_SIZE, last_error_counter)); + return 0; +} + +/* + * main test entry. + */ +int ioqueue_perf_test(void) +{ + int rc; + + rc = ioqueue_perf_test_imp(PJ_TRUE); + if (rc != 0) + return rc; + + rc = ioqueue_perf_test_imp(PJ_FALSE); + if (rc != 0) + return rc; + + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_uiq_perf_test; +#endif /* INCLUDE_IOQUEUE_PERF_TEST */ + + diff --git a/pjlib/src/pjlib-test/ioq_tcp.c b/pjlib/src/pjlib-test/ioq_tcp.c new file mode 100644 index 0000000..8ecbc0f --- /dev/null +++ b/pjlib/src/pjlib-test/ioq_tcp.c @@ -0,0 +1,972 @@ +/* $Id: ioq_tcp.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" + +/** + * \page page_pjlib_ioqueue_tcp_test Test: I/O Queue (TCP) + * + * This file provides implementation to test the + * functionality of the I/O queue when TCP socket is used. + * + * + * This file is <b>pjlib-test/ioq_tcp.c</b> + * + * \include pjlib-test/ioq_tcp.c + */ + + +#if INCLUDE_TCP_IOQUEUE_TEST + +#include <pjlib.h> + +#if PJ_HAS_TCP + +#define THIS_FILE "test_tcp" +#define NON_EXISTANT_PORT 50123 +#define LOOP 100 +#define BUF_MIN_SIZE 32 +#define BUF_MAX_SIZE 2048 +#define SOCK_INACTIVE_MIN (4-2) +#define SOCK_INACTIVE_MAX (PJ_IOQUEUE_MAX_HANDLES - 2) +#define POOL_SIZE (2*BUF_MAX_SIZE + SOCK_INACTIVE_MAX*128 + 2048) + +static pj_ssize_t callback_read_size, + callback_write_size, + callback_accept_status, + callback_connect_status; +static unsigned callback_call_count; +static pj_ioqueue_key_t *callback_read_key, + *callback_write_key, + *callback_accept_key, + *callback_connect_key; +static pj_ioqueue_op_key_t *callback_read_op, + *callback_write_op, + *callback_accept_op; + +static void on_ioqueue_read(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read) +{ + callback_read_key = key; + callback_read_op = op_key; + callback_read_size = bytes_read; + callback_call_count++; +} + +static void on_ioqueue_write(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_written) +{ + callback_write_key = key; + callback_write_op = op_key; + callback_write_size = bytes_written; + callback_call_count++; +} + +static void on_ioqueue_accept(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_sock_t sock, + int status) +{ + if (sock == PJ_INVALID_SOCKET) { + + if (status != PJ_SUCCESS) { + /* Ignore. Could be blocking error */ + app_perror(".....warning: received error in on_ioqueue_accept() callback", + status); + } else { + callback_accept_status = -61; + PJ_LOG(3,("", "..... on_ioqueue_accept() callback was given " + "invalid socket and status is %d", status)); + } + } else { + pj_sockaddr addr; + int client_addr_len; + + client_addr_len = sizeof(addr); + status = pj_sock_getsockname(sock, &addr, &client_addr_len); + if (status != PJ_SUCCESS) { + app_perror("...ERROR in pj_sock_getsockname()", status); + } + + callback_accept_key = key; + callback_accept_op = op_key; + callback_accept_status = status; + callback_call_count++; + } +} + +static void on_ioqueue_connect(pj_ioqueue_key_t *key, int status) +{ + callback_connect_key = key; + callback_connect_status = status; + callback_call_count++; +} + +static pj_ioqueue_callback test_cb = +{ + &on_ioqueue_read, + &on_ioqueue_write, + &on_ioqueue_accept, + &on_ioqueue_connect, +}; + +static int send_recv_test(pj_ioqueue_t *ioque, + pj_ioqueue_key_t *skey, + pj_ioqueue_key_t *ckey, + void *send_buf, + void *recv_buf, + pj_ssize_t bufsize, + pj_timestamp *t_elapsed) +{ + pj_status_t status; + pj_ssize_t bytes; + pj_time_val timeout; + pj_timestamp t1, t2; + int pending_op = 0; + pj_ioqueue_op_key_t read_op, write_op; + + // Start reading on the server side. + bytes = bufsize; + status = pj_ioqueue_recv(skey, &read_op, recv_buf, &bytes, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + app_perror("...pj_ioqueue_recv error", status); + return -100; + } + + if (status == PJ_EPENDING) + ++pending_op; + else { + /* Does not expect to return error or immediate data. */ + return -115; + } + + // Randomize send buffer. + pj_create_random_string((char*)send_buf, bufsize); + + // Starts send on the client side. + bytes = bufsize; + status = pj_ioqueue_send(ckey, &write_op, send_buf, &bytes, 0); + if (status != PJ_SUCCESS && bytes != PJ_EPENDING) { + return -120; + } + if (status == PJ_EPENDING) { + ++pending_op; + } + + // Begin time. + pj_get_timestamp(&t1); + + // Reset indicators + callback_read_size = callback_write_size = 0; + callback_read_key = callback_write_key = NULL; + callback_read_op = callback_write_op = NULL; + + // Poll the queue until we've got completion event in the server side. + status = 0; + while (pending_op > 0) { + timeout.sec = 1; timeout.msec = 0; +#ifdef PJ_SYMBIAN + PJ_UNUSED_ARG(ioque); + status = pj_symbianos_poll(-1, 1000); +#else + status = pj_ioqueue_poll(ioque, &timeout); +#endif + if (status > 0) { + if (callback_read_size) { + if (callback_read_size != bufsize) + return -160; + if (callback_read_key != skey) + return -161; + if (callback_read_op != &read_op) + return -162; + } + if (callback_write_size) { + if (callback_write_key != ckey) + return -163; + if (callback_write_op != &write_op) + return -164; + } + pending_op -= status; + } + if (status == 0) { + PJ_LOG(3,("", "...error: timed out")); + } + if (status < 0) { + return -170; + } + } + + // Pending op is zero. + // Subsequent poll should yield zero too. + timeout.sec = timeout.msec = 0; +#ifdef PJ_SYMBIAN + status = pj_symbianos_poll(-1, 1); +#else + status = pj_ioqueue_poll(ioque, &timeout); +#endif + if (status != 0) + return -173; + + // End time. + pj_get_timestamp(&t2); + t_elapsed->u32.lo += (t2.u32.lo - t1.u32.lo); + + // Compare recv buffer with send buffer. + if (pj_memcmp(send_buf, recv_buf, bufsize) != 0) { + return -180; + } + + // Success + return 0; +} + + +/* + * Compliance test for success scenario. + */ +static int compliance_test_0(pj_bool_t allow_concur) +{ + pj_sock_t ssock=-1, csock0=-1, csock1=-1; + pj_sockaddr_in addr, client_addr, rmt_addr; + int client_addr_len; + pj_pool_t *pool = NULL; + char *send_buf, *recv_buf; + pj_ioqueue_t *ioque = NULL; + pj_ioqueue_key_t *skey=NULL, *ckey0=NULL, *ckey1=NULL; + pj_ioqueue_op_key_t accept_op; + int bufsize = BUF_MIN_SIZE; + pj_ssize_t status = -1; + int pending_op = 0; + pj_timestamp t_elapsed; + pj_str_t s; + pj_status_t rc; + + // Create pool. + pool = pj_pool_create(mem, NULL, POOL_SIZE, 4000, NULL); + + // Allocate buffers for send and receive. + send_buf = (char*)pj_pool_alloc(pool, bufsize); + recv_buf = (char*)pj_pool_alloc(pool, bufsize); + + // Create server socket and client socket for connecting + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &ssock); + if (rc != PJ_SUCCESS) { + app_perror("...error creating socket", rc); + status=-1; goto on_error; + } + + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &csock1); + if (rc != PJ_SUCCESS) { + app_perror("...error creating socket", rc); + status=-1; goto on_error; + } + + // Bind server socket. + pj_sockaddr_in_init(&addr, 0, 0); + if ((rc=pj_sock_bind(ssock, &addr, sizeof(addr))) != 0 ) { + app_perror("...bind error", rc); + status=-10; goto on_error; + } + + // Get server address. + client_addr_len = sizeof(addr); + rc = pj_sock_getsockname(ssock, &addr, &client_addr_len); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_sock_getsockname()", rc); + status=-15; goto on_error; + } + addr.sin_addr = pj_inet_addr(pj_cstr(&s, "127.0.0.1")); + + // Create I/O Queue. + rc = pj_ioqueue_create(pool, PJ_IOQUEUE_MAX_HANDLES, &ioque); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_ioqueue_create()", rc); + status=-20; goto on_error; + } + + // Concurrency + rc = pj_ioqueue_set_default_concurrency(ioque, allow_concur); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_ioqueue_set_default_concurrency()", rc); + status=-21; goto on_error; + } + + // Register server socket and client socket. + rc = pj_ioqueue_register_sock(pool, ioque, ssock, NULL, &test_cb, &skey); + if (rc == PJ_SUCCESS) + rc = pj_ioqueue_register_sock(pool, ioque, csock1, NULL, &test_cb, + &ckey1); + else + ckey1 = NULL; + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_ioqueue_register_sock()", rc); + status=-23; goto on_error; + } + + // Server socket listen(). + if (pj_sock_listen(ssock, 5)) { + app_perror("...ERROR in pj_sock_listen()", rc); + status=-25; goto on_error; + } + + // Server socket accept() + client_addr_len = sizeof(pj_sockaddr_in); + status = pj_ioqueue_accept(skey, &accept_op, &csock0, + &client_addr, &rmt_addr, &client_addr_len); + if (status != PJ_EPENDING) { + app_perror("...ERROR in pj_ioqueue_accept()", rc); + status=-30; goto on_error; + } + if (status==PJ_EPENDING) { + ++pending_op; + } + + // Client socket connect() + status = pj_ioqueue_connect(ckey1, &addr, sizeof(addr)); + if (status!=PJ_SUCCESS && status != PJ_EPENDING) { + app_perror("...ERROR in pj_ioqueue_connect()", rc); + status=-40; goto on_error; + } + if (status==PJ_EPENDING) { + ++pending_op; + } + + // Poll until connected + callback_read_size = callback_write_size = 0; + callback_accept_status = callback_connect_status = -2; + callback_call_count = 0; + + callback_read_key = callback_write_key = + callback_accept_key = callback_connect_key = NULL; + callback_accept_op = callback_read_op = callback_write_op = NULL; + + while (pending_op) { + pj_time_val timeout = {1, 0}; + +#ifdef PJ_SYMBIAN + callback_call_count = 0; + pj_symbianos_poll(-1, 1000); + status = callback_call_count; +#else + status = pj_ioqueue_poll(ioque, &timeout); +#endif + if (status > 0) { + if (callback_accept_status != -2) { + if (callback_accept_status != 0) { + status=-41; goto on_error; + } + if (callback_accept_key != skey) { + status=-42; goto on_error; + } + if (callback_accept_op != &accept_op) { + status=-43; goto on_error; + } + callback_accept_status = -2; + } + + if (callback_connect_status != -2) { + if (callback_connect_status != 0) { + status=-50; goto on_error; + } + if (callback_connect_key != ckey1) { + status=-51; goto on_error; + } + callback_connect_status = -2; + } + + if (status > pending_op) { + PJ_LOG(3,(THIS_FILE, + "...error: pj_ioqueue_poll() returned %d " + "(only expecting %d)", + status, pending_op)); + return -52; + } + pending_op -= status; + + if (pending_op == 0) { + status = 0; + } + } + } + + // There's no pending operation. + // When we poll the ioqueue, there must not be events. + if (pending_op == 0) { + pj_time_val timeout = {1, 0}; +#ifdef PJ_SYMBIAN + status = pj_symbianos_poll(-1, 1000); +#else + status = pj_ioqueue_poll(ioque, &timeout); +#endif + if (status != 0) { + status=-60; goto on_error; + } + } + + // Check accepted socket. + if (csock0 == PJ_INVALID_SOCKET) { + status = -69; + app_perror("...accept() error", pj_get_os_error()); + goto on_error; + } + + // Register newly accepted socket. + rc = pj_ioqueue_register_sock(pool, ioque, csock0, NULL, + &test_cb, &ckey0); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_ioqueue_register_sock", rc); + status = -70; + goto on_error; + } + + // Test send and receive. + t_elapsed.u32.lo = 0; + status = send_recv_test(ioque, ckey0, ckey1, send_buf, + recv_buf, bufsize, &t_elapsed); + if (status != 0) { + goto on_error; + } + + // Success + status = 0; + +on_error: + if (skey != NULL) + pj_ioqueue_unregister(skey); + else if (ssock != PJ_INVALID_SOCKET) + pj_sock_close(ssock); + + if (ckey1 != NULL) + pj_ioqueue_unregister(ckey1); + else if (csock1 != PJ_INVALID_SOCKET) + pj_sock_close(csock1); + + if (ckey0 != NULL) + pj_ioqueue_unregister(ckey0); + else if (csock0 != PJ_INVALID_SOCKET) + pj_sock_close(csock0); + + if (ioque != NULL) + pj_ioqueue_destroy(ioque); + pj_pool_release(pool); + return status; + +} + +/* + * Compliance test for failed scenario. + * In this case, the client connects to a non-existant service. + */ +static int compliance_test_1(pj_bool_t allow_concur) +{ + pj_sock_t csock1=PJ_INVALID_SOCKET; + pj_sockaddr_in addr; + pj_pool_t *pool = NULL; + pj_ioqueue_t *ioque = NULL; + pj_ioqueue_key_t *ckey1 = NULL; + pj_ssize_t status = -1; + int pending_op = 0; + pj_str_t s; + pj_status_t rc; + + // Create pool. + pool = pj_pool_create(mem, NULL, POOL_SIZE, 4000, NULL); + + // Create I/O Queue. + rc = pj_ioqueue_create(pool, PJ_IOQUEUE_MAX_HANDLES, &ioque); + if (!ioque) { + status=-20; goto on_error; + } + + // Concurrency + rc = pj_ioqueue_set_default_concurrency(ioque, allow_concur); + if (rc != PJ_SUCCESS) { + status=-21; goto on_error; + } + + // Create client socket + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &csock1); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_sock_socket()", rc); + status=-1; goto on_error; + } + + // Register client socket. + rc = pj_ioqueue_register_sock(pool, ioque, csock1, NULL, + &test_cb, &ckey1); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_ioqueue_register_sock()", rc); + status=-23; goto on_error; + } + + // Initialize remote address. + pj_sockaddr_in_init(&addr, pj_cstr(&s, "127.0.0.1"), NON_EXISTANT_PORT); + + // Client socket connect() + status = pj_ioqueue_connect(ckey1, &addr, sizeof(addr)); + if (status==PJ_SUCCESS) { + // unexpectedly success! + status = -30; + goto on_error; + } + if (status != PJ_EPENDING) { + // success + } else { + ++pending_op; + } + + callback_connect_status = -2; + callback_connect_key = NULL; + + // Poll until we've got result + while (pending_op) { + pj_time_val timeout = {1, 0}; + +#ifdef PJ_SYMBIAN + callback_call_count = 0; + pj_symbianos_poll(-1, 1000); + status = callback_call_count; +#else + status = pj_ioqueue_poll(ioque, &timeout); +#endif + if (status > 0) { + if (callback_connect_key==ckey1) { + if (callback_connect_status == 0) { + // unexpectedly connected! + status = -50; + goto on_error; + } + } + + if (status > pending_op) { + PJ_LOG(3,(THIS_FILE, + "...error: pj_ioqueue_poll() returned %d " + "(only expecting %d)", + status, pending_op)); + return -552; + } + + pending_op -= status; + if (pending_op == 0) { + status = 0; + } + } + } + + // There's no pending operation. + // When we poll the ioqueue, there must not be events. + if (pending_op == 0) { + pj_time_val timeout = {1, 0}; +#ifdef PJ_SYMBIAN + status = pj_symbianos_poll(-1, 1000); +#else + status = pj_ioqueue_poll(ioque, &timeout); +#endif + if (status != 0) { + status=-60; goto on_error; + } + } + + // Success + status = 0; + +on_error: + if (ckey1 != NULL) + pj_ioqueue_unregister(ckey1); + else if (csock1 != PJ_INVALID_SOCKET) + pj_sock_close(csock1); + + if (ioque != NULL) + pj_ioqueue_destroy(ioque); + pj_pool_release(pool); + return status; +} + + +/* + * Repeated connect/accept on the same listener socket. + */ +static int compliance_test_2(pj_bool_t allow_concur) +{ +#if defined(PJ_SYMBIAN) && PJ_SYMBIAN!=0 + enum { MAX_PAIR = 1, TEST_LOOP = 2 }; +#else + enum { MAX_PAIR = 4, TEST_LOOP = 2 }; +#endif + + struct listener + { + pj_sock_t sock; + pj_ioqueue_key_t *key; + pj_sockaddr_in addr; + int addr_len; + } listener; + + struct server + { + pj_sock_t sock; + pj_ioqueue_key_t *key; + pj_sockaddr_in local_addr; + pj_sockaddr_in rem_addr; + int rem_addr_len; + pj_ioqueue_op_key_t accept_op; + } server[MAX_PAIR]; + + struct client + { + pj_sock_t sock; + pj_ioqueue_key_t *key; + } client[MAX_PAIR]; + + pj_pool_t *pool = NULL; + char *send_buf, *recv_buf; + pj_ioqueue_t *ioque = NULL; + int i, bufsize = BUF_MIN_SIZE; + pj_ssize_t status; + int test_loop, pending_op = 0; + pj_timestamp t_elapsed; + pj_str_t s; + pj_status_t rc; + + listener.sock = PJ_INVALID_SOCKET; + listener.key = NULL; + + for (i=0; i<MAX_PAIR; ++i) { + server[i].sock = PJ_INVALID_SOCKET; + server[i].key = NULL; + } + + for (i=0; i<MAX_PAIR; ++i) { + client[i].sock = PJ_INVALID_SOCKET; + client[i].key = NULL; + } + + // Create pool. + pool = pj_pool_create(mem, NULL, POOL_SIZE, 4000, NULL); + + + // Create I/O Queue. + rc = pj_ioqueue_create(pool, PJ_IOQUEUE_MAX_HANDLES, &ioque); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_ioqueue_create()", rc); + return -10; + } + + + // Concurrency + rc = pj_ioqueue_set_default_concurrency(ioque, allow_concur); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_ioqueue_set_default_concurrency()", rc); + return -11; + } + + // Allocate buffers for send and receive. + send_buf = (char*)pj_pool_alloc(pool, bufsize); + recv_buf = (char*)pj_pool_alloc(pool, bufsize); + + // Create listener socket + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &listener.sock); + if (rc != PJ_SUCCESS) { + app_perror("...error creating socket", rc); + status=-20; goto on_error; + } + + // Bind listener socket. + pj_sockaddr_in_init(&listener.addr, 0, 0); + if ((rc=pj_sock_bind(listener.sock, &listener.addr, sizeof(listener.addr))) != 0 ) { + app_perror("...bind error", rc); + status=-30; goto on_error; + } + + // Get listener address. + listener.addr_len = sizeof(listener.addr); + rc = pj_sock_getsockname(listener.sock, &listener.addr, &listener.addr_len); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_sock_getsockname()", rc); + status=-40; goto on_error; + } + listener.addr.sin_addr = pj_inet_addr(pj_cstr(&s, "127.0.0.1")); + + + // Register listener socket. + rc = pj_ioqueue_register_sock(pool, ioque, listener.sock, NULL, &test_cb, + &listener.key); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR", rc); + status=-50; goto on_error; + } + + + // Listener socket listen(). + if (pj_sock_listen(listener.sock, 5)) { + app_perror("...ERROR in pj_sock_listen()", rc); + status=-60; goto on_error; + } + + + for (test_loop=0; test_loop < TEST_LOOP; ++test_loop) { + // Client connect and server accept. + for (i=0; i<MAX_PAIR; ++i) { + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &client[i].sock); + if (rc != PJ_SUCCESS) { + app_perror("...error creating socket", rc); + status=-70; goto on_error; + } + + rc = pj_ioqueue_register_sock(pool, ioque, client[i].sock, NULL, + &test_cb, &client[i].key); + if (rc != PJ_SUCCESS) { + app_perror("...error ", rc); + status=-80; goto on_error; + } + + // Server socket accept() + pj_ioqueue_op_key_init(&server[i].accept_op, + sizeof(server[i].accept_op)); + server[i].rem_addr_len = sizeof(pj_sockaddr_in); + status = pj_ioqueue_accept(listener.key, &server[i].accept_op, + &server[i].sock, &server[i].local_addr, + &server[i].rem_addr, + &server[i].rem_addr_len); + if (status!=PJ_SUCCESS && status != PJ_EPENDING) { + app_perror("...ERROR in pj_ioqueue_accept()", rc); + status=-90; goto on_error; + } + if (status==PJ_EPENDING) { + ++pending_op; + } + + + // Client socket connect() + status = pj_ioqueue_connect(client[i].key, &listener.addr, + sizeof(listener.addr)); + if (status!=PJ_SUCCESS && status != PJ_EPENDING) { + app_perror("...ERROR in pj_ioqueue_connect()", rc); + status=-100; goto on_error; + } + if (status==PJ_EPENDING) { + ++pending_op; + } + + // Poll until connection of this pair established + while (pending_op) { + pj_time_val timeout = {1, 0}; + +#ifdef PJ_SYMBIAN + status = pj_symbianos_poll(-1, 1000); +#else + status = pj_ioqueue_poll(ioque, &timeout); +#endif + if (status > 0) { + if (status > pending_op) { + PJ_LOG(3,(THIS_FILE, + "...error: pj_ioqueue_poll() returned %d " + "(only expecting %d)", + status, pending_op)); + return -110; + } + pending_op -= status; + + if (pending_op == 0) { + status = 0; + } + } + } + } + + // There's no pending operation. + // When we poll the ioqueue, there must not be events. + if (pending_op == 0) { + pj_time_val timeout = {1, 0}; +#ifdef PJ_SYMBIAN + status = pj_symbianos_poll(-1, 1000); +#else + status = pj_ioqueue_poll(ioque, &timeout); +#endif + if (status != 0) { + status=-120; goto on_error; + } + } + + for (i=0; i<MAX_PAIR; ++i) { + // Check server socket. + if (server[i].sock == PJ_INVALID_SOCKET) { + status = -130; + app_perror("...accept() error", pj_get_os_error()); + goto on_error; + } + + // Check addresses + if (server[i].local_addr.sin_family != pj_AF_INET() || + server[i].local_addr.sin_addr.s_addr == 0 || + server[i].local_addr.sin_port == 0) + { + app_perror("...ERROR address not set", rc); + status = -140; + goto on_error; + } + + if (server[i].rem_addr.sin_family != pj_AF_INET() || + server[i].rem_addr.sin_addr.s_addr == 0 || + server[i].rem_addr.sin_port == 0) + { + app_perror("...ERROR address not set", rc); + status = -150; + goto on_error; + } + + + // Register newly accepted socket. + rc = pj_ioqueue_register_sock(pool, ioque, server[i].sock, NULL, + &test_cb, &server[i].key); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_ioqueue_register_sock", rc); + status = -160; + goto on_error; + } + + // Test send and receive. + t_elapsed.u32.lo = 0; + status = send_recv_test(ioque, server[i].key, client[i].key, + send_buf, recv_buf, bufsize, &t_elapsed); + if (status != 0) { + goto on_error; + } + } + + // Success + status = 0; + + for (i=0; i<MAX_PAIR; ++i) { + if (server[i].key != NULL) { + pj_ioqueue_unregister(server[i].key); + server[i].key = NULL; + server[i].sock = PJ_INVALID_SOCKET; + } else if (server[i].sock != PJ_INVALID_SOCKET) { + pj_sock_close(server[i].sock); + server[i].sock = PJ_INVALID_SOCKET; + } + + if (client[i].key != NULL) { + pj_ioqueue_unregister(client[i].key); + client[i].key = NULL; + client[i].sock = PJ_INVALID_SOCKET; + } else if (client[i].sock != PJ_INVALID_SOCKET) { + pj_sock_close(client[i].sock); + client[i].sock = PJ_INVALID_SOCKET; + } + } + } + + status = 0; + +on_error: + for (i=0; i<MAX_PAIR; ++i) { + if (server[i].key != NULL) { + pj_ioqueue_unregister(server[i].key); + server[i].key = NULL; + server[i].sock = PJ_INVALID_SOCKET; + } else if (server[i].sock != PJ_INVALID_SOCKET) { + pj_sock_close(server[i].sock); + server[i].sock = PJ_INVALID_SOCKET; + } + + if (client[i].key != NULL) { + pj_ioqueue_unregister(client[i].key); + client[i].key = NULL; + server[i].sock = PJ_INVALID_SOCKET; + } else if (client[i].sock != PJ_INVALID_SOCKET) { + pj_sock_close(client[i].sock); + client[i].sock = PJ_INVALID_SOCKET; + } + } + + if (listener.key) { + pj_ioqueue_unregister(listener.key); + listener.key = NULL; + } else if (listener.sock != PJ_INVALID_SOCKET) { + pj_sock_close(listener.sock); + listener.sock = PJ_INVALID_SOCKET; + } + + if (ioque != NULL) + pj_ioqueue_destroy(ioque); + pj_pool_release(pool); + return status; + +} + + +static int tcp_ioqueue_test_impl(pj_bool_t allow_concur) +{ + int status; + + PJ_LOG(3,(THIS_FILE, "..testing with concurency=%d", allow_concur)); + + PJ_LOG(3, (THIS_FILE, "..%s compliance test 0 (success scenario)", + pj_ioqueue_name())); + if ((status=compliance_test_0(allow_concur)) != 0) { + PJ_LOG(1, (THIS_FILE, "....FAILED (status=%d)\n", status)); + return status; + } + PJ_LOG(3, (THIS_FILE, "..%s compliance test 1 (failed scenario)", + pj_ioqueue_name())); + if ((status=compliance_test_1(allow_concur)) != 0) { + PJ_LOG(1, (THIS_FILE, "....FAILED (status=%d)\n", status)); + return status; + } + + PJ_LOG(3, (THIS_FILE, "..%s compliance test 2 (repeated accept)", + pj_ioqueue_name())); + if ((status=compliance_test_2(allow_concur)) != 0) { + PJ_LOG(1, (THIS_FILE, "....FAILED (status=%d)\n", status)); + return status; + } + + return 0; +} + +int tcp_ioqueue_test() +{ + int rc; + + rc = tcp_ioqueue_test_impl(PJ_TRUE); + if (rc != 0) + return rc; + + rc = tcp_ioqueue_test_impl(PJ_FALSE); + if (rc != 0) + return rc; + + return 0; +} + +#endif /* PJ_HAS_TCP */ + + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_uiq_tcp; +#endif /* INCLUDE_TCP_IOQUEUE_TEST */ + + diff --git a/pjlib/src/pjlib-test/ioq_udp.c b/pjlib/src/pjlib-test/ioq_udp.c new file mode 100644 index 0000000..6928641 --- /dev/null +++ b/pjlib/src/pjlib-test/ioq_udp.c @@ -0,0 +1,951 @@ +/* $Id: ioq_udp.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" + + +/** + * \page page_pjlib_ioqueue_udp_test Test: I/O Queue (UDP) + * + * This file provides implementation to test the + * functionality of the I/O queue when UDP socket is used. + * + * + * This file is <b>pjlib-test/ioq_udp.c</b> + * + * \include pjlib-test/ioq_udp.c + */ + + +#if INCLUDE_UDP_IOQUEUE_TEST + +#include <pjlib.h> + +#include <pj/compat/socket.h> + +#define THIS_FILE "test_udp" +#define PORT 51233 +#define LOOP 2 +///#define LOOP 2 +#define BUF_MIN_SIZE 32 +#define BUF_MAX_SIZE 2048 +#define SOCK_INACTIVE_MIN (1) +#define SOCK_INACTIVE_MAX (PJ_IOQUEUE_MAX_HANDLES - 2) +#define POOL_SIZE (2*BUF_MAX_SIZE + SOCK_INACTIVE_MAX*128 + 2048) + +#undef TRACE_ +#define TRACE_(msg) PJ_LOG(3,(THIS_FILE,"....." msg)) + +#if 0 +# define TRACE__(args) PJ_LOG(3,args) +#else +# define TRACE__(args) +#endif + + +static pj_ssize_t callback_read_size, + callback_write_size, + callback_accept_status, + callback_connect_status; +static pj_ioqueue_key_t *callback_read_key, + *callback_write_key, + *callback_accept_key, + *callback_connect_key; +static pj_ioqueue_op_key_t *callback_read_op, + *callback_write_op, + *callback_accept_op; + +static void on_ioqueue_read(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read) +{ + callback_read_key = key; + callback_read_op = op_key; + callback_read_size = bytes_read; + TRACE__((THIS_FILE, " callback_read_key = %p, bytes=%d", + key, bytes_read)); +} + +static void on_ioqueue_write(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_written) +{ + callback_write_key = key; + callback_write_op = op_key; + callback_write_size = bytes_written; +} + +static void on_ioqueue_accept(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_sock_t sock, int status) +{ + PJ_UNUSED_ARG(sock); + callback_accept_key = key; + callback_accept_op = op_key; + callback_accept_status = status; +} + +static void on_ioqueue_connect(pj_ioqueue_key_t *key, int status) +{ + callback_connect_key = key; + callback_connect_status = status; +} + +static pj_ioqueue_callback test_cb = +{ + &on_ioqueue_read, + &on_ioqueue_write, + &on_ioqueue_accept, + &on_ioqueue_connect, +}; + +#ifdef PJ_WIN32 +# define S_ADDR S_un.S_addr +#else +# define S_ADDR s_addr +#endif + +/* + * compliance_test() + * To test that the basic IOQueue functionality works. It will just exchange + * data between two sockets. + */ +static int compliance_test(pj_bool_t allow_concur) +{ + pj_sock_t ssock=-1, csock=-1; + pj_sockaddr_in addr, dst_addr; + int addrlen; + pj_pool_t *pool = NULL; + char *send_buf, *recv_buf; + pj_ioqueue_t *ioque = NULL; + pj_ioqueue_key_t *skey = NULL, *ckey = NULL; + pj_ioqueue_op_key_t read_op, write_op; + int bufsize = BUF_MIN_SIZE; + pj_ssize_t bytes, status = -1; + pj_str_t temp; + pj_bool_t send_pending, recv_pending; + pj_status_t rc; + + pj_set_os_error(PJ_SUCCESS); + + // Create pool. + pool = pj_pool_create(mem, NULL, POOL_SIZE, 4000, NULL); + + // Allocate buffers for send and receive. + send_buf = (char*)pj_pool_alloc(pool, bufsize); + recv_buf = (char*)pj_pool_alloc(pool, bufsize); + + // Allocate sockets for sending and receiving. + TRACE_("creating sockets..."); + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &ssock); + if (rc==PJ_SUCCESS) + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &csock); + else + csock = PJ_INVALID_SOCKET; + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_sock_socket()", rc); + status=-1; goto on_error; + } + + // Bind server socket. + TRACE_("bind socket..."); + pj_bzero(&addr, sizeof(addr)); + addr.sin_family = pj_AF_INET(); + addr.sin_port = pj_htons(PORT); + if (pj_sock_bind(ssock, &addr, sizeof(addr))) { + status=-10; goto on_error; + } + + // Create I/O Queue. + TRACE_("create ioqueue..."); + rc = pj_ioqueue_create(pool, PJ_IOQUEUE_MAX_HANDLES, &ioque); + if (rc != PJ_SUCCESS) { + status=-20; goto on_error; + } + + // Set concurrency + TRACE_("set concurrency..."); + rc = pj_ioqueue_set_default_concurrency(ioque, allow_concur); + if (rc != PJ_SUCCESS) { + status=-21; goto on_error; + } + + // Register server and client socket. + // We put this after inactivity socket, hopefully this can represent the + // worst waiting time. + TRACE_("registering first sockets..."); + rc = pj_ioqueue_register_sock(pool, ioque, ssock, NULL, + &test_cb, &skey); + if (rc != PJ_SUCCESS) { + app_perror("...error(10): ioqueue_register error", rc); + status=-25; goto on_error; + } + TRACE_("registering second sockets..."); + rc = pj_ioqueue_register_sock( pool, ioque, csock, NULL, + &test_cb, &ckey); + if (rc != PJ_SUCCESS) { + app_perror("...error(11): ioqueue_register error", rc); + status=-26; goto on_error; + } + + // Randomize send_buf. + pj_create_random_string(send_buf, bufsize); + + // Register reading from ioqueue. + TRACE_("start recvfrom..."); + pj_bzero(&addr, sizeof(addr)); + addrlen = sizeof(addr); + bytes = bufsize; + rc = pj_ioqueue_recvfrom(skey, &read_op, recv_buf, &bytes, 0, + &addr, &addrlen); + if (rc != PJ_SUCCESS && rc != PJ_EPENDING) { + app_perror("...error: pj_ioqueue_recvfrom", rc); + status=-28; goto on_error; + } else if (rc == PJ_EPENDING) { + recv_pending = 1; + PJ_LOG(3, (THIS_FILE, + "......ok: recvfrom returned pending")); + } else { + PJ_LOG(3, (THIS_FILE, + "......error: recvfrom returned immediate ok!")); + status=-29; goto on_error; + } + + // Set destination address to send the packet. + TRACE_("set destination address..."); + temp = pj_str("127.0.0.1"); + if ((rc=pj_sockaddr_in_init(&dst_addr, &temp, PORT)) != 0) { + app_perror("...error: unable to resolve 127.0.0.1", rc); + status=-290; goto on_error; + } + + // Write must return the number of bytes. + TRACE_("start sendto..."); + bytes = bufsize; + rc = pj_ioqueue_sendto(ckey, &write_op, send_buf, &bytes, 0, &dst_addr, + sizeof(dst_addr)); + if (rc != PJ_SUCCESS && rc != PJ_EPENDING) { + app_perror("...error: pj_ioqueue_sendto", rc); + status=-30; goto on_error; + } else if (rc == PJ_EPENDING) { + send_pending = 1; + PJ_LOG(3, (THIS_FILE, + "......ok: sendto returned pending")); + } else { + send_pending = 0; + PJ_LOG(3, (THIS_FILE, + "......ok: sendto returned immediate success")); + } + + // reset callback variables. + callback_read_size = callback_write_size = 0; + callback_accept_status = callback_connect_status = -2; + callback_read_key = callback_write_key = + callback_accept_key = callback_connect_key = NULL; + callback_read_op = callback_write_op = NULL; + + // Poll if pending. + while (send_pending || recv_pending) { + int rc; + pj_time_val timeout = { 5, 0 }; + + TRACE_("poll..."); +#ifdef PJ_SYMBIAN + rc = pj_symbianos_poll(-1, 5000); +#else + rc = pj_ioqueue_poll(ioque, &timeout); +#endif + + if (rc == 0) { + PJ_LOG(1,(THIS_FILE, "...ERROR: timed out...")); + status=-45; goto on_error; + } else if (rc < 0) { + app_perror("...ERROR in ioqueue_poll()", -rc); + status=-50; goto on_error; + } + + if (callback_read_key != NULL) { + if (callback_read_size != bufsize) { + status=-61; goto on_error; + } + if (callback_read_key != skey) { + status=-65; goto on_error; + } + if (callback_read_op != &read_op) { + status=-66; goto on_error; + } + + if (pj_memcmp(send_buf, recv_buf, bufsize) != 0) { + status=-67; goto on_error; + } + if (addrlen != sizeof(pj_sockaddr_in)) { + status=-68; goto on_error; + } + if (addr.sin_family != pj_AF_INET()) { + status=-69; goto on_error; + } + + + recv_pending = 0; + } + + if (callback_write_key != NULL) { + if (callback_write_size != bufsize) { + status=-73; goto on_error; + } + if (callback_write_key != ckey) { + status=-75; goto on_error; + } + if (callback_write_op != &write_op) { + status=-76; goto on_error; + } + + send_pending = 0; + } + } + + // Success + status = 0; + +on_error: + if (skey) + pj_ioqueue_unregister(skey); + else if (ssock != -1) + pj_sock_close(ssock); + + if (ckey) + pj_ioqueue_unregister(ckey); + else if (csock != -1) + pj_sock_close(csock); + + if (ioque != NULL) + pj_ioqueue_destroy(ioque); + pj_pool_release(pool); + return status; + +} + + +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read) +{ + unsigned *p_packet_cnt = (unsigned*) pj_ioqueue_get_user_data(key); + + PJ_UNUSED_ARG(op_key); + PJ_UNUSED_ARG(bytes_read); + + (*p_packet_cnt)++; +} + +/* + * unregister_test() + * Check if callback is still called after socket has been unregistered or + * closed. + */ +static int unregister_test(pj_bool_t allow_concur) +{ + enum { RPORT = 50000, SPORT = 50001 }; + pj_pool_t *pool; + pj_ioqueue_t *ioqueue; + pj_sock_t ssock; + pj_sock_t rsock; + int addrlen; + pj_sockaddr_in addr; + pj_ioqueue_key_t *key; + pj_ioqueue_op_key_t opkey; + pj_ioqueue_callback cb; + unsigned packet_cnt; + char sendbuf[10], recvbuf[10]; + pj_ssize_t bytes; + pj_time_val timeout; + pj_status_t status; + + pool = pj_pool_create(mem, "test", 4000, 4000, NULL); + if (!pool) { + app_perror("Unable to create pool", PJ_ENOMEM); + return -100; + } + + status = pj_ioqueue_create(pool, 16, &ioqueue); + if (status != PJ_SUCCESS) { + app_perror("Error creating ioqueue", status); + return -110; + } + + // Set concurrency + TRACE_("set concurrency..."); + status = pj_ioqueue_set_default_concurrency(ioqueue, allow_concur); + if (status != PJ_SUCCESS) { + return -112; + } + + /* Create sender socket */ + status = app_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, SPORT, &ssock); + if (status != PJ_SUCCESS) { + app_perror("Error initializing socket", status); + return -120; + } + + /* Create receiver socket. */ + status = app_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, RPORT, &rsock); + if (status != PJ_SUCCESS) { + app_perror("Error initializing socket", status); + return -130; + } + + /* Register rsock to ioqueue. */ + pj_bzero(&cb, sizeof(cb)); + cb.on_read_complete = &on_read_complete; + packet_cnt = 0; + status = pj_ioqueue_register_sock(pool, ioqueue, rsock, &packet_cnt, + &cb, &key); + if (status != PJ_SUCCESS) { + app_perror("Error registering to ioqueue", status); + return -140; + } + + /* Init operation key. */ + pj_ioqueue_op_key_init(&opkey, sizeof(opkey)); + + /* Start reading. */ + bytes = sizeof(recvbuf); + status = pj_ioqueue_recv( key, &opkey, recvbuf, &bytes, 0); + if (status != PJ_EPENDING) { + app_perror("Expecting PJ_EPENDING, but got this", status); + return -150; + } + + /* Init destination address. */ + addrlen = sizeof(addr); + status = pj_sock_getsockname(rsock, &addr, &addrlen); + if (status != PJ_SUCCESS) { + app_perror("getsockname error", status); + return -160; + } + + /* Override address with 127.0.0.1, since getsockname will return + * zero in the address field. + */ + addr.sin_addr = pj_inet_addr2("127.0.0.1"); + + /* Init buffer to send */ + pj_ansi_strcpy(sendbuf, "Hello0123"); + + /* Send one packet. */ + bytes = sizeof(sendbuf); + status = pj_sock_sendto(ssock, sendbuf, &bytes, 0, + &addr, sizeof(addr)); + + if (status != PJ_SUCCESS) { + app_perror("sendto error", status); + return -170; + } + + /* Check if packet is received. */ + timeout.sec = 1; timeout.msec = 0; +#ifdef PJ_SYMBIAN + pj_symbianos_poll(-1, 1000); +#else + pj_ioqueue_poll(ioqueue, &timeout); +#endif + + if (packet_cnt != 1) { + return -180; + } + + /* Just to make sure things are settled.. */ + pj_thread_sleep(100); + + /* Start reading again. */ + bytes = sizeof(recvbuf); + status = pj_ioqueue_recv( key, &opkey, recvbuf, &bytes, 0); + if (status != PJ_EPENDING) { + app_perror("Expecting PJ_EPENDING, but got this", status); + return -190; + } + + /* Reset packet counter */ + packet_cnt = 0; + + /* Send one packet. */ + bytes = sizeof(sendbuf); + status = pj_sock_sendto(ssock, sendbuf, &bytes, 0, + &addr, sizeof(addr)); + + if (status != PJ_SUCCESS) { + app_perror("sendto error", status); + return -200; + } + + /* Now unregister and close socket. */ + pj_ioqueue_unregister(key); + + /* Poll ioqueue. */ +#ifdef PJ_SYMBIAN + pj_symbianos_poll(-1, 1000); +#else + timeout.sec = 1; timeout.msec = 0; + pj_ioqueue_poll(ioqueue, &timeout); +#endif + + /* Must NOT receive any packets after socket is closed! */ + if (packet_cnt > 0) { + PJ_LOG(3,(THIS_FILE, "....errror: not expecting to receive packet " + "after socket has been closed")); + return -210; + } + + /* Success */ + pj_sock_close(ssock); + pj_ioqueue_destroy(ioqueue); + + pj_pool_release(pool); + + return 0; +} + + +/* + * Testing with many handles. + * This will just test registering PJ_IOQUEUE_MAX_HANDLES count + * of sockets to the ioqueue. + */ +static int many_handles_test(pj_bool_t allow_concur) +{ + enum { MAX = PJ_IOQUEUE_MAX_HANDLES }; + pj_pool_t *pool; + pj_ioqueue_t *ioqueue; + pj_sock_t *sock; + pj_ioqueue_key_t **key; + pj_status_t rc; + int count, i; /* must be signed */ + + PJ_LOG(3,(THIS_FILE,"...testing with so many handles")); + + pool = pj_pool_create(mem, NULL, 4000, 4000, NULL); + if (!pool) + return PJ_ENOMEM; + + key = (pj_ioqueue_key_t**) + pj_pool_alloc(pool, MAX*sizeof(pj_ioqueue_key_t*)); + sock = (pj_sock_t*) pj_pool_alloc(pool, MAX*sizeof(pj_sock_t)); + + /* Create IOQueue */ + rc = pj_ioqueue_create(pool, MAX, &ioqueue); + if (rc != PJ_SUCCESS || ioqueue == NULL) { + app_perror("...error in pj_ioqueue_create", rc); + return -10; + } + + // Set concurrency + rc = pj_ioqueue_set_default_concurrency(ioqueue, allow_concur); + if (rc != PJ_SUCCESS) { + return -11; + } + + /* Register as many sockets. */ + for (count=0; count<MAX; ++count) { + sock[count] = PJ_INVALID_SOCKET; + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &sock[count]); + if (rc != PJ_SUCCESS || sock[count] == PJ_INVALID_SOCKET) { + PJ_LOG(3,(THIS_FILE, "....unable to create %d-th socket, rc=%d", + count, rc)); + break; + } + key[count] = NULL; + rc = pj_ioqueue_register_sock(pool, ioqueue, sock[count], + NULL, &test_cb, &key[count]); + if (rc != PJ_SUCCESS || key[count] == NULL) { + PJ_LOG(3,(THIS_FILE, "....unable to register %d-th socket, rc=%d", + count, rc)); + return -30; + } + } + + /* Test complete. */ + + /* Now deregister and close all handles. */ + + /* NOTE for RTEMS: + * It seems that the order of close(sock) is pretty important here. + * If we close the sockets with the same order as when they were created, + * RTEMS doesn't seem to reuse the sockets, thus next socket created + * will have descriptor higher than the last socket created. + * If we close the sockets in the reverse order, then the descriptor will + * get reused. + * This used to cause problem with select ioqueue, since the ioqueue + * always gives FD_SETSIZE for the first select() argument. This ioqueue + * behavior can be changed with setting PJ_SELECT_NEEDS_NFDS macro. + */ + for (i=count-1; i>=0; --i) { + ///for (i=0; i<count; ++i) { + rc = pj_ioqueue_unregister(key[i]); + if (rc != PJ_SUCCESS) { + app_perror("...error in pj_ioqueue_unregister", rc); + } + } + + rc = pj_ioqueue_destroy(ioqueue); + if (rc != PJ_SUCCESS) { + app_perror("...error in pj_ioqueue_destroy", rc); + } + + pj_pool_release(pool); + + PJ_LOG(3,(THIS_FILE,"....many_handles_test() ok")); + + return 0; +} + +/* + * Multi-operation test. + */ + +/* + * Benchmarking IOQueue + */ +static int bench_test(pj_bool_t allow_concur, int bufsize, + int inactive_sock_count) +{ + pj_sock_t ssock=-1, csock=-1; + pj_sockaddr_in addr; + pj_pool_t *pool = NULL; + pj_sock_t *inactive_sock=NULL; + pj_ioqueue_op_key_t *inactive_read_op; + char *send_buf, *recv_buf; + pj_ioqueue_t *ioque = NULL; + pj_ioqueue_key_t *skey, *ckey, *keys[SOCK_INACTIVE_MAX+2]; + pj_timestamp t1, t2, t_elapsed; + int rc=0, i; /* i must be signed */ + pj_str_t temp; + char errbuf[PJ_ERR_MSG_SIZE]; + + TRACE__((THIS_FILE, " bench test %d", inactive_sock_count)); + + // Create pool. + pool = pj_pool_create(mem, NULL, POOL_SIZE, 4000, NULL); + + // Allocate buffers for send and receive. + send_buf = (char*)pj_pool_alloc(pool, bufsize); + recv_buf = (char*)pj_pool_alloc(pool, bufsize); + + // Allocate sockets for sending and receiving. + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &ssock); + if (rc == PJ_SUCCESS) { + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &csock); + } else + csock = PJ_INVALID_SOCKET; + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_sock_socket()", rc); + goto on_error; + } + + // Bind server socket. + pj_bzero(&addr, sizeof(addr)); + addr.sin_family = pj_AF_INET(); + addr.sin_port = pj_htons(PORT); + if (pj_sock_bind(ssock, &addr, sizeof(addr))) + goto on_error; + + pj_assert(inactive_sock_count+2 <= PJ_IOQUEUE_MAX_HANDLES); + + // Create I/O Queue. + rc = pj_ioqueue_create(pool, PJ_IOQUEUE_MAX_HANDLES, &ioque); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_ioqueue_create()", rc); + goto on_error; + } + + // Set concurrency + rc = pj_ioqueue_set_default_concurrency(ioque, allow_concur); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_ioqueue_set_default_concurrency()", rc); + goto on_error; + } + + // Allocate inactive sockets, and bind them to some arbitrary address. + // Then register them to the I/O queue, and start a read operation. + inactive_sock = (pj_sock_t*)pj_pool_alloc(pool, + inactive_sock_count*sizeof(pj_sock_t)); + inactive_read_op = (pj_ioqueue_op_key_t*)pj_pool_alloc(pool, + inactive_sock_count*sizeof(pj_ioqueue_op_key_t)); + pj_bzero(&addr, sizeof(addr)); + addr.sin_family = pj_AF_INET(); + for (i=0; i<inactive_sock_count; ++i) { + pj_ssize_t bytes; + + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &inactive_sock[i]); + if (rc != PJ_SUCCESS || inactive_sock[i] < 0) { + app_perror("...error: pj_sock_socket()", rc); + goto on_error; + } + if ((rc=pj_sock_bind(inactive_sock[i], &addr, sizeof(addr))) != 0) { + pj_sock_close(inactive_sock[i]); + inactive_sock[i] = PJ_INVALID_SOCKET; + app_perror("...error: pj_sock_bind()", rc); + goto on_error; + } + rc = pj_ioqueue_register_sock(pool, ioque, inactive_sock[i], + NULL, &test_cb, &keys[i]); + if (rc != PJ_SUCCESS) { + pj_sock_close(inactive_sock[i]); + inactive_sock[i] = PJ_INVALID_SOCKET; + app_perror("...error(1): pj_ioqueue_register_sock()", rc); + PJ_LOG(3,(THIS_FILE, "....i=%d", i)); + goto on_error; + } + bytes = bufsize; + rc = pj_ioqueue_recv(keys[i], &inactive_read_op[i], recv_buf, &bytes, 0); + if (rc != PJ_EPENDING) { + pj_sock_close(inactive_sock[i]); + inactive_sock[i] = PJ_INVALID_SOCKET; + app_perror("...error: pj_ioqueue_read()", rc); + goto on_error; + } + } + + // Register server and client socket. + // We put this after inactivity socket, hopefully this can represent the + // worst waiting time. + rc = pj_ioqueue_register_sock(pool, ioque, ssock, NULL, + &test_cb, &skey); + if (rc != PJ_SUCCESS) { + app_perror("...error(2): pj_ioqueue_register_sock()", rc); + goto on_error; + } + + rc = pj_ioqueue_register_sock(pool, ioque, csock, NULL, + &test_cb, &ckey); + if (rc != PJ_SUCCESS) { + app_perror("...error(3): pj_ioqueue_register_sock()", rc); + goto on_error; + } + + // Set destination address to send the packet. + pj_sockaddr_in_init(&addr, pj_cstr(&temp, "127.0.0.1"), PORT); + + // Test loop. + t_elapsed.u64 = 0; + for (i=0; i<LOOP; ++i) { + pj_ssize_t bytes; + pj_ioqueue_op_key_t read_op, write_op; + + // Randomize send buffer. + pj_create_random_string(send_buf, bufsize); + + // Start reading on the server side. + bytes = bufsize; + rc = pj_ioqueue_recv(skey, &read_op, recv_buf, &bytes, 0); + if (rc != PJ_EPENDING) { + app_perror("...error: pj_ioqueue_read()", rc); + break; + } + + // Starts send on the client side. + bytes = bufsize; + rc = pj_ioqueue_sendto(ckey, &write_op, send_buf, &bytes, 0, + &addr, sizeof(addr)); + if (rc != PJ_SUCCESS && rc != PJ_EPENDING) { + app_perror("...error: pj_ioqueue_write()", rc); + break; + } + if (rc == PJ_SUCCESS) { + if (bytes < 0) { + app_perror("...error: pj_ioqueue_sendto()", -bytes); + break; + } + } + + // Begin time. + pj_get_timestamp(&t1); + + // Poll the queue until we've got completion event in the server side. + callback_read_key = NULL; + callback_read_size = 0; + TRACE__((THIS_FILE, " waiting for key = %p", skey)); + do { + pj_time_val timeout = { 1, 0 }; +#ifdef PJ_SYMBIAN + rc = pj_symbianos_poll(-1, 1000); +#else + rc = pj_ioqueue_poll(ioque, &timeout); +#endif + TRACE__((THIS_FILE, " poll rc=%d", rc)); + } while (rc >= 0 && callback_read_key != skey); + + // End time. + pj_get_timestamp(&t2); + t_elapsed.u64 += (t2.u64 - t1.u64); + + if (rc < 0) { + app_perror(" error: pj_ioqueue_poll", -rc); + break; + } + + // Compare recv buffer with send buffer. + if (callback_read_size != bufsize || + pj_memcmp(send_buf, recv_buf, bufsize)) + { + rc = -10; + PJ_LOG(3,(THIS_FILE, " error: size/buffer mismatch")); + break; + } + + // Poll until all events are exhausted, before we start the next loop. + do { + pj_time_val timeout = { 0, 10 }; +#ifdef PJ_SYMBIAN + rc = pj_symbianos_poll(-1, 100); +#else + rc = pj_ioqueue_poll(ioque, &timeout); +#endif + } while (rc>0); + + rc = 0; + } + + // Print results + if (rc == 0) { + pj_timestamp tzero; + pj_uint32_t usec_delay; + + tzero.u32.hi = tzero.u32.lo = 0; + usec_delay = pj_elapsed_usec( &tzero, &t_elapsed); + + PJ_LOG(3, (THIS_FILE, "...%10d %15d % 9d", + bufsize, inactive_sock_count, usec_delay)); + + } else { + PJ_LOG(2, (THIS_FILE, "...ERROR rc=%d (buf:%d, fds:%d)", + rc, bufsize, inactive_sock_count+2)); + } + + // Cleaning up. + for (i=inactive_sock_count-1; i>=0; --i) { + pj_ioqueue_unregister(keys[i]); + } + + pj_ioqueue_unregister(skey); + pj_ioqueue_unregister(ckey); + + + pj_ioqueue_destroy(ioque); + pj_pool_release( pool); + return rc; + +on_error: + PJ_LOG(1,(THIS_FILE, "...ERROR: %s", + pj_strerror(pj_get_netos_error(), errbuf, sizeof(errbuf)))); + if (ssock) + pj_sock_close(ssock); + if (csock) + pj_sock_close(csock); + for (i=0; i<inactive_sock_count && inactive_sock && + inactive_sock[i]!=PJ_INVALID_SOCKET; ++i) + { + pj_sock_close(inactive_sock[i]); + } + if (ioque != NULL) + pj_ioqueue_destroy(ioque); + pj_pool_release( pool); + return -1; +} + +static int udp_ioqueue_test_imp(pj_bool_t allow_concur) +{ + int status; + int bufsize, sock_count; + + PJ_LOG(3,(THIS_FILE, "..testing with concurency=%d", allow_concur)); + + //goto pass1; + + PJ_LOG(3, (THIS_FILE, "...compliance test (%s)", pj_ioqueue_name())); + if ((status=compliance_test(allow_concur)) != 0) { + return status; + } + PJ_LOG(3, (THIS_FILE, "....compliance test ok")); + + + PJ_LOG(3, (THIS_FILE, "...unregister test (%s)", pj_ioqueue_name())); + if ((status=unregister_test(allow_concur)) != 0) { + return status; + } + PJ_LOG(3, (THIS_FILE, "....unregister test ok")); + + if ((status=many_handles_test(allow_concur)) != 0) { + return status; + } + + //return 0; + + PJ_LOG(4, (THIS_FILE, "...benchmarking different buffer size:")); + PJ_LOG(4, (THIS_FILE, "... note: buf=bytes sent, fds=# of fds, " + "elapsed=in timer ticks")); + +//pass1: + PJ_LOG(3, (THIS_FILE, "...Benchmarking poll times for %s:", pj_ioqueue_name())); + PJ_LOG(3, (THIS_FILE, "...=====================================")); + PJ_LOG(3, (THIS_FILE, "...Buf.size #inactive-socks Time/poll")); + PJ_LOG(3, (THIS_FILE, "... (bytes) (nanosec)")); + PJ_LOG(3, (THIS_FILE, "...=====================================")); + + //goto pass2; + + for (bufsize=BUF_MIN_SIZE; bufsize <= BUF_MAX_SIZE; bufsize *= 2) { + if ((status=bench_test(allow_concur, bufsize, SOCK_INACTIVE_MIN)) != 0) + return status; + } +//pass2: + bufsize = 512; + for (sock_count=SOCK_INACTIVE_MIN+2; + sock_count<=SOCK_INACTIVE_MAX+2; + sock_count *= 2) + { + //PJ_LOG(3,(THIS_FILE, "...testing with %d fds", sock_count)); + if ((status=bench_test(allow_concur, bufsize, sock_count-2)) != 0) + return status; + } + return 0; +} + +int udp_ioqueue_test() +{ + int rc; + + rc = udp_ioqueue_test_imp(PJ_TRUE); + if (rc != 0) + return rc; + + rc = udp_ioqueue_test_imp(PJ_FALSE); + if (rc != 0) + return rc; + + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_uiq_udp; +#endif /* INCLUDE_UDP_IOQUEUE_TEST */ + + diff --git a/pjlib/src/pjlib-test/ioq_unreg.c b/pjlib/src/pjlib-test/ioq_unreg.c new file mode 100644 index 0000000..c968e4f --- /dev/null +++ b/pjlib/src/pjlib-test/ioq_unreg.c @@ -0,0 +1,387 @@ +/* $Id: ioq_unreg.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_IOQUEUE_UNREG_TEST +/* + * This tests the thread safety of ioqueue unregistration operation. + */ + +#include <pj/errno.h> +#include <pj/ioqueue.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/sock.h> +#include <pj/compat/socket.h> +#include <pj/string.h> + + +#define THIS_FILE "ioq_unreg.c" + + +enum test_method +{ + UNREGISTER_IN_APP, + UNREGISTER_IN_CALLBACK, +}; + +static int thread_quitting; +static enum test_method test_method; +static pj_time_val time_to_unregister; + +struct sock_data +{ + pj_sock_t sock; + pj_sock_t csock; + pj_pool_t *pool; + pj_ioqueue_key_t *key; + pj_mutex_t *mutex; + pj_ioqueue_op_key_t *op_key; + char *buffer; + pj_size_t bufsize; + pj_bool_t unregistered; + unsigned received; +} sock_data; + +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_read) +{ + pj_ssize_t size; + char *sendbuf = "Hello world"; + pj_status_t status; + + if (sock_data.unregistered) + return; + + pj_mutex_lock(sock_data.mutex); + + if (sock_data.unregistered) { + pj_mutex_unlock(sock_data.mutex); + return; + } + + if (bytes_read < 0) { + if (-bytes_read != PJ_STATUS_FROM_OS(PJ_BLOCKING_ERROR_VAL)) + app_perror("ioqueue reported recv error", -bytes_read); + } else { + sock_data.received += bytes_read; + } + + if (test_method == UNREGISTER_IN_CALLBACK) { + pj_time_val now; + + pj_gettimeofday(&now); + if (PJ_TIME_VAL_GTE(now, time_to_unregister)) { + sock_data.unregistered = 1; + pj_ioqueue_unregister(key); + pj_mutex_unlock(sock_data.mutex); + return; + } + } + + do { + size = sock_data.bufsize; + status = pj_ioqueue_recv(key, op_key, sock_data.buffer, &size, 0); + if (status != PJ_EPENDING && status != PJ_SUCCESS) + app_perror("recv() error", status); + + } while (status == PJ_SUCCESS); + + pj_mutex_unlock(sock_data.mutex); + + size = pj_ansi_strlen(sendbuf); + status = pj_sock_send(sock_data.csock, sendbuf, &size, 0); + if (status != PJ_SUCCESS) + app_perror("send() error", status); + + size = pj_ansi_strlen(sendbuf); + status = pj_sock_send(sock_data.csock, sendbuf, &size, 0); + if (status != PJ_SUCCESS) + app_perror("send() error", status); + +} + +static int worker_thread(void *arg) +{ + pj_ioqueue_t *ioqueue = (pj_ioqueue_t*) arg; + + while (!thread_quitting) { + pj_time_val timeout = { 0, 20 }; + pj_ioqueue_poll(ioqueue, &timeout); + } + + return 0; +} + +/* + * Perform unregistration test. + * + * This will create ioqueue and register a server socket. Depending + * on the test method, either the callback or the main thread will + * unregister and destroy the server socket after some period of time. + */ +static int perform_unreg_test(pj_ioqueue_t *ioqueue, + pj_pool_t *test_pool, + const char *title, + pj_bool_t other_socket) +{ + enum { WORKER_CNT = 1, MSEC = 500, QUIT_MSEC = 500 }; + int i; + pj_thread_t *thread[WORKER_CNT]; + struct sock_data osd; + pj_ioqueue_callback callback; + pj_time_val end_time; + pj_status_t status; + + + /* Sometimes its important to have other sockets registered to + * the ioqueue, because when no sockets are registered, the ioqueue + * will return from the poll early. + */ + if (other_socket) { + status = app_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, 56127, &osd.sock); + if (status != PJ_SUCCESS) { + app_perror("Error creating other socket", status); + return -12; + } + + pj_bzero(&callback, sizeof(callback)); + status = pj_ioqueue_register_sock(test_pool, ioqueue, osd.sock, + NULL, &callback, &osd.key); + if (status != PJ_SUCCESS) { + app_perror("Error registering other socket", status); + return -13; + } + + } else { + osd.key = NULL; + osd.sock = PJ_INVALID_SOCKET; + } + + /* Init both time duration of testing */ + thread_quitting = 0; + pj_gettimeofday(&time_to_unregister); + time_to_unregister.msec += MSEC; + pj_time_val_normalize(&time_to_unregister); + + end_time = time_to_unregister; + end_time.msec += QUIT_MSEC; + pj_time_val_normalize(&end_time); + + + /* Create polling thread */ + for (i=0; i<WORKER_CNT; ++i) { + status = pj_thread_create(test_pool, "unregtest", &worker_thread, + ioqueue, 0, 0, &thread[i]); + if (status != PJ_SUCCESS) { + app_perror("Error creating thread", status); + return -20; + } + } + + /* Create pair of client/server sockets */ + status = app_socketpair(pj_AF_INET(), pj_SOCK_DGRAM(), 0, + &sock_data.sock, &sock_data.csock); + if (status != PJ_SUCCESS) { + app_perror("app_socketpair error", status); + return -30; + } + + + /* Initialize test data */ + sock_data.pool = pj_pool_create(mem, "sd", 1000, 1000, NULL); + sock_data.buffer = (char*) pj_pool_alloc(sock_data.pool, 128); + sock_data.bufsize = 128; + sock_data.op_key = (pj_ioqueue_op_key_t*) + pj_pool_alloc(sock_data.pool, + sizeof(*sock_data.op_key)); + sock_data.received = 0; + sock_data.unregistered = 0; + + pj_ioqueue_op_key_init(sock_data.op_key, sizeof(*sock_data.op_key)); + + status = pj_mutex_create_simple(sock_data.pool, "sd", &sock_data.mutex); + if (status != PJ_SUCCESS) { + app_perror("create_mutex() error", status); + return -35; + } + + /* Register socket to ioqueue */ + pj_bzero(&callback, sizeof(callback)); + callback.on_read_complete = &on_read_complete; + status = pj_ioqueue_register_sock(sock_data.pool, ioqueue, sock_data.sock, + NULL, &callback, &sock_data.key); + if (status != PJ_SUCCESS) { + app_perror("pj_ioqueue_register error", status); + return -40; + } + + /* Bootstrap the first send/receive */ + on_read_complete(sock_data.key, sock_data.op_key, 0); + + /* Loop until test time ends */ + for (;;) { + pj_time_val now, timeout; + int n; + + pj_gettimeofday(&now); + + if (test_method == UNREGISTER_IN_APP && + PJ_TIME_VAL_GTE(now, time_to_unregister) && + !sock_data.unregistered) + { + sock_data.unregistered = 1; + /* Wait (as much as possible) for callback to complete */ + pj_mutex_lock(sock_data.mutex); + pj_mutex_unlock(sock_data.mutex); + pj_ioqueue_unregister(sock_data.key); + } + + if (PJ_TIME_VAL_GT(now, end_time) && sock_data.unregistered) + break; + + timeout.sec = 0; timeout.msec = 10; + n = pj_ioqueue_poll(ioqueue, &timeout); + if (n < 0) { + app_perror("pj_ioqueue_poll error", -n); + pj_thread_sleep(1); + } + } + + thread_quitting = 1; + + for (i=0; i<WORKER_CNT; ++i) { + pj_thread_join(thread[i]); + pj_thread_destroy(thread[i]); + } + + /* Destroy data */ + pj_mutex_destroy(sock_data.mutex); + pj_pool_release(sock_data.pool); + sock_data.pool = NULL; + + if (other_socket) { + pj_ioqueue_unregister(osd.key); + } + + pj_sock_close(sock_data.csock); + + PJ_LOG(3,(THIS_FILE, "....%s: done (%d KB/s)", + title, sock_data.received * 1000 / MSEC / 1000)); + return 0; +} + +static int udp_ioqueue_unreg_test_imp(pj_bool_t allow_concur) +{ + enum { LOOP = 10 }; + int i, rc; + char title[30]; + pj_ioqueue_t *ioqueue; + pj_pool_t *test_pool; + + PJ_LOG(3,(THIS_FILE, "..testing with concurency=%d", allow_concur)); + + test_method = UNREGISTER_IN_APP; + + test_pool = pj_pool_create(mem, "unregtest", 4000, 4000, NULL); + + rc = pj_ioqueue_create(test_pool, 16, &ioqueue); + if (rc != PJ_SUCCESS) { + app_perror("Error creating ioqueue", rc); + return -10; + } + + rc = pj_ioqueue_set_default_concurrency(ioqueue, allow_concur); + if (rc != PJ_SUCCESS) { + app_perror("Error in pj_ioqueue_set_default_concurrency()", rc); + return -12; + } + + PJ_LOG(3, (THIS_FILE, "...ioqueue unregister stress test 0/3, unregister in app (%s)", + pj_ioqueue_name())); + for (i=0; i<LOOP; ++i) { + pj_ansi_sprintf(title, "repeat %d/%d", i, LOOP); + rc = perform_unreg_test(ioqueue, test_pool, title, 0); + if (rc != 0) + return rc; + } + + + PJ_LOG(3, (THIS_FILE, "...ioqueue unregister stress test 1/3, unregister in app (%s)", + pj_ioqueue_name())); + for (i=0; i<LOOP; ++i) { + pj_ansi_sprintf(title, "repeat %d/%d", i, LOOP); + rc = perform_unreg_test(ioqueue, test_pool, title, 1); + if (rc != 0) + return rc; + } + + test_method = UNREGISTER_IN_CALLBACK; + + PJ_LOG(3, (THIS_FILE, "...ioqueue unregister stress test 2/3, unregister in cb (%s)", + pj_ioqueue_name())); + for (i=0; i<LOOP; ++i) { + pj_ansi_sprintf(title, "repeat %d/%d", i, LOOP); + rc = perform_unreg_test(ioqueue, test_pool, title, 0); + if (rc != 0) + return rc; + } + + + PJ_LOG(3, (THIS_FILE, "...ioqueue unregister stress test 3/3, unregister in cb (%s)", + pj_ioqueue_name())); + for (i=0; i<LOOP; ++i) { + pj_ansi_sprintf(title, "repeat %d/%d", i, LOOP); + rc = perform_unreg_test(ioqueue, test_pool, title, 1); + if (rc != 0) + return rc; + } + + pj_ioqueue_destroy(ioqueue); + pj_pool_release(test_pool); + + return 0; +} + +int udp_ioqueue_unreg_test(void) +{ + int rc; + + rc = udp_ioqueue_unreg_test_imp(PJ_TRUE); + if (rc != 0) + return rc; + + rc = udp_ioqueue_unreg_test_imp(PJ_FALSE); + if (rc != 0) + return rc; + + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_uiq_unreg; +#endif /* INCLUDE_IOQUEUE_UNREG_TEST */ + + diff --git a/pjlib/src/pjlib-test/list.c b/pjlib/src/pjlib-test/list.c new file mode 100644 index 0000000..6472857 --- /dev/null +++ b/pjlib/src/pjlib-test/list.c @@ -0,0 +1,226 @@ +/* $Id: list.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" + +/** + * \page page_pjlib_list_test Test: Linked List + * + * This file provides implementation of \b list_test(). It tests the + * functionality of the linked-list API. + * + * \section list_test_sec Scope of the Test + * + * API tested: + * - pj_list_init() + * - pj_list_insert_before() + * - pj_list_insert_after() + * - pj_list_merge_last() + * - pj_list_empty() + * - pj_list_insert_nodes_before() + * - pj_list_erase() + * - pj_list_find_node() + * - pj_list_search() + * + * + * This file is <b>pjlib-test/list.c</b> + * + * \include pjlib-test/list.c + */ + +#if INCLUDE_LIST_TEST + +#include <pjlib.h> + +typedef struct list_node +{ + PJ_DECL_LIST_MEMBER(struct list_node); + int value; +} list_node; + +static int compare_node(void *value, const pj_list_type *nd) +{ + list_node *node = (list_node*)nd; + return ((long)value == node->value) ? 0 : -1; +} + +#define PJ_SIGNED_ARRAY_SIZE(a) ((int)PJ_ARRAY_SIZE(a)) + +int list_test() +{ + list_node nodes[4]; // must be even number of nodes + list_node list; + list_node list2; + list_node *p; + int i; // don't change to unsigned! + + // + // Test insert_before(). + // + list.value = (unsigned)-1; + pj_list_init(&list); + for (i=0; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i) { + nodes[i].value = i; + pj_list_insert_before(&list, &nodes[i]); + } + // check. + for (i=0, p=list.next; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i, p=p->next) { + pj_assert(p->value == i); + if (p->value != i) { + return -1; + } + } + + // + // Test insert_after() + // + pj_list_init(&list); + for (i=PJ_SIGNED_ARRAY_SIZE(nodes)-1; i>=0; --i) { + pj_list_insert_after(&list, &nodes[i]); + } + // check. + for (i=0, p=list.next; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i, p=p->next) { + pj_assert(p->value == i); + if (p->value != i) { + return -1; + } + } + + // + // Test merge_last() + // + // Init lists + pj_list_init(&list); + pj_list_init(&list2); + for (i=0; i<PJ_SIGNED_ARRAY_SIZE(nodes)/2; ++i) { + pj_list_insert_before(&list, &nodes[i]); + } + for (i=PJ_SIGNED_ARRAY_SIZE(nodes)/2; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i) { + pj_list_insert_before(&list2, &nodes[i]); + } + // merge + pj_list_merge_last(&list, &list2); + // check. + for (i=0, p=list.next; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i, p=p->next) { + pj_assert(p->value == i); + if (p->value != i) { + return -1; + } + } + // check list is empty + pj_assert( pj_list_empty(&list2) ); + if (!pj_list_empty(&list2)) { + return -1; + } + + // + // Check merge_first() + // + pj_list_init(&list); + pj_list_init(&list2); + for (i=0; i<PJ_SIGNED_ARRAY_SIZE(nodes)/2; ++i) { + pj_list_insert_before(&list, &nodes[i]); + } + for (i=PJ_SIGNED_ARRAY_SIZE(nodes)/2; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i) { + pj_list_insert_before(&list2, &nodes[i]); + } + // merge + pj_list_merge_first(&list2, &list); + // check (list2). + for (i=0, p=list2.next; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i, p=p->next) { + pj_assert(p->value == i); + if (p->value != i) { + return -1; + } + } + // check list is empty + pj_assert( pj_list_empty(&list) ); + if (!pj_list_empty(&list)) { + return -1; + } + + // + // Test insert_nodes_before() + // + // init list + pj_list_init(&list); + for (i=0; i<PJ_SIGNED_ARRAY_SIZE(nodes)/2; ++i) { + pj_list_insert_before(&list, &nodes[i]); + } + // chain remaining nodes + pj_list_init(&nodes[PJ_SIGNED_ARRAY_SIZE(nodes)/2]); + for (i=PJ_SIGNED_ARRAY_SIZE(nodes)/2+1; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i) { + pj_list_insert_before(&nodes[PJ_SIGNED_ARRAY_SIZE(nodes)/2], &nodes[i]); + } + // insert nodes + pj_list_insert_nodes_before(&list, &nodes[PJ_SIGNED_ARRAY_SIZE(nodes)/2]); + // check + for (i=0, p=list.next; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i, p=p->next) { + pj_assert(p->value == i); + if (p->value != i) { + return -1; + } + } + + // erase test. + pj_list_init(&list); + for (i=0; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i) { + nodes[i].value = i; + pj_list_insert_before(&list, &nodes[i]); + } + for (i=PJ_SIGNED_ARRAY_SIZE(nodes)-1; i>=0; --i) { + int j; + pj_list_erase(&nodes[i]); + for (j=0, p=list.next; j<i; ++j, p=p->next) { + pj_assert(p->value == j); + if (p->value != j) { + return -1; + } + } + } + + // find and search + pj_list_init(&list); + for (i=0; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i) { + nodes[i].value = i; + pj_list_insert_before(&list, &nodes[i]); + } + for (i=0; i<PJ_SIGNED_ARRAY_SIZE(nodes); ++i) { + p = (list_node*) pj_list_find_node(&list, &nodes[i]); + pj_assert( p == &nodes[i] ); + if (p != &nodes[i]) { + return -1; + } + p = (list_node*) pj_list_search(&list, (void*)(long)i, &compare_node); + pj_assert( p == &nodes[i] ); + if (p != &nodes[i]) { + return -1; + } + } + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_list_test; +#endif /* INCLUDE_LIST_TEST */ + + diff --git a/pjlib/src/pjlib-test/main.c b/pjlib/src/pjlib-test/main.c new file mode 100644 index 0000000..e0241b6 --- /dev/null +++ b/pjlib/src/pjlib-test/main.c @@ -0,0 +1,107 @@ +/* $Id: main.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 <pj/string.h> +#include <pj/sock.h> +#include <pj/log.h> +#include <stdio.h> + +extern int param_echo_sock_type; +extern const char *param_echo_server; +extern int param_echo_port; + + +//#if defined(PJ_WIN32) && PJ_WIN32!=0 +#if 0 +#include <windows.h> +static void boost(void) +{ + SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS); +} +#else +#define boost() +#endif + +#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 + +int main(int argc, char *argv[]) +{ + int rc; + int interractive = 0; + + boost(); + init_signals(); + + while (argc > 1) { + char *arg = argv[--argc]; + + if (*arg=='-' && *(arg+1)=='i') { + interractive = 1; + + } else if (*arg=='-' && *(arg+1)=='p') { + pj_str_t port = pj_str(argv[--argc]); + + param_echo_port = pj_strtoul(&port); + + } else if (*arg=='-' && *(arg+1)=='s') { + param_echo_server = argv[--argc]; + + } else if (*arg=='-' && *(arg+1)=='t') { + pj_str_t type = pj_str(argv[--argc]); + + if (pj_stricmp2(&type, "tcp")==0) + param_echo_sock_type = pj_SOCK_STREAM(); + else if (pj_stricmp2(&type, "udp")==0) + param_echo_sock_type = pj_SOCK_DGRAM(); + else { + PJ_LOG(3,("", "error: unknown socket type %s", type.ptr)); + return 1; + } + } + } + + rc = test_main(); + + if (interractive) { + char s[10]; + puts(""); + puts("Press <ENTER> to exit"); + if (!fgets(s, sizeof(s), stdin)) + return rc; + } + + return rc; +} + diff --git a/pjlib/src/pjlib-test/main_mod.c b/pjlib/src/pjlib-test/main_mod.c new file mode 100644 index 0000000..bb52a6f --- /dev/null +++ b/pjlib/src/pjlib-test/main_mod.c @@ -0,0 +1,40 @@ +/* $Id: main_mod.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 <linux/module.h> +#include <linux/kernel.h> + +int init_module(void) +{ + printk(KERN_INFO "PJLIB test module loaded. Starting tests...\n"); + + test_main(); + + /* Prevent module from loading. We've finished test anyway.. */ + return 1; +} + +void cleanup_module(void) +{ + printk(KERN_INFO "PJLIB test module unloading...\n"); +} + +MODULE_LICENSE("GPL"); + diff --git a/pjlib/src/pjlib-test/main_rtems.c b/pjlib/src/pjlib-test/main_rtems.c new file mode 100644 index 0000000..b2c8d9b --- /dev/null +++ b/pjlib/src/pjlib-test/main_rtems.c @@ -0,0 +1,326 @@ +/* $Id: main_rtems.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 + */ + +/* + * - Many thanks for Zetron, Inc. and Phil Torre <ptorre@zetron.com> for + * donating this file and the RTEMS port in general! + */ + +#include "test.h" + +#include <pj/errno.h> +#include <pj/string.h> +#include <pj/sock.h> +#include <pj/log.h> + +extern int param_echo_sock_type; +extern const char *param_echo_server; +extern int param_echo_port; + +#include <bsp.h> + +#define CONFIGURE_USE_IMFS_AS_BASE_FILESYSTEM +#define CONFIGURE_LIBIO_MAXIMUM_FILE_DESCRIPTORS 300 +#define CONFIGURE_MAXIMUM_TASKS 50 +#define CONFIGURE_MAXIMUM_MESSAGE_QUEUES rtems_resource_unlimited(10) +#define CONFIGURE_MAXIMUM_SEMAPHORES rtems_resource_unlimited(10) +#define CONFIGURE_MAXIMUM_TIMERS 50 +#define CONFIGURE_MAXIMUM_REGIONS 3 +#define CONFIGURE_APPLICATION_NEEDS_CONSOLE_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER +#define CONFIGURE_APPLICATION_NEEDS_TIMER_DRIVER +#define CONFIGURE_TICKS_PER_TIMESLICE 2 +//#define CONFIGURE_RTEMS_INIT_TASKS_TABLE +#define CONFIGURE_POSIX_INIT_THREAD_TABLE + + +#define CONFIGURE_MAXIMUM_POSIX_MUTEXES rtems_resource_unlimited(16) +#define CONFIGURE_MAXIMUM_POSIX_CONDITION_VARIABLES rtems_resource_unlimited(5) +#define CONFIGURE_MAXIMUM_POSIX_SEMAPHORES rtems_resource_unlimited(16) +#define CONFIGURE_MAXIMUM_POSIX_TIMERS rtems_resource_unlimited(5) +#define CONFIGURE_MAXIMUM_POSIX_THREADS rtems_resource_unlimited(16) +#define CONFIGURE_MAXIMUM_POSIX_KEYS rtems_resource_unlimited(16) + +#define CONFIGURE_POSIX_INIT_THREAD_STACK_SIZE 4096 + +/* Make sure that stack size is at least 4096 */ +#define SZ (4096-RTEMS_MINIMUM_STACK_SIZE) +#define CONFIGURE_EXTRA_TASK_STACKS ((SZ)<0 ? 0 : (SZ)) + +#define CONFIGURE_INIT +#define STACK_CHECKER_ON + +rtems_task Init(rtems_task_argument Argument) ; +void *POSIX_Init(void *argument); + +#include <confdefs.h> +#include <rtems.h> + +/* Any tests that want to build a linked executable for RTEMS must include + these headers to get a default config for the network stack. */ +#include <rtems/rtems_bsdnet.h> +#include "rtems_network_config.h" + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define THIS_FILE "main_rtems.c" + +static void* pjlib_test_main(void* unused); +static void initialize_network(); +static void test_sock(void); + +static void my_perror(pj_status_t status, const char *title) +{ + char err[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, err, sizeof(err)); + printf("%s: %s [%d]\n", title, err, status); +} + +#define TEST(expr) { int rc;\ + /*PJ_LOG(3,(THIS_FILE,"%s", #expr));*/ \ + /*sleep(1);*/ \ + rc=expr; \ + if (rc) my_perror(PJ_STATUS_FROM_OS(rc),#expr); } + + + +//rtems_task Init(rtems_task_argument Argument) +void *POSIX_Init(void *argument) +{ + pthread_attr_t threadAttr; + pthread_t theThread; + struct sched_param sched_param; + size_t stack_size; + int result; + char data[1000]; + + + memset(data, 1, sizeof(data)); + + /* Set the TOD clock, so that gettimeofday() will work */ + rtems_time_of_day fakeTime = { 2006, 3, 15, 17, 30, 0, 0 }; + + if (RTEMS_SUCCESSFUL != rtems_clock_set(&fakeTime)) + { + assert(0); + } + + /* Bring up the network stack so we can run the socket tests. */ + initialize_network(); + + /* Start a POSIX thread for pjlib_test_main(), since that's what it + * thinks it is running in. + */ + + /* Initialize attribute */ + TEST( pthread_attr_init(&threadAttr) ); + + /* Looks like the rest of the attributes must be fully initialized too, + * or otherwise pthread_create will return EINVAL. + */ + + /* Specify explicit scheduling request */ + TEST( pthread_attr_setinheritsched(&threadAttr, PTHREAD_EXPLICIT_SCHED)); + + /* Timeslicing is needed by thread test, and this is accomplished by + * SCHED_RR. + */ + TEST( pthread_attr_setschedpolicy(&threadAttr, SCHED_RR)); + + /* Set priority */ + TEST( pthread_attr_getschedparam(&threadAttr, &sched_param)); + sched_param.sched_priority = NETWORK_STACK_PRIORITY - 10; + TEST( pthread_attr_setschedparam(&threadAttr, &sched_param)); + + /* Must have sufficient stack size (large size is needed by + * logger, because default settings for logger is to use message buffer + * from the stack). + */ + TEST( pthread_attr_getstacksize(&threadAttr, &stack_size)); + if (stack_size < 8192) + TEST( pthread_attr_setstacksize(&threadAttr, 8192)); + + + /* Create the thread for application */ + result = pthread_create(&theThread, &threadAttr, &pjlib_test_main, NULL); + if (result != 0) { + my_perror(PJ_STATUS_FROM_OS(result), + "Error creating pjlib_test_main thread"); + assert(!"Error creating main thread"); + } + + return NULL; +} + + + +#define boost() +#define init_signals() + +static void* +pjlib_test_main(void* unused) +{ + int rc; + + /* Drop our priority to below that of the network stack, otherwise + * select() tests will fail. */ + struct sched_param schedParam; + int schedPolicy; + + printf("pjlib_test_main thread started..\n"); + + TEST( pthread_getschedparam(pthread_self(), &schedPolicy, &schedParam) ); + + schedParam.sched_priority = NETWORK_STACK_PRIORITY - 10; + + TEST( pthread_setschedparam(pthread_self(), schedPolicy, &schedParam) ); + + boost(); + init_signals(); + + //my_test_thread("from pjlib_test_main"); + //test_sock(); + + rc = test_main(); + + return (void*)rc; +} + +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <unistd.h> + +/* + * Send UDP packet to some host. We can then use Ethereal to sniff the packet + * to see if this target really transmits UDP packet. + */ +static void +send_udp(const char *target) +{ + int sock, rc; + struct sockaddr_in addr; + + PJ_LOG(3,("main_rtems.c", "IP addr=%s/%s, gw=%s", + DEFAULT_IP_ADDRESS_STRING, + DEFAULT_NETMASK_STRING, + DEFAULT_GATEWAY_STRING)); + + sock = socket(AF_INET, SOCK_DGRAM, 0); + assert(sock > 0); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + + rc = bind(sock, (struct sockaddr*)&addr, sizeof(addr)); + assert("bind error" && rc==0); + + addr.sin_addr.s_addr = inet_addr(target); + addr.sin_port = htons(4444); + + while(1) { + const char *data = "hello"; + + rc = sendto(sock, data, 5, 0, (struct sockaddr*)&addr, sizeof(addr)); + PJ_LOG(3,("main_rtems.c", "pinging %s..(rc=%d)", target, rc)); + sleep(1); + } +} + + +static void test_sock(void) +{ + int sock; + struct sockaddr_in addr; + int rc; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + printf("socket() error\n"); + goto end; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(5000); + + rc = bind(sock, (struct sockaddr*)&addr, sizeof(addr)); + if (rc != 0) { + printf("bind() error %d\n", rc); + close(sock); + goto end; + } + + puts("Bind socket success"); + + close(sock); + +end: + while(1) sleep(1); +} + +/* + * Initialize the network stack and Ethernet driver, using the configuration + * in rtems-network-config.h + */ +static void +initialize_network() +{ + unsigned32 fd, result; + char ip_address_string[] = DEFAULT_IP_ADDRESS_STRING; + char netmask_string[] = DEFAULT_NETMASK_STRING; + char gateway_string[] = DEFAULT_GATEWAY_STRING; + + // Write the network config files to /etc/hosts and /etc/host.conf + result = mkdir("/etc", S_IRWXU | S_IRWXG | S_IRWXO); + fd = open("/etc/host.conf", O_RDWR | O_CREAT, 0744); + result = write(fd, "hosts,bind\n", 11); + result = close(fd); + fd = open("/etc/hosts", O_RDWR | O_CREAT, 0744); + result = write(fd, "127.0.0.1 localhost\n", 41); + result = write(fd, ip_address_string, strlen(ip_address_string)); + result = write(fd, " pjsip-test\n", 32); + result = close(fd); + + netdriver_config.ip_address = ip_address_string; + netdriver_config.ip_netmask = netmask_string; + rtems_bsdnet_config.gateway = gateway_string; + + if (0 != rtems_bsdnet_initialize_network()) + PJ_LOG(3,(THIS_FILE, "Error: Unable to initialize network stack!")); + else + PJ_LOG(3,(THIS_FILE, "IP addr=%s/%s, gw=%s", + ip_address_string, + netmask_string, + gateway_string)); + + //rtems_rdbg_initialize(); + //enterRdbg(); + //send_udp("192.168.0.1"); + //test_sock(); +} + + diff --git a/pjlib/src/pjlib-test/main_symbian.cpp b/pjlib/src/pjlib-test/main_symbian.cpp new file mode 100644 index 0000000..a91b671 --- /dev/null +++ b/pjlib/src/pjlib-test/main_symbian.cpp @@ -0,0 +1,133 @@ +//Auto-generated file. Please do not modify. +//#include <e32cmn.h> + +//#pragma data_seg(".SYMBIAN") +//__EMULATOR_IMAGE_HEADER2 (0x1000007a,0x00000000,0x00000000,EPriorityForeground,0x00000000u,0x00000000u,0x00000000,0x00000000,0x00000000,0) +//#pragma data_seg() + +#include "test.h" +#include <stdlib.h> +#include <pj/errno.h> +#include <pj/os.h> +#include <pj/log.h> +#include <pj/unicode.h> +#include <stdio.h> + +#include <e32std.h> + +#if 0 +int main() +{ + int err = 0; + int exp = 0; + + err = test_main(); + //err = test_main(); + + if (err) + return err; + return exp; + //return 0; +} + +#else +#include <pj/os.h> + +#include <e32base.h> +#include <e32std.h> +#include <e32cons.h> // Console + + + +// Global Variables + +LOCAL_D CConsoleBase* console; // write all messages to this + + +class MyScheduler : public CActiveScheduler +{ +public: + MyScheduler() + {} + + void Error(TInt aError) const; +}; + +void MyScheduler::Error(TInt aError) const +{ + PJ_UNUSED_ARG(aError); +} + +LOCAL_C void DoStartL() + { + // Create active scheduler (to run active objects) + CActiveScheduler* scheduler = new (ELeave) MyScheduler; + CleanupStack::PushL(scheduler); + CActiveScheduler::Install(scheduler); + + test_main(); + + CActiveScheduler::Install(NULL); + CleanupStack::Pop(scheduler); + delete scheduler; + } + +#define WRITE_TO_DEBUG_CONSOLE + +#ifdef WRITE_TO_DEBUG_CONSOLE +#include<e32debug.h> +#endif + +// Global Functions +static void log_writer(int level, const char *buf, int len) +{ + static wchar_t buf16[PJ_LOG_MAX_SIZE]; + + PJ_UNUSED_ARG(level); + + pj_ansi_to_unicode(buf, len, buf16, PJ_ARRAY_SIZE(buf16)); + buf16[len] = 0; + buf16[len+1] = 0; + + TPtrC16 aBuf((const TUint16*)buf16, (TInt)len); + console->Write(aBuf); + +#ifdef WRITE_TO_DEBUG_CONSOLE + RDebug::Print(aBuf); +#endif +} + + +GLDEF_C TInt E32Main() + { + // Create cleanup stack + __UHEAP_MARK; + CTrapCleanup* cleanup = CTrapCleanup::New(); + + // Create output console + TRAPD(createError, console = Console::NewL(_L("Console"), TSize(KConsFullScreen,KConsFullScreen))); + if (createError) + return createError; + + pj_log_set_log_func(&log_writer); + + // Run application code inside TRAP harness, wait keypress when terminated + TRAPD(mainError, DoStartL()); + if (mainError) + console->Printf(_L(" failed, leave code = %d"), mainError); + + console->Printf(_L(" [press any key]\n")); + console->Getch(); + + delete console; + delete cleanup; + + CloseSTDLIB(); + + __UHEAP_MARKEND; + + return KErrNone; + } + +#endif /* if 0 */ + diff --git a/pjlib/src/pjlib-test/main_win32.c b/pjlib/src/pjlib-test/main_win32.c new file mode 100644 index 0000000..fd107c3 --- /dev/null +++ b/pjlib/src/pjlib-test/main_win32.c @@ -0,0 +1,211 @@ +/* $Id: main_win32.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 <pj/string.h> +#include <pj/unicode.h> +#include <pj/sock.h> +#include <pj/log.h> + +#define WIN32_LEAN_AND_MEAN +#define NONAMELESSUNION +#include <windows.h> +#include <commctrl.h> +#include <tchar.h> + +#define MAX_LOADSTRING 100 +#define THIS_FILE "main_win32.c" + +#define IDC_HELLO_WINCE 3 +#define ID_LOGWINDOW 104 + + +ATOM MyRegisterClass (HINSTANCE, LPTSTR); +BOOL InitInstance (HINSTANCE, int); +LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); + + +extern int param_log_decor; // in test.c + +static HINSTANCE hInst; +static HWND hwndLog; +static HFONT hFixedFont; + + +static void write_log(int level, const char *data, int len) +{ + PJ_DECL_UNICODE_TEMP_BUF(wdata,256); + + PJ_UNUSED_ARG(level); + PJ_UNUSED_ARG(len); + SendMessage(hwndLog, EM_REPLACESEL, FALSE, + (LPARAM)PJ_STRING_TO_NATIVE(data,wdata,256)); +} + + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPTSTR lpCmdLine, int nCmdShow) +{ + MSG msg; + + PJ_UNUSED_ARG(lpCmdLine); + PJ_UNUSED_ARG(hPrevInstance); + + + if (!InitInstance (hInstance, nCmdShow)) + return FALSE; + + pj_log_set_log_func( &write_log ); + param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_CR; + + // Run the test! + test_main(); + + PJ_LOG(3,(THIS_FILE,"")); + PJ_LOG(3,(THIS_FILE,"Press ESC to quit")); + + // Message loop, waiting to quit. + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + DeleteObject(hFixedFont); + return msg.wParam; +} + + +#ifdef _CONSOLE +int main() +{ + return WinMain(GetModuleHandle(NULL), NULL, NULL, SW_SHOW); +} +#endif + + +ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass) +{ + WNDCLASS wc; + + wc.style = CS_HREDRAW | CS_VREDRAW; + wc.lpfnWndProc = (WNDPROC) WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = hInstance; + ///wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_HELLO_WINCE)); + wc.hIcon = NULL; + wc.hCursor = 0; + wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); + wc.lpszMenuName = 0; + wc.lpszClassName = szWindowClass; + + return RegisterClass(&wc); +} + + +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) +{ + HWND hWnd; + TCHAR *szTitle = _T("PJSIP Test"); + TCHAR *szWindowClass = _T("PJSIP_TEST"); + LOGFONT lf; + + + memset(&lf, 0, sizeof(lf)); + lf.lfHeight = 13; +#if PJ_NATIVE_STRING_IS_UNICODE + wcscpy(lf.lfFaceName, _T("Courier New")); +#else + strcpy(lf.lfFaceName, "Lucida Console"); +#endif + + hFixedFont = CreateFontIndirect(&lf); + if (!hFixedFont) + return FALSE; + + hInst = hInstance; + + MyRegisterClass(hInstance, szWindowClass); + + hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, NULL, NULL, hInstance, NULL); + + if (!hWnd) + return FALSE; + + ShowWindow(hWnd, nCmdShow); + UpdateWindow(hWnd); + + if (hwndLog) { + SendMessage(hwndLog, WM_SETFONT, (WPARAM) hFixedFont, (LPARAM) 0); + ShowWindow(hwndLog, TRUE); + } + + return TRUE; +} + + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + RECT rt; + DWORD dwStyle; + + switch (message) + { + case WM_CREATE: + // Create text control. + GetClientRect(hWnd, &rt); + dwStyle = WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL | + WS_BORDER | ES_LEFT | ES_MULTILINE | ES_NOHIDESEL | + ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_READONLY; + hwndLog = CreateWindow( TEXT("edit"), // class + NULL, // window text + dwStyle, // style + 0, // x-left + 0, // y-top + rt.right-rt.left, // w + rt.bottom-rt.top, // h + hWnd, // parent + (HMENU)ID_LOGWINDOW,// id + hInst, // instance + NULL); // NULL for control. + break; + case WM_ACTIVATE: + if (LOWORD(wParam) == WA_INACTIVE) + DestroyWindow(hWnd); + break; + case WM_CHAR: + if (wParam == 27) { + DestroyWindow(hWnd); + } + break; + case WM_CLOSE: + DestroyWindow(hWnd); + break; + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + return 0; +} + diff --git a/pjlib/src/pjlib-test/mutex.c b/pjlib/src/pjlib-test/mutex.c new file mode 100644 index 0000000..0c5fa07 --- /dev/null +++ b/pjlib/src/pjlib-test/mutex.c @@ -0,0 +1,234 @@ +/* $Id: mutex.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.h> + +#if INCLUDE_MUTEX_TEST + +#undef TRACE_ +//#define TRACE_(x) PJ_LOG(3,x) +#define TRACE_(x) + +/* Test witn non-recursive mutex. */ +static int simple_mutex_test(pj_pool_t *pool) +{ + pj_status_t rc; + pj_mutex_t *mutex; + + PJ_LOG(3,("", "...testing simple mutex")); + + /* Create mutex. */ + TRACE_(("", "....create mutex")); + rc = pj_mutex_create( pool, "", PJ_MUTEX_SIMPLE, &mutex); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_mutex_create", rc); + return -10; + } + + /* Normal lock/unlock cycle. */ + TRACE_(("", "....lock mutex")); + rc = pj_mutex_lock(mutex); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_mutex_lock", rc); + return -20; + } + TRACE_(("", "....unlock mutex")); + rc = pj_mutex_unlock(mutex); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_mutex_unlock", rc); + return -30; + } + + /* Lock again. */ + TRACE_(("", "....lock mutex")); + rc = pj_mutex_lock(mutex); + if (rc != PJ_SUCCESS) return -40; + + /* Try-lock should fail. It should not deadlocked. */ + TRACE_(("", "....trylock mutex")); + rc = pj_mutex_trylock(mutex); + if (rc == PJ_SUCCESS) + PJ_LOG(3,("", "...info: looks like simple mutex is recursive")); + + /* Unlock and done. */ + TRACE_(("", "....unlock mutex")); + rc = pj_mutex_unlock(mutex); + if (rc != PJ_SUCCESS) return -50; + + TRACE_(("", "....destroy mutex")); + rc = pj_mutex_destroy(mutex); + if (rc != PJ_SUCCESS) return -60; + + TRACE_(("", "....done")); + return PJ_SUCCESS; +} + + +/* Test with recursive mutex. */ +static int recursive_mutex_test(pj_pool_t *pool) +{ + pj_status_t rc; + pj_mutex_t *mutex; + + PJ_LOG(3,("", "...testing recursive mutex")); + + /* Create mutex. */ + TRACE_(("", "....create mutex")); + rc = pj_mutex_create( pool, "", PJ_MUTEX_RECURSE, &mutex); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_mutex_create", rc); + return -10; + } + + /* Normal lock/unlock cycle. */ + TRACE_(("", "....lock mutex")); + rc = pj_mutex_lock(mutex); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_mutex_lock", rc); + return -20; + } + TRACE_(("", "....unlock mutex")); + rc = pj_mutex_unlock(mutex); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_mutex_unlock", rc); + return -30; + } + + /* Lock again. */ + TRACE_(("", "....lock mutex")); + rc = pj_mutex_lock(mutex); + if (rc != PJ_SUCCESS) return -40; + + /* Try-lock should NOT fail. . */ + TRACE_(("", "....trylock mutex")); + rc = pj_mutex_trylock(mutex); + if (rc != PJ_SUCCESS) { + app_perror("...error: recursive mutex is not recursive!", rc); + return -40; + } + + /* Locking again should not fail. */ + TRACE_(("", "....lock mutex")); + rc = pj_mutex_lock(mutex); + if (rc != PJ_SUCCESS) { + app_perror("...error: recursive mutex is not recursive!", rc); + return -45; + } + + /* Unlock several times and done. */ + TRACE_(("", "....unlock mutex 3x")); + rc = pj_mutex_unlock(mutex); + if (rc != PJ_SUCCESS) return -50; + rc = pj_mutex_unlock(mutex); + if (rc != PJ_SUCCESS) return -51; + rc = pj_mutex_unlock(mutex); + if (rc != PJ_SUCCESS) return -52; + + TRACE_(("", "....destroy mutex")); + rc = pj_mutex_destroy(mutex); + if (rc != PJ_SUCCESS) return -60; + + TRACE_(("", "....done")); + return PJ_SUCCESS; +} + +#if PJ_HAS_SEMAPHORE +static int semaphore_test(pj_pool_t *pool) +{ + pj_sem_t *sem; + pj_status_t status; + + PJ_LOG(3,("", "...testing semaphore")); + + status = pj_sem_create(pool, NULL, 0, 1, &sem); + if (status != PJ_SUCCESS) { + app_perror("...error: pj_sem_create()", status); + return -151; + } + + status = pj_sem_post(sem); + if (status != PJ_SUCCESS) { + app_perror("...error: pj_sem_post()", status); + pj_sem_destroy(sem); + return -153; + } + + status = pj_sem_trywait(sem); + if (status != PJ_SUCCESS) { + app_perror("...error: pj_sem_trywait()", status); + pj_sem_destroy(sem); + return -156; + } + + status = pj_sem_post(sem); + if (status != PJ_SUCCESS) { + app_perror("...error: pj_sem_post()", status); + pj_sem_destroy(sem); + return -159; + } + + status = pj_sem_wait(sem); + if (status != PJ_SUCCESS) { + app_perror("...error: pj_sem_wait()", status); + pj_sem_destroy(sem); + return -161; + } + + status = pj_sem_destroy(sem); + if (status != PJ_SUCCESS) { + app_perror("...error: pj_sem_destroy()", status); + return -163; + } + + return 0; +} +#endif /* PJ_HAS_SEMAPHORE */ + + +int mutex_test(void) +{ + pj_pool_t *pool; + int rc; + + pool = pj_pool_create(mem, "", 4000, 4000, NULL); + + rc = simple_mutex_test(pool); + if (rc != 0) + return rc; + + rc = recursive_mutex_test(pool); + if (rc != 0) + return rc; + +#if PJ_HAS_SEMAPHORE + rc = semaphore_test(pool); + if (rc != 0) + return rc; +#endif + + pj_pool_release(pool); + + return 0; +} + +#else +int dummy_mutex_test; +#endif + diff --git a/pjlib/src/pjlib-test/os.c b/pjlib/src/pjlib-test/os.c new file mode 100644 index 0000000..87270ff --- /dev/null +++ b/pjlib/src/pjlib-test/os.c @@ -0,0 +1,116 @@ +/* $Id: os.c 3841 2011-10-24 09:28:13Z ming $ */ +/* + * 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/log.h> +#include <pj/os.h> + +#if INCLUDE_OS_TEST +static int endianness_test32(void) +{ + union t + { + pj_uint32_t u32; + pj_uint16_t u16[2]; + pj_uint8_t u8[4]; + } t; + + PJ_LOG(3,("", " Testing endianness..")); + + t.u32 = 0x11223344; + +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN + PJ_LOG(3,("", " Library is set to little endian")); + +# if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN +# error Error: Both PJ_IS_LITTLE_ENDIAN and PJ_IS_BIG_ENDIAN are set! +# endif + + if ((t.u16[0] & 0xFFFF) != 0x3344 || + (t.u16[1] & 0xFFFF) != 0x1122) + { + PJ_LOG(3,("", " Error: wrong 16bit values 0x%x and 0x%x", + (t.u16[0] & 0xFFFF), (t.u16[1] & 0xFFFF))); + return 10; + } + + if ((t.u8[0] & 0xFF) != 0x44 || + (t.u8[1] & 0xFF) != 0x33 || + (t.u8[2] & 0xFF) != 0x22 || + (t.u8[3] & 0xFF) != 0x11) + { + PJ_LOG(3,("", " Error: wrong 8bit values")); + return 12; + } + +#elif defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN + PJ_LOG(3,("", " Library is set to big endian")); + + if ((t.u16[0] & 0xFFFF) != 0x1122 || + (t.u16[1] & 0xFFFF) != 0x3344) + { + PJ_LOG(3,("", " Error: wrong 16bit values 0x%x and 0x%x", + (t.u16[0] & 0xFFFF), (t.u16[1] & 0xFFFF))); + return 20; + } + + if ((t.u8[0] & 0xFF) != 0x11 || + (t.u8[1] & 0xFF) != 0x22 || + (t.u8[2] & 0xFF) != 0x33 || + (t.u8[3] & 0xFF) != 0x44) + { + PJ_LOG(3,("", " Error: wrong 8bit values")); + return 22; + } + +# if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN +# error Error: Both PJ_IS_LITTLE_ENDIAN and PJ_IS_BIG_ENDIAN are set! +# endif + + +#else +# error Error: Endianness is not set properly! +#endif + + return 0; +} + +int os_test(void) +{ + const pj_sys_info *si; + int rc = 0; + + PJ_LOG(3,("", " Sys info:")); + si = pj_get_sys_info(); + PJ_LOG(3,("", " machine: %s", si->machine.ptr)); + PJ_LOG(3,("", " os_name: %s", si->os_name.ptr)); + PJ_LOG(3,("", " os_ver: 0x%x", si->os_ver)); + PJ_LOG(3,("", " sdk_name: %s", si->sdk_name.ptr)); + PJ_LOG(3,("", " sdk_ver: 0x%x", si->sdk_ver)); + PJ_LOG(3,("", " info: %s", si->info.ptr)); + + rc = endianness_test32(); + + return rc; +} + +#else +int dummy_os_var; +#endif + diff --git a/pjlib/src/pjlib-test/pjlib_test_reg.rss b/pjlib/src/pjlib-test/pjlib_test_reg.rss new file mode 100644 index 0000000..dd892e4 --- /dev/null +++ b/pjlib/src/pjlib-test/pjlib_test_reg.rss @@ -0,0 +1,12 @@ +// Symbian application registration info for pjlib-test
+
+#include <appinfo.rh>
+
+UID2 KUidAppRegistrationResourceFile
+UID3 0xA0000002
+
+RESOURCE APP_REGISTRATION_INFO
+{
+ app_file="pjlib_test";
+}
+
diff --git a/pjlib/src/pjlib-test/pool.c b/pjlib/src/pjlib-test/pool.c new file mode 100644 index 0000000..76ef79a --- /dev/null +++ b/pjlib/src/pjlib-test/pool.c @@ -0,0 +1,321 @@ +/* $Id: pool.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 <pj/pool.h> +#include <pj/pool_buf.h> +#include <pj/rand.h> +#include <pj/log.h> +#include <pj/except.h> +#include "test.h" + +/** + * \page page_pjlib_pool_test Test: Pool + * + * This file provides implementation of \b pool_test(). It tests the + * functionality of the memory pool. + * + * + * This file is <b>pjlib-test/pool.c</b> + * + * \include pjlib-test/pool.c + */ + + +#if INCLUDE_POOL_TEST + +#define SIZE 4096 + +/* Normally we should throw exception when memory alloc fails. + * Here we do nothing so that the flow will go back to original caller, + * which will test the result using NULL comparison. Normally caller will + * catch the exception instead of checking for NULLs. + */ +static void null_callback(pj_pool_t *pool, pj_size_t size) +{ + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(size); +} + +#define GET_FREE(p) (pj_pool_get_capacity(p)-pj_pool_get_used_size(p)) + +/* Test that the capacity and used size reported by the pool is correct. + */ +static int capacity_test(void) +{ + pj_pool_t *pool = pj_pool_create(mem, NULL, SIZE, 0, &null_callback); + pj_size_t freesize; + + PJ_LOG(3,("test", "...capacity_test()")); + + if (!pool) + return -200; + + freesize = GET_FREE(pool); + + if (pj_pool_alloc(pool, freesize) == NULL) { + PJ_LOG(3,("test", "...error: wrong freesize %u reported", + freesize)); + pj_pool_release(pool); + return -210; + } + + pj_pool_release(pool); + return 0; +} + +/* Test that the alignment works. */ +static int pool_alignment_test(void) +{ + pj_pool_t *pool; + void *ptr; + enum { MEMSIZE = 64, LOOP = 100 }; + unsigned i; + + PJ_LOG(3,("test", "...alignment test")); + + pool = pj_pool_create(mem, NULL, PJ_POOL_SIZE+MEMSIZE, MEMSIZE, NULL); + if (!pool) + return -300; + +#define IS_ALIGNED(p) ((((unsigned long)p) & (PJ_POOL_ALIGNMENT-1)) == 0) + + for (i=0; i<LOOP; ++i) { + /* Test first allocation */ + ptr = pj_pool_alloc(pool, 1); + if (!IS_ALIGNED(ptr)) { + pj_pool_release(pool); + return -310; + } + + /* Test subsequent allocation */ + ptr = pj_pool_alloc(pool, 1); + if (!IS_ALIGNED(ptr)) { + pj_pool_release(pool); + return -320; + } + + /* Test allocation after new block is created */ + ptr = pj_pool_alloc(pool, MEMSIZE*2+1); + if (!IS_ALIGNED(ptr)) { + pj_pool_release(pool); + return -330; + } + + /* Reset the pool */ + pj_pool_reset(pool); + } + + /* Done */ + pj_pool_release(pool); + + return 0; +} + +/* Test that the alignment works for pool on buf. */ +static int pool_buf_alignment_test(void) +{ + pj_pool_t *pool; + char buf[512]; + void *ptr; + enum { LOOP = 100 }; + unsigned i; + + PJ_LOG(3,("test", "...pool_buf alignment test")); + + pool = pj_pool_create_on_buf(NULL, buf, sizeof(buf)); + if (!pool) + return -400; + + for (i=0; i<LOOP; ++i) { + /* Test first allocation */ + ptr = pj_pool_alloc(pool, 1); + if (!IS_ALIGNED(ptr)) { + pj_pool_release(pool); + return -410; + } + + /* Test subsequent allocation */ + ptr = pj_pool_alloc(pool, 1); + if (!IS_ALIGNED(ptr)) { + pj_pool_release(pool); + return -420; + } + + /* Reset the pool */ + pj_pool_reset(pool); + } + + /* Done */ + return 0; +} + +/* Test function to drain the pool's space. + */ +static int drain_test(pj_size_t size, pj_size_t increment) +{ + pj_pool_t *pool = pj_pool_create(mem, NULL, size, increment, + &null_callback); + pj_size_t freesize; + void *p; + int status = 0; + + PJ_LOG(3,("test", "...drain_test(%d,%d)", size, increment)); + + if (!pool) + return -10; + + /* Get free size */ + freesize = GET_FREE(pool); + if (freesize < 1) { + status=-15; + goto on_error; + } + + /* Drain the pool until there's nothing left. */ + while (freesize > 0) { + int size; + + if (freesize > 255) + size = ((pj_rand() & 0x000000FF) + PJ_POOL_ALIGNMENT) & + ~(PJ_POOL_ALIGNMENT - 1); + else + size = freesize; + + p = pj_pool_alloc(pool, size); + if (!p) { + status=-20; goto on_error; + } + + freesize -= size; + } + + /* Check that capacity is zero. */ + if (GET_FREE(pool) != 0) { + PJ_LOG(3,("test", "....error: returned free=%u (expecting 0)", + GET_FREE(pool))); + status=-30; goto on_error; + } + + /* Try to allocate once more */ + p = pj_pool_alloc(pool, 257); + if (!p) { + status=-40; goto on_error; + } + + /* Check that capacity is NOT zero. */ + if (GET_FREE(pool) == 0) { + status=-50; goto on_error; + } + + +on_error: + pj_pool_release(pool); + return status; +} + +/* Test the buffer based pool */ +static int pool_buf_test(void) +{ + enum { STATIC_BUF_SIZE = 40 }; + /* 16 is the internal struct in pool_buf */ + static char buf[ STATIC_BUF_SIZE + sizeof(pj_pool_t) + + sizeof(pj_pool_block) + 2 * PJ_POOL_ALIGNMENT]; + pj_pool_t *pool; + void *p; + PJ_USE_EXCEPTION; + + PJ_LOG(3,("test", "...pool_buf test")); + + pool = pj_pool_create_on_buf("no name", buf, sizeof(buf)); + if (!pool) + return -70; + + /* Drain the pool */ + PJ_TRY { + if ((p=pj_pool_alloc(pool, STATIC_BUF_SIZE/2)) == NULL) + return -75; + + if ((p=pj_pool_alloc(pool, STATIC_BUF_SIZE/2)) == NULL) + return -76; + } + PJ_CATCH_ANY { + return -77; + } + PJ_END; + + /* On the next alloc, exception should be thrown */ + PJ_TRY { + p = pj_pool_alloc(pool, STATIC_BUF_SIZE); + if (p != NULL) { + /* This is unexpected, the alloc should fail */ + return -78; + } + } + PJ_CATCH_ANY { + /* This is the expected result */ + } + PJ_END; + + /* Done */ + return 0; +} + + +int pool_test(void) +{ + enum { LOOP = 2 }; + int loop; + int rc; + + rc = capacity_test(); + if (rc) return rc; + + rc = pool_alignment_test(); + if (rc) return rc; + + rc = pool_buf_alignment_test(); + if (rc) return rc; + + for (loop=0; loop<LOOP; ++loop) { + /* Test that the pool should grow automaticly. */ + rc = drain_test(SIZE, SIZE); + if (rc != 0) return rc; + + /* Test situation where pool is not allowed to grow. + * We expect the test to return correct error. + */ + rc = drain_test(SIZE, 0); + if (rc != -40) return rc; + } + + rc = pool_buf_test(); + if (rc != 0) + return rc; + + + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_pool_test; +#endif /* INCLUDE_POOL_TEST */ + diff --git a/pjlib/src/pjlib-test/pool_perf.c b/pjlib/src/pjlib-test/pool_perf.c new file mode 100644 index 0000000..d5985cb --- /dev/null +++ b/pjlib/src/pjlib-test/pool_perf.c @@ -0,0 +1,194 @@ +/* $Id: pool_perf.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_POOL_PERF_TEST + +#include <pjlib.h> +#include <pj/compat/malloc.h> + +#if !PJ_HAS_HIGH_RES_TIMER +# error Need high resolution timer for this test. +#endif + +#define THIS_FILE "test" + +#define LOOP 10 +#define COUNT 1024 +static unsigned sizes[COUNT]; +static char *p[COUNT]; +#define MIN_SIZE 4 +#define MAX_SIZE 512 +static unsigned total_size; + + +static int pool_test_pool() +{ + int i; + pj_pool_t *pool = pj_pool_create(mem, NULL, total_size + 4*COUNT, 0, NULL); + if (!pool) + return -1; + + for (i=0; i<COUNT; ++i) { + char *p; + if ( (p=(char*)pj_pool_alloc(pool, sizes[i])) == NULL) { + PJ_LOG(3,(THIS_FILE," error: pool failed to allocate %d bytes", + sizes[i])); + pj_pool_release(pool); + return -1; + } + *p = '\0'; + } + + pj_pool_release(pool); + return 0; +} + +/* Symbian doesn't have malloc()/free(), so we use new/delete instead */ +//#if defined(PJ_SYMBIAN) && PJ_SYMBIAN != 0 +#if 0 +static int pool_test_malloc_free() +{ + int i; /* must be signed */ + + for (i=0; i<COUNT; ++i) { + p[i] = new char[sizes[i]]; + if (!p[i]) { + PJ_LOG(3,(THIS_FILE," error: malloc failed to allocate %d bytes", + sizes[i])); + --i; + while (i >= 0) { + delete [] p[i]; + --i; + } + return -1; + } + *p[i] = '\0'; + } + + for (i=0; i<COUNT; ++i) { + delete [] p[i]; + } + + return 0; +} + +#else /* PJ_SYMBIAN */ + +static int pool_test_malloc_free() +{ + int i; /* must be signed */ + + for (i=0; i<COUNT; ++i) { + p[i] = (char*)malloc(sizes[i]); + if (!p[i]) { + PJ_LOG(3,(THIS_FILE," error: malloc failed to allocate %d bytes", + sizes[i])); + --i; + while (i >= 0) + free(p[i]), --i; + return -1; + } + *p[i] = '\0'; + } + + for (i=0; i<COUNT; ++i) { + free(p[i]); + } + + return 0; +} + +#endif /* PJ_SYMBIAN */ + +int pool_perf_test() +{ + unsigned i; + pj_uint32_t pool_time=0, malloc_time=0, pool_time2=0; + pj_timestamp start, end; + pj_uint32_t best, worst; + + /* Initialize size of chunks to allocate in for the test. */ + for (i=0; i<COUNT; ++i) { + unsigned aligned_size; + sizes[i] = MIN_SIZE + (pj_rand() % MAX_SIZE); + aligned_size = sizes[i]; + if (aligned_size & (PJ_POOL_ALIGNMENT-1)) + aligned_size = ((aligned_size + PJ_POOL_ALIGNMENT - 1)) & ~(PJ_POOL_ALIGNMENT - 1); + total_size += aligned_size; + } + + /* Add some more for pool admin area */ + total_size += 512; + + PJ_LOG(3, (THIS_FILE, "Benchmarking pool..")); + + /* Warmup */ + pool_test_pool(); + pool_test_malloc_free(); + + for (i=0; i<LOOP; ++i) { + pj_get_timestamp(&start); + if (pool_test_pool()) { + return 1; + } + pj_get_timestamp(&end); + pool_time += (end.u32.lo - start.u32.lo); + + pj_get_timestamp(&start); + if (pool_test_malloc_free()) { + return 2; + } + pj_get_timestamp(&end); + malloc_time += (end.u32.lo - start.u32.lo); + + pj_get_timestamp(&start); + if (pool_test_pool()) { + return 4; + } + pj_get_timestamp(&end); + pool_time2 += (end.u32.lo - start.u32.lo); + } + + PJ_LOG(4,(THIS_FILE,"..LOOP count: %u",LOOP)); + PJ_LOG(4,(THIS_FILE,"..number of alloc/dealloc per loop: %u",COUNT)); + PJ_LOG(4,(THIS_FILE,"..pool allocation/deallocation time: %u",pool_time)); + PJ_LOG(4,(THIS_FILE,"..malloc/free time: %u",malloc_time)); + PJ_LOG(4,(THIS_FILE,"..pool again, second invocation: %u",pool_time2)); + + if (pool_time2==0) pool_time2=1; + if (pool_time < pool_time2) + best = pool_time, worst = pool_time2; + else + best = pool_time2, worst = pool_time; + + /* avoid division by zero */ + if (best==0) best=1; + if (worst==0) worst=1; + + PJ_LOG(3, (THIS_FILE, "..pool speedup over malloc best=%dx, worst=%dx", + (int)(malloc_time/best), + (int)(malloc_time/worst))); + return 0; +} + + +#endif /* INCLUDE_POOL_PERF_TEST */ + diff --git a/pjlib/src/pjlib-test/pool_wrap.cpp b/pjlib/src/pjlib-test/pool_wrap.cpp new file mode 100644 index 0000000..f435ae8 --- /dev/null +++ b/pjlib/src/pjlib-test/pool_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: pool_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "pool.c" diff --git a/pjlib/src/pjlib-test/rand.c b/pjlib/src/pjlib-test/rand.c new file mode 100644 index 0000000..3c442d2 --- /dev/null +++ b/pjlib/src/pjlib-test/rand.c @@ -0,0 +1,54 @@ +/* $Id: rand.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 <pj/rand.h> +#include <pj/log.h> +#include "test.h" + +#if INCLUDE_RAND_TEST + +#define COUNT 1024 +static int values[COUNT]; + +/* + * rand_test(), simply generates COUNT number of random number and + * check that there's no duplicate numbers. + */ +int rand_test(void) +{ + int i; + + for (i=0; i<COUNT; ++i) { + int j; + + values[i] = pj_rand(); + for (j=0; j<i; ++j) { + if (values[i] == values[j]) { + PJ_LOG(3,("test", "error: duplicate value %d at %d-th index", + values[i], i)); + return -10; + } + } + } + + return 0; +} + +#endif /* INCLUDE_RAND_TEST */ + diff --git a/pjlib/src/pjlib-test/rbtree.c b/pjlib/src/pjlib-test/rbtree.c new file mode 100644 index 0000000..601a163 --- /dev/null +++ b/pjlib/src/pjlib-test/rbtree.c @@ -0,0 +1,168 @@ +/* $Id: rbtree.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_RBTREE_TEST + +#include <pjlib.h> + +#define LOOP 32 +#define MIN_COUNT 64 +#define MAX_COUNT (LOOP * MIN_COUNT) +#define STRSIZE 16 +#define THIS_FILE "rbtree_test" + +typedef struct node_key +{ + pj_uint32_t hash; + char str[STRSIZE]; +} node_key; + +static int compare_node(const node_key *k1, const node_key *k2) +{ + if (k1->hash == k2->hash) { + return strcmp(k1->str, k2->str); + } else { + return k1->hash < k2->hash ? -1 : 1; + } +} + +void randomize_string(char *str, int len) +{ + int i; + for (i=0; i<len-1; ++i) + str[i] = (char)('a' + pj_rand() % 26); + str[len-1] = '\0'; +} + +static int test(void) +{ + pj_rbtree rb; + node_key *key; + pj_rbtree_node *node; + pj_pool_t *pool; + int err=0; + int count = MIN_COUNT; + int i; + unsigned size; + + pj_rbtree_init(&rb, (pj_rbtree_comp*)&compare_node); + size = MAX_COUNT*(sizeof(*key)+PJ_RBTREE_NODE_SIZE) + + PJ_RBTREE_SIZE + PJ_POOL_SIZE; + pool = pj_pool_create( mem, "pool", size, 0, NULL); + if (!pool) { + PJ_LOG(3,("test", "...error: creating pool of %u bytes", size)); + return -10; + } + + key = (node_key *)pj_pool_alloc(pool, MAX_COUNT*sizeof(*key)); + if (!key) + return -20; + + node = (pj_rbtree_node*)pj_pool_alloc(pool, MAX_COUNT*sizeof(*node)); + if (!node) + return -30; + + for (i=0; i<LOOP; ++i) { + int j; + pj_rbtree_node *prev, *it; + pj_timestamp t1, t2, t_setup, t_insert, t_search, t_erase; + + pj_assert(rb.size == 0); + + t_setup.u32.lo = t_insert.u32.lo = t_search.u32.lo = t_erase.u32.lo = 0; + + for (j=0; j<count; j++) { + randomize_string(key[j].str, STRSIZE); + + pj_get_timestamp(&t1); + node[j].key = &key[j]; + node[j].user_data = key[j].str; + key[j].hash = pj_hash_calc(0, key[j].str, PJ_HASH_KEY_STRING); + pj_get_timestamp(&t2); + t_setup.u32.lo += (t2.u32.lo - t1.u32.lo); + + pj_get_timestamp(&t1); + pj_rbtree_insert(&rb, &node[j]); + pj_get_timestamp(&t2); + t_insert.u32.lo += (t2.u32.lo - t1.u32.lo); + } + + pj_assert(rb.size == (unsigned)count); + + // Iterate key, make sure they're sorted. + prev = NULL; + it = pj_rbtree_first(&rb); + while (it) { + if (prev) { + if (compare_node((node_key*)prev->key,(node_key*)it->key)>=0) { + ++err; + PJ_LOG(3, (THIS_FILE, "Error: %s >= %s", + (char*)prev->user_data, (char*)it->user_data)); + } + } + prev = it; + it = pj_rbtree_next(&rb, it); + } + + // Search. + for (j=0; j<count; j++) { + pj_get_timestamp(&t1); + it = pj_rbtree_find(&rb, &key[j]); + pj_get_timestamp(&t2); + t_search.u32.lo += (t2.u32.lo - t1.u32.lo); + + pj_assert(it != NULL); + if (it == NULL) + ++err; + } + + // Erase node. + for (j=0; j<count; j++) { + pj_get_timestamp(&t1); + it = pj_rbtree_erase(&rb, &node[j]); + pj_get_timestamp(&t2); + t_erase.u32.lo += (t2.u32.lo - t1.u32.lo); + } + + PJ_LOG(4, (THIS_FILE, + "...count:%d, setup:%d, insert:%d, search:%d, erase:%d", + count, + t_setup.u32.lo / count, t_insert.u32.lo / count, + t_search.u32.lo / count, t_erase.u32.lo / count)); + + count = 2 * count; + if (count > MAX_COUNT) + break; + } + + pj_pool_release(pool); + return err; +} + + +int rbtree_test() +{ + return test(); +} + +#endif /* INCLUDE_RBTREE_TEST */ + + diff --git a/pjlib/src/pjlib-test/rtems_network_config.h b/pjlib/src/pjlib-test/rtems_network_config.h new file mode 100644 index 0000000..b2d5652 --- /dev/null +++ b/pjlib/src/pjlib-test/rtems_network_config.h @@ -0,0 +1,149 @@ +/* $Id: rtems_network_config.h 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 + */ + +/* + * Thanks Zetron, Inc and Phil Torre <ptorre@zetron.com> for donating PJLIB + * port to RTEMS. + */ + +/* + * Network configuration + * + ************************************************************ + * EDIT THIS FILE TO REFLECT YOUR NETWORK CONFIGURATION * + * BEFORE RUNNING ANY RTEMS PROGRAMS WHICH USE THE NETWORK! * + ************************************************************ + * + */ + +#ifndef _RTEMS_NETWORKCONFIG_H_ +#define _RTEMS_NETWORKCONFIG_H_ + + +#define DEFAULT_IP_ADDRESS_STRING "192.168.0.2" +#define DEFAULT_NETMASK_STRING "255.255.255.0" +#define DEFAULT_GATEWAY_STRING "192.168.0.1" + + + + +#ifndef RTEMS_BSP_NETWORK_DRIVER_NAME +#warning "RTEMS_BSP_NETWORK_DRIVER_NAME is not defined" +#define RTEMS_BSP_NETWORK_DRIVER_NAME "no_network1" +#endif + +#ifndef RTEMS_BSP_NETWORK_DRIVER_ATTACH +#warning "RTEMS_BSP_NETWORK_DRIVER_ATTACH is not defined" +#define RTEMS_BSP_NETWORK_DRIVER_ATTACH 0 +#endif + +#define NETWORK_STACK_PRIORITY 128 +/* #define RTEMS_USE_BOOTP */ + +/* #define RTEMS_USE_LOOPBACK */ + +#include <bsp.h> + +/* + * Define RTEMS_SET_ETHERNET_ADDRESS if you want to specify the + * Ethernet address here. If RTEMS_SET_ETHERNET_ADDRESS is not + * defined the driver will choose an address. + */ +// NOTE: The address below is a dummy address that should only ever +// be used for testing on a private network. DO NOT LET A PRODUCT +// CONTAINING THIS ETHERNET ADDRESS OUT INTO THE FIELD! +//#define RTEMS_SET_ETHERNET_ADDRESS +#if (defined (RTEMS_SET_ETHERNET_ADDRESS)) +static char ethernet_address[6] = { 0x00, 0x80, 0x7F, 0x22, 0x61, 0x77 }; +#endif + +#define RTEMS_USE_LOOPBACK +#ifdef RTEMS_USE_LOOPBACK +/* + * Loopback interface + */ +extern int rtems_bsdnet_loopattach(struct rtems_bsdnet_ifconfig* dummy, int unused); +static struct rtems_bsdnet_ifconfig loopback_config = { + "lo0", /* name */ + rtems_bsdnet_loopattach, /* attach function */ + NULL, /* link to next interface */ + "127.0.0.1", /* IP address */ + "255.0.0.0", /* IP net mask */ +}; +#endif + +/* + * Default network interface + */ +static struct rtems_bsdnet_ifconfig netdriver_config = { + RTEMS_BSP_NETWORK_DRIVER_NAME, /* name */ + RTEMS_BSP_NETWORK_DRIVER_ATTACH, /* attach function */ + +#ifdef RTEMS_USE_LOOPBACK + &loopback_config, /* link to next interface */ +#else + NULL, /* No more interfaces */ +#endif + +#if (defined (RTEMS_USE_BOOTP)) + NULL, /* BOOTP supplies IP address */ + NULL, /* BOOTP supplies IP net mask */ +#else + "192.168.0.33", /* IP address */ + "255.255.255.0", /* IP net mask */ +#endif /* !RTEMS_USE_BOOTP */ + +#if (defined (RTEMS_SET_ETHERNET_ADDRESS)) + ethernet_address, /* Ethernet hardware address */ +#else + NULL, /* Driver supplies hardware address */ +#endif + 0 /* Use default driver parameters */ +}; + +/* + * Network configuration + */ +struct rtems_bsdnet_config rtems_bsdnet_config = { + &netdriver_config, + +#if (defined (RTEMS_USE_BOOTP)) + rtems_bsdnet_do_bootp, +#else + NULL, +#endif + + NETWORK_STACK_PRIORITY, /* Default network task priority */ + 1048576, /* Default mbuf capacity */ + 1048576, /* Default mbuf cluster capacity */ + +#if (!defined (RTEMS_USE_BOOTP)) + "testnode", /* Host name */ + "example.org", /* Domain name */ + "192.168.6.9", /* Gateway */ + "192.168.7.41", /* Log host */ + {"198.137.231.1" }, /* Name server(s) */ + {"207.202.190.162" }, /* NTP server(s) */ +#endif /* !RTEMS_USE_BOOTP */ + +}; + +#endif /* _RTEMS_NETWORKCONFIG_H_ */ + diff --git a/pjlib/src/pjlib-test/select.c b/pjlib/src/pjlib-test/select.c new file mode 100644 index 0000000..d7d891a --- /dev/null +++ b/pjlib/src/pjlib-test/select.c @@ -0,0 +1,221 @@ +/* $Id: select.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" + +/** + * \page page_pjlib_select_test Test: Socket Select() + * + * This file provides implementation of \b select_test(). It tests the + * functionality of the pj_sock_select() API. + * + * + * This file is <b>pjlib-test/select.c</b> + * + * \include pjlib-test/select.c + */ + + +#if INCLUDE_SELECT_TEST + +#include <pj/sock.h> +#include <pj/sock_select.h> +#include <pj/log.h> +#include <pj/string.h> +#include <pj/assert.h> +#include <pj/os.h> +#include <pj/errno.h> + +enum +{ + READ_FDS, + WRITE_FDS, + EXCEPT_FDS +}; + +#define UDP_PORT 51232 +#define THIS_FILE "select_test" + +/* + * do_select() + * + * Perform pj_sock_select() and find out which sockets + * are signalled. + */ +static int do_select( pj_sock_t sock1, pj_sock_t sock2, + int setcount[]) +{ + pj_fd_set_t fds[3]; + pj_time_val timeout; + int i, n; + + for (i=0; i<3; ++i) { + PJ_FD_ZERO(&fds[i]); + PJ_FD_SET(sock1, &fds[i]); + PJ_FD_SET(sock2, &fds[i]); + setcount[i] = 0; + } + + timeout.sec = 1; + timeout.msec = 0; + + n = pj_sock_select(PJ_IOQUEUE_MAX_HANDLES, &fds[0], &fds[1], &fds[2], + &timeout); + if (n < 0) + return n; + if (n == 0) + return 0; + + for (i=0; i<3; ++i) { + if (PJ_FD_ISSET(sock1, &fds[i])) + setcount[i]++; + if (PJ_FD_ISSET(sock2, &fds[i])) + setcount[i]++; + } + + return n; +} + +/* + * select_test() + * + * Test main entry. + */ +int select_test() +{ + pj_sock_t udp1=PJ_INVALID_SOCKET, udp2=PJ_INVALID_SOCKET; + pj_sockaddr_in udp_addr; + int status; + int setcount[3]; + pj_str_t s; + const char data[] = "hello"; + const int datalen = 5; + pj_ssize_t sent, received; + char buf[10]; + pj_status_t rc; + + PJ_LOG(3, (THIS_FILE, "...Testing simple UDP select()")); + + // Create two UDP sockets. + rc = pj_sock_socket( pj_AF_INET(), pj_SOCK_DGRAM(), 0, &udp1); + if (rc != PJ_SUCCESS) { + app_perror("...error: unable to create socket", rc); + status=-10; goto on_return; + } + rc = pj_sock_socket( pj_AF_INET(), pj_SOCK_DGRAM(), 0, &udp2); + if (udp2 == PJ_INVALID_SOCKET) { + app_perror("...error: unable to create socket", rc); + status=-20; goto on_return; + } + + // Bind one of the UDP socket. + pj_bzero(&udp_addr, sizeof(udp_addr)); + udp_addr.sin_family = pj_AF_INET(); + udp_addr.sin_port = UDP_PORT; + udp_addr.sin_addr = pj_inet_addr(pj_cstr(&s, "127.0.0.1")); + + if (pj_sock_bind(udp2, &udp_addr, sizeof(udp_addr))) { + status=-30; goto on_return; + } + + // Send data. + sent = datalen; + rc = pj_sock_sendto(udp1, data, &sent, 0, &udp_addr, sizeof(udp_addr)); + if (rc != PJ_SUCCESS || sent != datalen) { + app_perror("...error: sendto() error", rc); + status=-40; goto on_return; + } + + // Sleep a bit. See http://trac.pjsip.org/repos/ticket/890 + pj_thread_sleep(10); + + // Check that socket is marked as reable. + // Note that select() may also report that sockets are writable. + status = do_select(udp1, udp2, setcount); + if (status < 0) { + char errbuf[128]; + pj_strerror(pj_get_netos_error(), errbuf, sizeof(errbuf)); + PJ_LOG(1,(THIS_FILE, "...error: %s", errbuf)); + status=-50; goto on_return; + } + if (status == 0) { + status=-60; goto on_return; + } + + if (setcount[READ_FDS] != 1) { + status=-70; goto on_return; + } + if (setcount[WRITE_FDS] != 0) { + if (setcount[WRITE_FDS] == 2) { + PJ_LOG(3,(THIS_FILE, "...info: system reports writable sockets")); + } else { + status=-80; goto on_return; + } + } else { + PJ_LOG(3,(THIS_FILE, + "...info: system doesn't report writable sockets")); + } + if (setcount[EXCEPT_FDS] != 0) { + status=-90; goto on_return; + } + + // Read the socket to clear readable sockets. + received = sizeof(buf); + rc = pj_sock_recv(udp2, buf, &received, 0); + if (rc != PJ_SUCCESS || received != 5) { + status=-100; goto on_return; + } + + status = 0; + + // Test timeout on the read part. + // This won't necessarily return zero, as select() may report that + // sockets are writable. + setcount[0] = setcount[1] = setcount[2] = 0; + status = do_select(udp1, udp2, setcount); + if (status != 0 && status != setcount[WRITE_FDS]) { + PJ_LOG(3,(THIS_FILE, "...error: expecting timeout but got %d sks set", + status)); + PJ_LOG(3,(THIS_FILE, " rdset: %d, wrset: %d, exset: %d", + setcount[0], setcount[1], setcount[2])); + status = -110; goto on_return; + } + if (setcount[READ_FDS] != 0) { + PJ_LOG(3,(THIS_FILE, "...error: readable socket not expected")); + status = -120; goto on_return; + } + + status = 0; + +on_return: + if (udp1 != PJ_INVALID_SOCKET) + pj_sock_close(udp1); + if (udp2 != PJ_INVALID_SOCKET) + pj_sock_close(udp2); + return status; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_select_test; +#endif /* INCLUDE_SELECT_TEST */ + + diff --git a/pjlib/src/pjlib-test/sleep.c b/pjlib/src/pjlib-test/sleep.c new file mode 100644 index 0000000..03bef7d --- /dev/null +++ b/pjlib/src/pjlib-test/sleep.c @@ -0,0 +1,224 @@ +/* $Id: sleep.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" + +/** + * \page page_pjlib_sleep_test Test: Sleep, Time, and Timestamp + * + * This file provides implementation of \b sleep_test(). + * + * \section sleep_test_sec Scope of the Test + * + * This tests: + * - whether pj_thread_sleep() works. + * - whether pj_gettimeofday() works. + * - whether pj_get_timestamp() and friends works. + * + * API tested: + * - pj_thread_sleep() + * - pj_gettimeofday() + * - PJ_TIME_VAL_SUB() + * - PJ_TIME_VAL_LTE() + * - pj_get_timestamp() + * - pj_get_timestamp_freq() (implicitly) + * - pj_elapsed_time() + * - pj_elapsed_usec() + * + * + * This file is <b>pjlib-test/sleep.c</b> + * + * \include pjlib-test/sleep.c + */ + +#if INCLUDE_SLEEP_TEST + +#include <pjlib.h> + +#define THIS_FILE "sleep_test" + +static int simple_sleep_test(void) +{ + enum { COUNT = 10 }; + int i; + pj_status_t rc; + + PJ_LOG(3,(THIS_FILE, "..will write messages every 1 second:")); + + for (i=0; i<COUNT; ++i) { + pj_time_val tv; + pj_parsed_time pt; + + rc = pj_thread_sleep(1000); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_thread_sleep()", rc); + return -10; + } + + rc = pj_gettimeofday(&tv); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_gettimeofday()", rc); + return -11; + } + + pj_time_decode(&tv, &pt); + + PJ_LOG(3,(THIS_FILE, + "...%04d-%02d-%02d %02d:%02d:%02d.%03d", + pt.year, pt.mon, pt.day, + pt.hour, pt.min, pt.sec, pt.msec)); + + } + + return 0; +} + +static int sleep_duration_test(void) +{ + enum { MIS = 20}; + unsigned duration[] = { 2000, 1000, 500, 200, 100 }; + unsigned i; + pj_status_t rc; + + PJ_LOG(3,(THIS_FILE, "..running sleep duration test")); + + /* Test pj_thread_sleep() and pj_gettimeofday() */ + for (i=0; i<PJ_ARRAY_SIZE(duration); ++i) { + pj_time_val start, stop; + pj_uint32_t msec; + + /* Mark start of test. */ + rc = pj_gettimeofday(&start); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_gettimeofday()", rc); + return -10; + } + + /* Sleep */ + rc = pj_thread_sleep(duration[i]); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_thread_sleep()", rc); + return -20; + } + + /* Mark end of test. */ + rc = pj_gettimeofday(&stop); + + /* Calculate duration (store in stop). */ + PJ_TIME_VAL_SUB(stop, start); + + /* Convert to msec. */ + msec = PJ_TIME_VAL_MSEC(stop); + + /* Check if it's within range. */ + if (msec < duration[i] * (100-MIS)/100 || + msec > duration[i] * (100+MIS)/100) + { + PJ_LOG(3,(THIS_FILE, + "...error: slept for %d ms instead of %d ms " + "(outside %d%% err window)", + msec, duration[i], MIS)); + return -30; + } + } + + + /* Test pj_thread_sleep() and pj_get_timestamp() and friends */ + for (i=0; i<PJ_ARRAY_SIZE(duration); ++i) { + pj_time_val t1, t2; + pj_timestamp start, stop; + pj_uint32_t msec; + + pj_thread_sleep(0); + + /* Mark start of test. */ + rc = pj_get_timestamp(&start); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_get_timestamp()", rc); + return -60; + } + + /* ..also with gettimeofday() */ + pj_gettimeofday(&t1); + + /* Sleep */ + rc = pj_thread_sleep(duration[i]); + if (rc != PJ_SUCCESS) { + app_perror("...error: pj_thread_sleep()", rc); + return -70; + } + + /* Mark end of test. */ + pj_get_timestamp(&stop); + + /* ..also with gettimeofday() */ + pj_gettimeofday(&t2); + + /* Compare t1 and t2. */ + if (PJ_TIME_VAL_LT(t2, t1)) { + PJ_LOG(3,(THIS_FILE, "...error: t2 is less than t1!!")); + return -75; + } + + /* Get elapsed time in msec */ + msec = pj_elapsed_msec(&start, &stop); + + /* Check if it's within range. */ + if (msec < duration[i] * (100-MIS)/100 || + msec > duration[i] * (100+MIS)/100) + { + PJ_LOG(3,(THIS_FILE, + "...error: slept for %d ms instead of %d ms " + "(outside %d%% err window)", + msec, duration[i], MIS)); + PJ_TIME_VAL_SUB(t2, t1); + PJ_LOG(3,(THIS_FILE, + "...info: gettimeofday() reported duration is " + "%d msec", + PJ_TIME_VAL_MSEC(t2))); + + return -76; + } + } + + /* All done. */ + return 0; +} + +int sleep_test() +{ + int rc; + + rc = simple_sleep_test(); + if (rc != PJ_SUCCESS) + return rc; + + rc = sleep_duration_test(); + if (rc != PJ_SUCCESS) + return rc; + + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_sleep_test; +#endif /* INCLUDE_SLEEP_TEST */ diff --git a/pjlib/src/pjlib-test/sock.c b/pjlib/src/pjlib-test/sock.c new file mode 100644 index 0000000..330fb97 --- /dev/null +++ b/pjlib/src/pjlib-test/sock.c @@ -0,0 +1,877 @@ +/* $Id: sock.c 3841 2011-10-24 09:28:13Z ming $ */ +/* + * 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.h> +#include "test.h" + + +/** + * \page page_pjlib_sock_test Test: Socket + * + * This file provides implementation of \b sock_test(). It tests the + * various aspects of the socket API. + * + * \section sock_test_scope_sec Scope of the Test + * + * The scope of the test: + * - verify the validity of the address structs. + * - verify that address manipulation API works. + * - simple socket creation and destruction. + * - simple socket send/recv and sendto/recvfrom. + * - UDP connect() + * - send/recv big data. + * - all for both UDP and TCP. + * + * The APIs tested in this test: + * - pj_inet_aton() + * - pj_inet_ntoa() + * - pj_inet_pton() (only if IPv6 is enabled) + * - pj_inet_ntop() (only if IPv6 is enabled) + * - pj_gethostname() + * - pj_sock_socket() + * - pj_sock_close() + * - pj_sock_send() + * - pj_sock_sendto() + * - pj_sock_recv() + * - pj_sock_recvfrom() + * - pj_sock_bind() + * - pj_sock_connect() + * - pj_sock_listen() + * - pj_sock_accept() + * - pj_gethostbyname() + * + * + * This file is <b>pjlib-test/sock.c</b> + * + * \include pjlib-test/sock.c + */ + +#if INCLUDE_SOCK_TEST + +#define UDP_PORT 51234 +#define TCP_PORT (UDP_PORT+10) +#define BIG_DATA_LEN 8192 +#define ADDRESS "127.0.0.1" + +static char bigdata[BIG_DATA_LEN]; +static char bigbuffer[BIG_DATA_LEN]; + +/* Macro for checking the value of "sin_len" member of sockaddr + * (it must always be zero). + */ +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN!=0 +# define CHECK_SA_ZERO_LEN(addr, ret) \ + if (((pj_addr_hdr*)(addr))->sa_zero_len != 0) \ + return ret +#else +# define CHECK_SA_ZERO_LEN(addr, ret) +#endif + + +static int format_test(void) +{ + pj_str_t s = pj_str(ADDRESS); + unsigned char *p; + pj_in_addr addr; + char zero[64]; + pj_sockaddr_in addr2; + const pj_str_t *hostname; + const unsigned char A[] = {127, 0, 0, 1}; + + PJ_LOG(3,("test", "...format_test()")); + + /* pj_inet_aton() */ + if (pj_inet_aton(&s, &addr) != 1) + return -10; + + /* Check the result. */ + p = (unsigned char*)&addr; + if (p[0]!=A[0] || p[1]!=A[1] || p[2]!=A[2] || p[3]!=A[3]) { + PJ_LOG(3,("test", " error: mismatched address. p0=%d, p1=%d, " + "p2=%d, p3=%d", p[0] & 0xFF, p[1] & 0xFF, + p[2] & 0xFF, p[3] & 0xFF)); + return -15; + } + + /* pj_inet_ntoa() */ + p = (unsigned char*) pj_inet_ntoa(addr); + if (!p) + return -20; + + if (pj_strcmp2(&s, (char*)p) != 0) + return -22; + +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6!=0 + /* pj_inet_pton() */ + /* pj_inet_ntop() */ + { + const pj_str_t s_ipv4 = pj_str("127.0.0.1"); + const pj_str_t s_ipv6 = pj_str("fe80::2ff:83ff:fe7c:8b42"); + char buf_ipv4[PJ_INET_ADDRSTRLEN]; + char buf_ipv6[PJ_INET6_ADDRSTRLEN]; + pj_in_addr ipv4; + pj_in6_addr ipv6; + + if (pj_inet_pton(pj_AF_INET(), &s_ipv4, &ipv4) != PJ_SUCCESS) + return -24; + + p = (unsigned char*)&ipv4; + if (p[0]!=A[0] || p[1]!=A[1] || p[2]!=A[2] || p[3]!=A[3]) { + return -25; + } + + if (pj_inet_pton(pj_AF_INET6(), &s_ipv6, &ipv6) != PJ_SUCCESS) + return -26; + + p = (unsigned char*)&ipv6; + if (p[0] != 0xfe || p[1] != 0x80 || p[2] != 0 || p[3] != 0 || + p[4] != 0 || p[5] != 0 || p[6] != 0 || p[7] != 0 || + p[8] != 0x02 || p[9] != 0xff || p[10] != 0x83 || p[11] != 0xff || + p[12]!=0xfe || p[13]!=0x7c || p[14] != 0x8b || p[15]!=0x42) + { + return -27; + } + + if (pj_inet_ntop(pj_AF_INET(), &ipv4, buf_ipv4, sizeof(buf_ipv4)) != PJ_SUCCESS) + return -28; + if (pj_stricmp2(&s_ipv4, buf_ipv4) != 0) + return -29; + + if (pj_inet_ntop(pj_AF_INET6(), &ipv6, buf_ipv6, sizeof(buf_ipv6)) != PJ_SUCCESS) + return -30; + if (pj_stricmp2(&s_ipv6, buf_ipv6) != 0) + return -31; + } + +#endif /* PJ_HAS_IPV6 */ + + /* Test that pj_sockaddr_in_init() initialize the whole structure, + * including sin_zero. + */ + pj_sockaddr_in_init(&addr2, 0, 1000); + pj_bzero(zero, sizeof(zero)); + if (pj_memcmp(addr2.sin_zero, zero, sizeof(addr2.sin_zero)) != 0) + return -35; + + /* pj_gethostname() */ + hostname = pj_gethostname(); + if (!hostname || !hostname->ptr || !hostname->slen) + return -40; + + PJ_LOG(3,("test", "....hostname is %.*s", + (int)hostname->slen, hostname->ptr)); + + /* pj_gethostaddr() */ + + /* Various constants */ +#if !defined(PJ_SYMBIAN) || PJ_SYMBIAN==0 + if (PJ_AF_INET==0xFFFF) return -5500; + if (PJ_AF_INET6==0xFFFF) return -5501; + + /* 0xFFFF could be a valid SOL_SOCKET (e.g: on some Win or Mac) */ + //if (PJ_SOL_SOCKET==0xFFFF) return -5503; + + if (PJ_SOL_IP==0xFFFF) return -5502; + if (PJ_SOL_TCP==0xFFFF) return -5510; + if (PJ_SOL_UDP==0xFFFF) return -5520; + if (PJ_SOL_IPV6==0xFFFF) return -5530; + + if (PJ_SO_TYPE==0xFFFF) return -5540; + if (PJ_SO_RCVBUF==0xFFFF) return -5550; + if (PJ_SO_SNDBUF==0xFFFF) return -5560; + if (PJ_TCP_NODELAY==0xFFFF) return -5570; + if (PJ_SO_REUSEADDR==0xFFFF) return -5580; + + if (PJ_MSG_OOB==0xFFFF) return -5590; + if (PJ_MSG_PEEK==0xFFFF) return -5600; +#endif + + return 0; +} + +static int parse_test(void) +{ +#define IPv4 1 +#define IPv6 2 + + struct test_t { + const char *input; + int result_af; + const char *result_ip; + pj_uint16_t result_port; + }; + struct test_t valid_tests[] = + { + /* IPv4 */ + { "10.0.0.1:80", IPv4, "10.0.0.1", 80}, + { "10.0.0.1", IPv4, "10.0.0.1", 0}, + { "10.0.0.1:", IPv4, "10.0.0.1", 0}, + { "10.0.0.1:0", IPv4, "10.0.0.1", 0}, + { ":80", IPv4, "0.0.0.0", 80}, + { ":", IPv4, "0.0.0.0", 0}, +#if !PJ_SYMBIAN + { "localhost", IPv4, "127.0.0.1", 0}, + { "localhost:", IPv4, "127.0.0.1", 0}, + { "localhost:80", IPv4, "127.0.0.1", 80}, +#endif + +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 + { "fe::01:80", IPv6, "fe::01:80", 0}, + { "[fe::01]:80", IPv6, "fe::01", 80}, + { "fe::01", IPv6, "fe::01", 0}, + { "[fe::01]", IPv6, "fe::01", 0}, + { "fe::01:", IPv6, "fe::01", 0}, + { "[fe::01]:", IPv6, "fe::01", 0}, + { "::", IPv6, "::0", 0}, + { "[::]", IPv6, "::", 0}, + { ":::", IPv6, "::", 0}, + { "[::]:", IPv6, "::", 0}, + { ":::80", IPv6, "::", 80}, + { "[::]:80", IPv6, "::", 80}, +#endif + }; + struct test_t invalid_tests[] = + { + /* IPv4 */ + { "10.0.0.1:abcd", IPv4}, /* port not numeric */ + { "10.0.0.1:-1", IPv4}, /* port contains illegal character */ + { "10.0.0.1:123456", IPv4}, /* port too big */ + { "1.2.3.4.5:80", IPv4}, /* invalid IP */ + { "10:0:80", IPv4}, /* hostname has colon */ + +#if defined(PJ_HAS_IPV6) && PJ_HAS_IPV6 + { "[fe::01]:abcd", IPv6}, /* port not numeric */ + { "[fe::01]:-1", IPv6}, /* port contains illegal character */ + { "[fe::01]:123456", IPv6}, /* port too big */ + { "fe::01:02::03:04:80", IPv6}, /* invalid IP */ + { "[fe::01:02::03:04]:80", IPv6}, /* invalid IP */ + { "[fe:01", IPv6}, /* Unterminated bracket */ +#endif + }; + + unsigned i; + + PJ_LOG(3,("test", "...IP address parsing")); + + for (i=0; i<PJ_ARRAY_SIZE(valid_tests); ++i) { + pj_status_t status; + pj_str_t input; + pj_sockaddr addr, result; + + switch (valid_tests[i].result_af) { + case IPv4: + valid_tests[i].result_af = PJ_AF_INET; + break; + case IPv6: + valid_tests[i].result_af = PJ_AF_INET6; + break; + default: + pj_assert(!"Invalid AF!"); + continue; + } + + /* Try parsing with PJ_AF_UNSPEC */ + status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0, + pj_cstr(&input, valid_tests[i].input), + &addr); + if (status != PJ_SUCCESS) { + PJ_LOG(1,("test", ".... failed when parsing %s (i=%d)", + valid_tests[i].input, i)); + return -10; + } + + /* Check "sin_len" member of parse result */ + CHECK_SA_ZERO_LEN(&addr, -20); + + /* Build the correct result */ + status = pj_sockaddr_init(valid_tests[i].result_af, + &result, + pj_cstr(&input, valid_tests[i].result_ip), + valid_tests[i].result_port); + if (status != PJ_SUCCESS) { + PJ_LOG(1,("test", ".... error building IP address %s", + valid_tests[i].input)); + return -30; + } + + /* Compare the result */ + if (pj_sockaddr_cmp(&addr, &result) != 0) { + PJ_LOG(1,("test", ".... parsed result mismatched for %s", + valid_tests[i].input)); + return -40; + } + + /* Parse again with the specified af */ + status = pj_sockaddr_parse(valid_tests[i].result_af, 0, + pj_cstr(&input, valid_tests[i].input), + &addr); + if (status != PJ_SUCCESS) { + PJ_LOG(1,("test", ".... failed when parsing %s", + valid_tests[i].input)); + return -50; + } + + /* Check "sin_len" member of parse result */ + CHECK_SA_ZERO_LEN(&addr, -55); + + /* Compare the result again */ + if (pj_sockaddr_cmp(&addr, &result) != 0) { + PJ_LOG(1,("test", ".... parsed result mismatched for %s", + valid_tests[i].input)); + return -60; + } + } + + for (i=0; i<PJ_ARRAY_SIZE(invalid_tests); ++i) { + pj_status_t status; + pj_str_t input; + pj_sockaddr addr; + + switch (invalid_tests[i].result_af) { + case IPv4: + invalid_tests[i].result_af = PJ_AF_INET; + break; + case IPv6: + invalid_tests[i].result_af = PJ_AF_INET6; + break; + default: + pj_assert(!"Invalid AF!"); + continue; + } + + /* Try parsing with PJ_AF_UNSPEC */ + status = pj_sockaddr_parse(PJ_AF_UNSPEC, 0, + pj_cstr(&input, invalid_tests[i].input), + &addr); + if (status == PJ_SUCCESS) { + PJ_LOG(1,("test", ".... expecting failure when parsing %s", + invalid_tests[i].input)); + return -100; + } + } + + return 0; +} + +static int purity_test(void) +{ + PJ_LOG(3,("test", "...purity_test()")); + +#if defined(PJ_SOCKADDR_HAS_LEN) && PJ_SOCKADDR_HAS_LEN!=0 + /* Check on "sin_len" member of sockaddr */ + { + const pj_str_t str_ip = {"1.1.1.1", 7}; + pj_sockaddr addr[16]; + pj_addrinfo ai[16]; + unsigned cnt; + pj_status_t rc; + + /* pj_enum_ip_interface() */ + cnt = PJ_ARRAY_SIZE(addr); + rc = pj_enum_ip_interface(pj_AF_UNSPEC(), &cnt, addr); + if (rc == PJ_SUCCESS) { + while (cnt--) + CHECK_SA_ZERO_LEN(&addr[cnt], -10); + } + + /* pj_gethostip() on IPv4 */ + rc = pj_gethostip(pj_AF_INET(), &addr[0]); + if (rc == PJ_SUCCESS) + CHECK_SA_ZERO_LEN(&addr[0], -20); + + /* pj_gethostip() on IPv6 */ + rc = pj_gethostip(pj_AF_INET6(), &addr[0]); + if (rc == PJ_SUCCESS) + CHECK_SA_ZERO_LEN(&addr[0], -30); + + /* pj_getdefaultipinterface() on IPv4 */ + rc = pj_getdefaultipinterface(pj_AF_INET(), &addr[0]); + if (rc == PJ_SUCCESS) + CHECK_SA_ZERO_LEN(&addr[0], -40); + + /* pj_getdefaultipinterface() on IPv6 */ + rc = pj_getdefaultipinterface(pj_AF_INET6(), &addr[0]); + if (rc == PJ_SUCCESS) + CHECK_SA_ZERO_LEN(&addr[0], -50); + + /* pj_getaddrinfo() on a host name */ + cnt = PJ_ARRAY_SIZE(ai); + rc = pj_getaddrinfo(pj_AF_UNSPEC(), pj_gethostname(), &cnt, ai); + if (rc == PJ_SUCCESS) { + while (cnt--) + CHECK_SA_ZERO_LEN(&ai[cnt].ai_addr, -60); + } + + /* pj_getaddrinfo() on an IP address */ + cnt = PJ_ARRAY_SIZE(ai); + rc = pj_getaddrinfo(pj_AF_UNSPEC(), &str_ip, &cnt, ai); + if (rc == PJ_SUCCESS) { + pj_assert(cnt == 1); + CHECK_SA_ZERO_LEN(&ai[0].ai_addr, -70); + } + } +#endif + + return 0; +} + +static int simple_sock_test(void) +{ + int types[2]; + pj_sock_t sock; + int i; + pj_status_t rc = PJ_SUCCESS; + + types[0] = pj_SOCK_STREAM(); + types[1] = pj_SOCK_DGRAM(); + + PJ_LOG(3,("test", "...simple_sock_test()")); + + for (i=0; i<(int)(sizeof(types)/sizeof(types[0])); ++i) { + + rc = pj_sock_socket(pj_AF_INET(), types[i], 0, &sock); + if (rc != PJ_SUCCESS) { + app_perror("...error: unable to create socket", rc); + break; + } else { + rc = pj_sock_close(sock); + if (rc != 0) { + app_perror("...error: close socket", rc); + break; + } + } + } + return rc; +} + + +static int send_recv_test(int sock_type, + pj_sock_t ss, pj_sock_t cs, + pj_sockaddr_in *dstaddr, pj_sockaddr_in *srcaddr, + int addrlen) +{ + enum { DATA_LEN = 16 }; + char senddata[DATA_LEN+4], recvdata[DATA_LEN+4]; + pj_ssize_t sent, received, total_received; + pj_status_t rc; + + TRACE_(("test", "....create_random_string()")); + pj_create_random_string(senddata, DATA_LEN); + senddata[DATA_LEN-1] = '\0'; + + /* + * Test send/recv small data. + */ + TRACE_(("test", "....sendto()")); + if (dstaddr) { + sent = DATA_LEN; + rc = pj_sock_sendto(cs, senddata, &sent, 0, dstaddr, addrlen); + if (rc != PJ_SUCCESS || sent != DATA_LEN) { + app_perror("...sendto error", rc); + rc = -140; goto on_error; + } + } else { + sent = DATA_LEN; + rc = pj_sock_send(cs, senddata, &sent, 0); + if (rc != PJ_SUCCESS || sent != DATA_LEN) { + app_perror("...send error", rc); + rc = -145; goto on_error; + } + } + + TRACE_(("test", "....recv()")); + if (srcaddr) { + pj_sockaddr_in addr; + int srclen = sizeof(addr); + + pj_bzero(&addr, sizeof(addr)); + + received = DATA_LEN; + rc = pj_sock_recvfrom(ss, recvdata, &received, 0, &addr, &srclen); + if (rc != PJ_SUCCESS || received != DATA_LEN) { + app_perror("...recvfrom error", rc); + rc = -150; goto on_error; + } + if (srclen != addrlen) + return -151; + if (pj_sockaddr_cmp(&addr, srcaddr) != 0) { + char srcaddr_str[32], addr_str[32]; + strcpy(srcaddr_str, pj_inet_ntoa(srcaddr->sin_addr)); + strcpy(addr_str, pj_inet_ntoa(addr.sin_addr)); + PJ_LOG(3,("test", "...error: src address mismatch (original=%s, " + "recvfrom addr=%s)", + srcaddr_str, addr_str)); + return -152; + } + + } else { + /* Repeat recv() until all data is received. + * This applies only for non-UDP of course, since for UDP + * we would expect all data to be received in one packet. + */ + total_received = 0; + do { + received = DATA_LEN-total_received; + rc = pj_sock_recv(ss, recvdata+total_received, &received, 0); + if (rc != PJ_SUCCESS) { + app_perror("...recv error", rc); + rc = -155; goto on_error; + } + if (received <= 0) { + PJ_LOG(3,("", "...error: socket has closed! (received=%d)", + received)); + rc = -156; goto on_error; + } + if (received != DATA_LEN-total_received) { + if (sock_type != pj_SOCK_STREAM()) { + PJ_LOG(3,("", "...error: expecting %u bytes, got %u bytes", + DATA_LEN-total_received, received)); + rc = -157; goto on_error; + } + } + total_received += received; + } while (total_received < DATA_LEN); + } + + TRACE_(("test", "....memcmp()")); + if (pj_memcmp(senddata, recvdata, DATA_LEN) != 0) { + PJ_LOG(3,("","...error: received data mismatch " + "(got:'%s' expecting:'%s'", + recvdata, senddata)); + rc = -160; goto on_error; + } + + /* + * Test send/recv big data. + */ + TRACE_(("test", "....sendto()")); + if (dstaddr) { + sent = BIG_DATA_LEN; + rc = pj_sock_sendto(cs, bigdata, &sent, 0, dstaddr, addrlen); + if (rc != PJ_SUCCESS || sent != BIG_DATA_LEN) { + app_perror("...sendto error", rc); + rc = -161; goto on_error; + } + } else { + sent = BIG_DATA_LEN; + rc = pj_sock_send(cs, bigdata, &sent, 0); + if (rc != PJ_SUCCESS || sent != BIG_DATA_LEN) { + app_perror("...send error", rc); + rc = -165; goto on_error; + } + } + + TRACE_(("test", "....recv()")); + + /* Repeat recv() until all data is received. + * This applies only for non-UDP of course, since for UDP + * we would expect all data to be received in one packet. + */ + total_received = 0; + do { + received = BIG_DATA_LEN-total_received; + rc = pj_sock_recv(ss, bigbuffer+total_received, &received, 0); + if (rc != PJ_SUCCESS) { + app_perror("...recv error", rc); + rc = -170; goto on_error; + } + if (received <= 0) { + PJ_LOG(3,("", "...error: socket has closed! (received=%d)", + received)); + rc = -173; goto on_error; + } + if (received != BIG_DATA_LEN-total_received) { + if (sock_type != pj_SOCK_STREAM()) { + PJ_LOG(3,("", "...error: expecting %u bytes, got %u bytes", + BIG_DATA_LEN-total_received, received)); + rc = -176; goto on_error; + } + } + total_received += received; + } while (total_received < BIG_DATA_LEN); + + TRACE_(("test", "....memcmp()")); + if (pj_memcmp(bigdata, bigbuffer, BIG_DATA_LEN) != 0) { + PJ_LOG(3,("", "...error: received data has been altered!")); + rc = -180; goto on_error; + } + + rc = 0; + +on_error: + return rc; +} + +static int udp_test(void) +{ + pj_sock_t cs = PJ_INVALID_SOCKET, ss = PJ_INVALID_SOCKET; + pj_sockaddr_in dstaddr, srcaddr; + pj_str_t s; + pj_status_t rc = 0, retval; + + PJ_LOG(3,("test", "...udp_test()")); + + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &ss); + if (rc != 0) { + app_perror("...error: unable to create socket", rc); + return -100; + } + + rc = pj_sock_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, &cs); + if (rc != 0) + return -110; + + /* Bind server socket. */ + pj_bzero(&dstaddr, sizeof(dstaddr)); + dstaddr.sin_family = pj_AF_INET(); + dstaddr.sin_port = pj_htons(UDP_PORT); + dstaddr.sin_addr = pj_inet_addr(pj_cstr(&s, ADDRESS)); + + if ((rc=pj_sock_bind(ss, &dstaddr, sizeof(dstaddr))) != 0) { + app_perror("...bind error udp:"ADDRESS, rc); + rc = -120; goto on_error; + } + + /* Bind client socket. */ + pj_bzero(&srcaddr, sizeof(srcaddr)); + srcaddr.sin_family = pj_AF_INET(); + srcaddr.sin_port = pj_htons(UDP_PORT-1); + srcaddr.sin_addr = pj_inet_addr(pj_cstr(&s, ADDRESS)); + + if ((rc=pj_sock_bind(cs, &srcaddr, sizeof(srcaddr))) != 0) { + app_perror("...bind error", rc); + rc = -121; goto on_error; + } + + /* Test send/recv, with sendto */ + rc = send_recv_test(pj_SOCK_DGRAM(), ss, cs, &dstaddr, NULL, + sizeof(dstaddr)); + if (rc != 0) + goto on_error; + + /* Test send/recv, with sendto and recvfrom */ + rc = send_recv_test(pj_SOCK_DGRAM(), ss, cs, &dstaddr, + &srcaddr, sizeof(dstaddr)); + if (rc != 0) + goto on_error; + + /* Disable this test on Symbian since UDP connect()/send() failed + * with S60 3rd edition (including MR2). + * See http://www.pjsip.org/trac/ticket/264 + */ +#if !defined(PJ_SYMBIAN) || PJ_SYMBIAN==0 + /* connect() the sockets. */ + rc = pj_sock_connect(cs, &dstaddr, sizeof(dstaddr)); + if (rc != 0) { + app_perror("...connect() error", rc); + rc = -122; goto on_error; + } + + /* Test send/recv with send() */ + rc = send_recv_test(pj_SOCK_DGRAM(), ss, cs, NULL, NULL, 0); + if (rc != 0) + goto on_error; + + /* Test send/recv with send() and recvfrom */ + rc = send_recv_test(pj_SOCK_DGRAM(), ss, cs, NULL, &srcaddr, + sizeof(srcaddr)); + if (rc != 0) + goto on_error; +#endif + +on_error: + retval = rc; + if (cs != PJ_INVALID_SOCKET) { + rc = pj_sock_close(cs); + if (rc != PJ_SUCCESS) { + app_perror("...error in closing socket", rc); + return -1000; + } + } + if (ss != PJ_INVALID_SOCKET) { + rc = pj_sock_close(ss); + if (rc != PJ_SUCCESS) { + app_perror("...error in closing socket", rc); + return -1010; + } + } + + return retval; +} + +static int tcp_test(void) +{ + pj_sock_t cs, ss; + pj_status_t rc = 0, retval; + + PJ_LOG(3,("test", "...tcp_test()")); + + rc = app_socketpair(pj_AF_INET(), pj_SOCK_STREAM(), 0, &ss, &cs); + if (rc != PJ_SUCCESS) { + app_perror("...error: app_socketpair():", rc); + return -2000; + } + + /* Test send/recv with send() and recv() */ + retval = send_recv_test(pj_SOCK_STREAM(), ss, cs, NULL, NULL, 0); + + rc = pj_sock_close(cs); + if (rc != PJ_SUCCESS) { + app_perror("...error in closing socket", rc); + return -2000; + } + + rc = pj_sock_close(ss); + if (rc != PJ_SUCCESS) { + app_perror("...error in closing socket", rc); + return -2010; + } + + return retval; +} + +static int ioctl_test(void) +{ + return 0; +} + +static int gethostbyname_test(void) +{ + pj_str_t host; + pj_hostent he; + pj_status_t status; + + /* Testing pj_gethostbyname() with invalid host */ + host = pj_str("an-invalid-host-name"); + status = pj_gethostbyname(&host, &he); + + /* Must return failure! */ + if (status == PJ_SUCCESS) + return -20100; + else + return 0; +} + +#if 0 +#include "../pj/os_symbian.h" +static int connect_test() +{ + RSocketServ rSockServ; + RSocket rSock; + TInetAddr inetAddr; + TRequestStatus reqStatus; + char buffer[16]; + TPtrC8 data((const TUint8*)buffer, (TInt)sizeof(buffer)); + int rc; + + rc = rSockServ.Connect(); + if (rc != KErrNone) + return rc; + + rc = rSock.Open(rSockServ, KAfInet, KSockDatagram, KProtocolInetUdp); + if (rc != KErrNone) + { + rSockServ.Close(); + return rc; + } + + inetAddr.Init(KAfInet); + inetAddr.Input(_L("127.0.0.1")); + inetAddr.SetPort(80); + + rSock.Connect(inetAddr, reqStatus); + User::WaitForRequest(reqStatus); + + if (reqStatus != KErrNone) { + rSock.Close(); + rSockServ.Close(); + return rc; + } + + rSock.Send(data, 0, reqStatus); + User::WaitForRequest(reqStatus); + + if (reqStatus!=KErrNone) { + rSock.Close(); + rSockServ.Close(); + return rc; + } + + rSock.Close(); + rSockServ.Close(); + return KErrNone; +} +#endif + +int sock_test() +{ + int rc; + + pj_create_random_string(bigdata, BIG_DATA_LEN); + +// Enable this to demonstrate the error witn S60 3rd Edition MR2 +#if 0 + rc = connect_test(); + if (rc != 0) + return rc; +#endif + + rc = format_test(); + if (rc != 0) + return rc; + + rc = parse_test(); + if (rc != 0) + return rc; + + rc = purity_test(); + if (rc != 0) + return rc; + + rc = gethostbyname_test(); + if (rc != 0) + return rc; + + rc = simple_sock_test(); + if (rc != 0) + return rc; + + rc = ioctl_test(); + if (rc != 0) + return rc; + + rc = udp_test(); + if (rc != 0) + return rc; + + rc = tcp_test(); + if (rc != 0) + return rc; + + return 0; +} + + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_sock_test; +#endif /* INCLUDE_SOCK_TEST */ + diff --git a/pjlib/src/pjlib-test/sock_perf.c b/pjlib/src/pjlib-test/sock_perf.c new file mode 100644 index 0000000..3225bcd --- /dev/null +++ b/pjlib/src/pjlib-test/sock_perf.c @@ -0,0 +1,190 @@ +/* $Id: sock_perf.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.h> +#include <pj/compat/high_precision.h> + + +/** + * \page page_pjlib_sock_perf_test Test: Socket Performance + * + * Test the performance of the socket communication. This will perform + * simple producer-consumer type of test, where we calculate how long + * does it take to send certain number of packets from producer to + * consumer. + * + * This file is <b>pjlib-test/sock_perf.c</b> + * + * \include pjlib-test/sock_perf.c + */ + +#if INCLUDE_SOCK_PERF_TEST + +/* + * sock_producer_consumer() + * + * Simple producer-consumer benchmarking. Send loop number of + * buf_size size packets as fast as possible. + */ +static int sock_producer_consumer(int sock_type, + unsigned buf_size, + unsigned loop, + unsigned *p_bandwidth) +{ + pj_sock_t consumer, producer; + pj_pool_t *pool; + char *outgoing_buffer, *incoming_buffer; + pj_timestamp start, stop; + unsigned i; + pj_highprec_t elapsed, bandwidth; + pj_size_t total_received; + pj_status_t rc; + + /* Create pool. */ + pool = pj_pool_create(mem, NULL, 4096, 4096, NULL); + if (!pool) + return -10; + + /* Create producer-consumer pair. */ + rc = app_socketpair(pj_AF_INET(), sock_type, 0, &consumer, &producer); + if (rc != PJ_SUCCESS) { + app_perror("...error: create socket pair", rc); + return -20; + } + + /* Create buffers. */ + outgoing_buffer = (char*) pj_pool_alloc(pool, buf_size); + incoming_buffer = (char*) pj_pool_alloc(pool, buf_size); + + /* Start loop. */ + pj_get_timestamp(&start); + total_received = 0; + for (i=0; i<loop; ++i) { + pj_ssize_t sent, part_received, received; + pj_time_val delay; + + sent = buf_size; + rc = pj_sock_send(producer, outgoing_buffer, &sent, 0); + if (rc != PJ_SUCCESS || sent != (pj_ssize_t)buf_size) { + app_perror("...error: send()", rc); + return -61; + } + + /* Repeat recv() until all data is part_received. + * This applies only for non-UDP of course, since for UDP + * we would expect all data to be part_received in one packet. + */ + received = 0; + do { + part_received = buf_size-received; + rc = pj_sock_recv(consumer, incoming_buffer+received, + &part_received, 0); + if (rc != PJ_SUCCESS) { + app_perror("...recv error", rc); + return -70; + } + if (part_received <= 0) { + PJ_LOG(3,("", "...error: socket has closed (part_received=%d)!", + part_received)); + return -73; + } + if ((pj_size_t)part_received != buf_size-received) { + if (sock_type != pj_SOCK_STREAM()) { + PJ_LOG(3,("", "...error: expecting %u bytes, got %u bytes", + buf_size-received, part_received)); + return -76; + } + } + received += part_received; + } while ((pj_size_t)received < buf_size); + + total_received += received; + + /* Stop test if it's been runnign for more than 10 secs. */ + pj_get_timestamp(&stop); + delay = pj_elapsed_time(&start, &stop); + if (delay.sec > 10) + break; + } + + /* Stop timer. */ + pj_get_timestamp(&stop); + + elapsed = pj_elapsed_usec(&start, &stop); + + /* bandwidth = total_received * 1000 / elapsed */ + bandwidth = total_received; + pj_highprec_mul(bandwidth, 1000); + pj_highprec_div(bandwidth, elapsed); + + *p_bandwidth = (pj_uint32_t)bandwidth; + + /* Close sockets. */ + pj_sock_close(consumer); + pj_sock_close(producer); + + /* Done */ + pj_pool_release(pool); + + return 0; +} + +/* + * sock_perf_test() + * + * Main test entry. + */ +int sock_perf_test(void) +{ + enum { LOOP = 64 * 1024 }; + int rc; + unsigned bandwidth; + + PJ_LOG(3,("", "...benchmarking socket " + "(2 sockets, packet=512, single threaded):")); + + /* Disable this test on Symbian since UDP connect()/send() failed + * with S60 3rd edition (including MR2). + * See http://www.pjsip.org/trac/ticket/264 + */ +#if !defined(PJ_SYMBIAN) || PJ_SYMBIAN==0 + /* Benchmarking UDP */ + rc = sock_producer_consumer(pj_SOCK_DGRAM(), 512, LOOP, &bandwidth); + if (rc != 0) return rc; + PJ_LOG(3,("", "....bandwidth UDP = %d KB/s", bandwidth)); +#endif + + /* Benchmarking TCP */ + rc = sock_producer_consumer(pj_SOCK_STREAM(), 512, LOOP, &bandwidth); + if (rc != 0) return rc; + PJ_LOG(3,("", "....bandwidth TCP = %d KB/s", bandwidth)); + + return rc; +} + + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_sock_perf_test; +#endif /* INCLUDE_SOCK_PERF_TEST */ + + diff --git a/pjlib/src/pjlib-test/ssl_sock.c b/pjlib/src/pjlib-test/ssl_sock.c new file mode 100644 index 0000000..d05a4db --- /dev/null +++ b/pjlib/src/pjlib-test/ssl_sock.c @@ -0,0 +1,1424 @@ +/* $Id: ssl_sock.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.h> + + +#define CERT_DIR "../build/" +#define CERT_CA_FILE CERT_DIR "cacert.pem" +#define CERT_FILE CERT_DIR "cacert.pem" +#define CERT_PRIVKEY_FILE CERT_DIR "privkey.pem" +#define CERT_PRIVKEY_PASS "" + + +#if INCLUDE_SSLSOCK_TEST + +/* Global vars */ +static int clients_num; + +struct send_key { + pj_ioqueue_op_key_t op_key; +}; + + +static int get_cipher_list(void) { + pj_status_t status; + pj_ssl_cipher ciphers[100]; + unsigned cipher_num; + unsigned i; + + cipher_num = PJ_ARRAY_SIZE(ciphers); + status = pj_ssl_cipher_get_availables(ciphers, &cipher_num); + if (status != PJ_SUCCESS) { + app_perror("...FAILED to get available ciphers", status); + return status; + } + + PJ_LOG(3, ("", "...Found %u ciphers:", cipher_num)); + for (i = 0; i < cipher_num; ++i) { + const char* st; + st = pj_ssl_cipher_name(ciphers[i]); + if (st == NULL) + st = "[Unknown]"; + + PJ_LOG(3, ("", "...%3u: 0x%08x=%s", i+1, ciphers[i], st)); + } + + return PJ_SUCCESS; +} + + +struct test_state +{ + pj_pool_t *pool; /* pool */ + pj_ioqueue_t *ioqueue; /* ioqueue */ + pj_bool_t is_server; /* server role flag */ + pj_bool_t is_verbose; /* verbose flag, e.g: cert info */ + pj_bool_t echo; /* echo received data */ + pj_status_t err; /* error flag */ + unsigned sent; /* bytes sent */ + unsigned recv; /* bytes received */ + pj_uint8_t read_buf[256]; /* read buffer */ + pj_bool_t done; /* test done flag */ + char *send_str; /* data to send once connected */ + unsigned send_str_len; /* send data length */ + pj_bool_t check_echo; /* flag to compare sent & echoed data */ + const char *check_echo_ptr; /* pointer/cursor for comparing data */ + struct send_key send_key; /* send op key */ +}; + +static void dump_ssl_info(const pj_ssl_sock_info *si) +{ + const char *tmp_st; + + /* Print cipher name */ + tmp_st = pj_ssl_cipher_name(si->cipher); + if (tmp_st == NULL) + tmp_st = "[Unknown]"; + PJ_LOG(3, ("", ".....Cipher: %s", tmp_st)); + + /* Print remote certificate info and verification result */ + if (si->remote_cert_info && si->remote_cert_info->subject.info.slen) + { + char buf[2048]; + const char *verif_msgs[32]; + unsigned verif_msg_cnt; + + /* Dump remote TLS certificate info */ + PJ_LOG(3, ("", ".....Remote certificate info:")); + pj_ssl_cert_info_dump(si->remote_cert_info, " ", buf, sizeof(buf)); + PJ_LOG(3,("", "\n%s", buf)); + + /* Dump remote TLS certificate verification result */ + verif_msg_cnt = PJ_ARRAY_SIZE(verif_msgs); + pj_ssl_cert_get_verify_status_strings(si->verify_status, + verif_msgs, &verif_msg_cnt); + PJ_LOG(3,("", ".....Remote certificate verification result: %s", + (verif_msg_cnt == 1? verif_msgs[0]:""))); + if (verif_msg_cnt > 1) { + unsigned i; + for (i = 0; i < verif_msg_cnt; ++i) + PJ_LOG(3,("", "..... - %s", verif_msgs[i])); + } + } +} + + +static pj_bool_t ssl_on_connect_complete(pj_ssl_sock_t *ssock, + pj_status_t status) +{ + struct test_state *st = (struct test_state*) + pj_ssl_sock_get_user_data(ssock); + void *read_buf[1]; + pj_ssl_sock_info info; + char buf1[64], buf2[64]; + + if (status != PJ_SUCCESS) { + app_perror("...ERROR ssl_on_connect_complete()", status); + goto on_return; + } + + status = pj_ssl_sock_get_info(ssock, &info); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_get_info()", status); + goto on_return; + } + + pj_sockaddr_print((pj_sockaddr_t*)&info.local_addr, buf1, sizeof(buf1), 1); + pj_sockaddr_print((pj_sockaddr_t*)&info.remote_addr, buf2, sizeof(buf2), 1); + PJ_LOG(3, ("", "...Connected %s -> %s!", buf1, buf2)); + + if (st->is_verbose) + dump_ssl_info(&info); + + /* Start reading data */ + read_buf[0] = st->read_buf; + status = pj_ssl_sock_start_read2(ssock, st->pool, sizeof(st->read_buf), (void**)read_buf, 0); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_start_read2()", status); + goto on_return; + } + + /* Start sending data */ + while (st->sent < st->send_str_len) { + pj_ssize_t size; + + size = st->send_str_len - st->sent; + status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key, + st->send_str + st->sent, &size, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + app_perror("...ERROR pj_ssl_sock_send()", status); + goto on_return; + } + + if (status == PJ_SUCCESS) + st->sent += size; + else + break; + } + +on_return: + st->err = status; + + if (st->err != PJ_SUCCESS) { + pj_ssl_sock_close(ssock); + clients_num--; + return PJ_FALSE; + } + + return PJ_TRUE; +} + + +static pj_bool_t ssl_on_accept_complete(pj_ssl_sock_t *ssock, + pj_ssl_sock_t *newsock, + const pj_sockaddr_t *src_addr, + int src_addr_len) +{ + struct test_state *parent_st = (struct test_state*) + pj_ssl_sock_get_user_data(ssock); + struct test_state *st; + void *read_buf[1]; + pj_ssl_sock_info info; + char buf[64]; + pj_status_t status; + + PJ_UNUSED_ARG(src_addr_len); + + /* Duplicate parent test state to newly accepted test state */ + st = (struct test_state*)pj_pool_zalloc(parent_st->pool, sizeof(struct test_state)); + *st = *parent_st; + pj_ssl_sock_set_user_data(newsock, st); + + status = pj_ssl_sock_get_info(newsock, &info); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_get_info()", status); + goto on_return; + } + + pj_sockaddr_print(src_addr, buf, sizeof(buf), 1); + PJ_LOG(3, ("", "...Accepted connection from %s", buf)); + + if (st->is_verbose) + dump_ssl_info(&info); + + /* Start reading data */ + read_buf[0] = st->read_buf; + status = pj_ssl_sock_start_read2(newsock, st->pool, sizeof(st->read_buf), (void**)read_buf, 0); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_start_read2()", status); + goto on_return; + } + + /* Start sending data */ + while (st->sent < st->send_str_len) { + pj_ssize_t size; + + size = st->send_str_len - st->sent; + status = pj_ssl_sock_send(newsock, (pj_ioqueue_op_key_t*)&st->send_key, + st->send_str + st->sent, &size, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + app_perror("...ERROR pj_ssl_sock_send()", status); + goto on_return; + } + + if (status == PJ_SUCCESS) + st->sent += size; + else + break; + } + +on_return: + st->err = status; + + if (st->err != PJ_SUCCESS) { + pj_ssl_sock_close(newsock); + return PJ_FALSE; + } + + return PJ_TRUE; +} + +static pj_bool_t ssl_on_data_read(pj_ssl_sock_t *ssock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder) +{ + struct test_state *st = (struct test_state*) + pj_ssl_sock_get_user_data(ssock); + + PJ_UNUSED_ARG(remainder); + PJ_UNUSED_ARG(data); + + if (size > 0) { + pj_size_t consumed; + + /* Set random remainder */ + *remainder = pj_rand() % 100; + + /* Apply zero remainder if: + * - remainder is less than size, or + * - connection closed/error + * - echo/check_eco set + */ + if (*remainder > size || status != PJ_SUCCESS || st->echo || st->check_echo) + *remainder = 0; + + consumed = size - *remainder; + st->recv += consumed; + + //printf("%.*s", consumed, (char*)data); + + pj_memmove(data, (char*)data + consumed, *remainder); + + /* Echo data when specified to */ + if (st->echo) { + pj_ssize_t size_ = consumed; + status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key, data, &size_, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + app_perror("...ERROR pj_ssl_sock_send()", status); + goto on_return; + } + + if (status == PJ_SUCCESS) + st->sent += size_; + } + + /* Verify echoed data when specified to */ + if (st->check_echo) { + if (!st->check_echo_ptr) + st->check_echo_ptr = st->send_str; + + if (pj_memcmp(st->check_echo_ptr, data, consumed)) { + status = PJ_EINVAL; + app_perror("...ERROR echoed data not exact", status); + goto on_return; + } + st->check_echo_ptr += consumed; + + /* Echo received completely */ + if (st->send_str_len == st->recv) { + pj_ssl_sock_info info; + char buf[64]; + + status = pj_ssl_sock_get_info(ssock, &info); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_get_info()", status); + goto on_return; + } + + pj_sockaddr_print((pj_sockaddr_t*)&info.local_addr, buf, sizeof(buf), 1); + PJ_LOG(3, ("", "...%s successfully recv %d bytes echo", buf, st->recv)); + st->done = PJ_TRUE; + } + } + } + + if (status != PJ_SUCCESS) { + if (status == PJ_EEOF) { + status = PJ_SUCCESS; + st->done = PJ_TRUE; + } else { + app_perror("...ERROR ssl_on_data_read()", status); + } + } + +on_return: + st->err = status; + + if (st->err != PJ_SUCCESS || st->done) { + pj_ssl_sock_close(ssock); + if (!st->is_server) + clients_num--; + return PJ_FALSE; + } + + return PJ_TRUE; +} + +static pj_bool_t ssl_on_data_sent(pj_ssl_sock_t *ssock, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t sent) +{ + struct test_state *st = (struct test_state*) + pj_ssl_sock_get_user_data(ssock); + PJ_UNUSED_ARG(op_key); + + if (sent < 0) { + st->err = -sent; + } else { + st->sent += sent; + + /* Send more if any */ + while (st->sent < st->send_str_len) { + pj_ssize_t size; + pj_status_t status; + + size = st->send_str_len - st->sent; + status = pj_ssl_sock_send(ssock, (pj_ioqueue_op_key_t*)&st->send_key, + st->send_str + st->sent, &size, 0); + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + app_perror("...ERROR pj_ssl_sock_send()", status); + st->err = status; + break; + } + + if (status == PJ_SUCCESS) + st->sent += size; + else + break; + } + } + + if (st->err != PJ_SUCCESS) { + pj_ssl_sock_close(ssock); + if (!st->is_server) + clients_num--; + return PJ_FALSE; + } + + return PJ_TRUE; +} + +#define HTTP_REQ "GET / HTTP/1.0\r\n\r\n"; +#define HTTP_SERVER_ADDR "trac.pjsip.org" +#define HTTP_SERVER_PORT 443 + +static int https_client_test(unsigned ms_timeout) +{ + pj_pool_t *pool = NULL; + pj_ioqueue_t *ioqueue = NULL; + pj_timer_heap_t *timer = NULL; + pj_ssl_sock_t *ssock = NULL; + pj_ssl_sock_param param; + pj_status_t status; + struct test_state state = {0}; + pj_sockaddr local_addr, rem_addr; + pj_str_t tmp_st; + + pool = pj_pool_create(mem, "https_get", 256, 256, NULL); + + status = pj_ioqueue_create(pool, 4, &ioqueue); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_timer_heap_create(pool, 4, &timer); + if (status != PJ_SUCCESS) { + goto on_return; + } + + state.pool = pool; + state.send_str = HTTP_REQ; + state.send_str_len = pj_ansi_strlen(state.send_str); + state.is_verbose = PJ_TRUE; + + pj_ssl_sock_param_default(¶m); + param.cb.on_connect_complete = &ssl_on_connect_complete; + param.cb.on_data_read = &ssl_on_data_read; + param.cb.on_data_sent = &ssl_on_data_sent; + param.ioqueue = ioqueue; + param.user_data = &state; + param.server_name = pj_str((char*)HTTP_SERVER_ADDR); + param.timer_heap = timer; + param.timeout.sec = 0; + param.timeout.msec = ms_timeout; + param.proto = PJ_SSL_SOCK_PROTO_SSL23; + pj_time_val_normalize(¶m.timeout); + + status = pj_ssl_sock_create(pool, ¶m, &ssock); + if (status != PJ_SUCCESS) { + goto on_return; + } + + pj_sockaddr_init(PJ_AF_INET, &local_addr, pj_strset2(&tmp_st, "0.0.0.0"), 0); + pj_sockaddr_init(PJ_AF_INET, &rem_addr, pj_strset2(&tmp_st, HTTP_SERVER_ADDR), HTTP_SERVER_PORT); + status = pj_ssl_sock_start_connect(ssock, pool, &local_addr, &rem_addr, sizeof(rem_addr)); + if (status == PJ_SUCCESS) { + ssl_on_connect_complete(ssock, PJ_SUCCESS); + } else if (status == PJ_EPENDING) { + status = PJ_SUCCESS; + } else { + goto on_return; + } + + /* Wait until everything has been sent/received */ + while (state.err == PJ_SUCCESS && !state.done) { +#ifdef PJ_SYMBIAN + pj_symbianos_poll(-1, 1000); +#else + pj_time_val delay = {0, 100}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer, &delay); +#endif + } + + if (state.err) { + status = state.err; + goto on_return; + } + + PJ_LOG(3, ("", "...Done!")); + PJ_LOG(3, ("", ".....Sent/recv: %d/%d bytes", state.sent, state.recv)); + +on_return: + if (ssock && !state.err && !state.done) + pj_ssl_sock_close(ssock); + if (ioqueue) + pj_ioqueue_destroy(ioqueue); + if (timer) + pj_timer_heap_destroy(timer); + if (pool) + pj_pool_release(pool); + + return status; +} + + +static int echo_test(pj_ssl_sock_proto srv_proto, pj_ssl_sock_proto cli_proto, + pj_ssl_cipher srv_cipher, pj_ssl_cipher cli_cipher, + pj_bool_t req_client_cert, pj_bool_t client_provide_cert) +{ + pj_pool_t *pool = NULL; + pj_ioqueue_t *ioqueue = NULL; + pj_ssl_sock_t *ssock_serv = NULL; + pj_ssl_sock_t *ssock_cli = NULL; + pj_ssl_sock_param param; + struct test_state state_serv = { 0 }; + struct test_state state_cli = { 0 }; + pj_sockaddr addr, listen_addr; + pj_ssl_cipher ciphers[1]; + pj_ssl_cert_t *cert = NULL; + pj_status_t status; + + pool = pj_pool_create(mem, "ssl_echo", 256, 256, NULL); + + status = pj_ioqueue_create(pool, 4, &ioqueue); + if (status != PJ_SUCCESS) { + goto on_return; + } + + pj_ssl_sock_param_default(¶m); + param.cb.on_accept_complete = &ssl_on_accept_complete; + param.cb.on_connect_complete = &ssl_on_connect_complete; + param.cb.on_data_read = &ssl_on_data_read; + param.cb.on_data_sent = &ssl_on_data_sent; + param.ioqueue = ioqueue; + param.ciphers = ciphers; + + /* Init default bind address */ + { + pj_str_t tmp_st; + pj_sockaddr_init(PJ_AF_INET, &addr, pj_strset2(&tmp_st, "127.0.0.1"), 0); + } + + /* === SERVER === */ + param.proto = srv_proto; + param.user_data = &state_serv; + param.ciphers_num = (srv_cipher == -1)? 0 : 1; + param.require_client_cert = req_client_cert; + ciphers[0] = srv_cipher; + + state_serv.pool = pool; + state_serv.echo = PJ_TRUE; + state_serv.is_server = PJ_TRUE; + state_serv.is_verbose = PJ_TRUE; + + status = pj_ssl_sock_create(pool, ¶m, &ssock_serv); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Set server cert */ + { + pj_str_t tmp1, tmp2, tmp3, tmp4; + + status = pj_ssl_cert_load_from_files(pool, + pj_strset2(&tmp1, (char*)CERT_CA_FILE), + pj_strset2(&tmp2, (char*)CERT_FILE), + pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE), + pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS), + &cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } + + status = pj_ssl_sock_start_accept(ssock_serv, pool, &addr, pj_sockaddr_get_len(&addr)); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Get listener address */ + { + pj_ssl_sock_info info; + + pj_ssl_sock_get_info(ssock_serv, &info); + pj_sockaddr_cp(&listen_addr, &info.local_addr); + } + + /* === CLIENT === */ + param.proto = cli_proto; + param.user_data = &state_cli; + param.ciphers_num = (cli_cipher == -1)? 0 : 1; + ciphers[0] = cli_cipher; + + state_cli.pool = pool; + state_cli.check_echo = PJ_TRUE; + state_cli.is_verbose = PJ_TRUE; + + { + pj_time_val now; + + pj_gettimeofday(&now); + pj_srand((unsigned)now.sec); + state_cli.send_str_len = (pj_rand() % 5 + 1) * 1024 + pj_rand() % 1024; + } + state_cli.send_str = (char*)pj_pool_alloc(pool, state_cli.send_str_len); + { + unsigned i; + for (i = 0; i < state_cli.send_str_len; ++i) + state_cli.send_str[i] = (char)(pj_rand() % 256); + } + + status = pj_ssl_sock_create(pool, ¶m, &ssock_cli); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Set cert for client */ + { + + if (!client_provide_cert) { + pj_str_t tmp1, tmp2; + + pj_strset2(&tmp1, (char*)CERT_CA_FILE); + pj_strset2(&tmp2, NULL); + status = pj_ssl_cert_load_from_files(pool, + &tmp1, &tmp2, &tmp2, &tmp2, + &cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } + + status = pj_ssl_sock_set_certificate(ssock_cli, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } + + status = pj_ssl_sock_start_connect(ssock_cli, pool, &addr, &listen_addr, pj_sockaddr_get_len(&addr)); + if (status == PJ_SUCCESS) { + ssl_on_connect_complete(ssock_cli, PJ_SUCCESS); + } else if (status == PJ_EPENDING) { + status = PJ_SUCCESS; + } else { + goto on_return; + } + + /* Wait until everything has been sent/received or error */ + while (!state_serv.err && !state_cli.err && !state_serv.done && !state_cli.done) + { +#ifdef PJ_SYMBIAN + pj_symbianos_poll(-1, 1000); +#else + pj_time_val delay = {0, 100}; + pj_ioqueue_poll(ioqueue, &delay); +#endif + } + + /* Clean up sockets */ + { + pj_time_val delay = {0, 100}; + while (pj_ioqueue_poll(ioqueue, &delay) > 0); + } + + if (state_serv.err || state_cli.err) { + if (state_serv.err != PJ_SUCCESS) + status = state_serv.err; + else + status = state_cli.err; + + goto on_return; + } + + PJ_LOG(3, ("", "...Done!")); + PJ_LOG(3, ("", ".....Sent/recv: %d/%d bytes", state_cli.sent, state_cli.recv)); + +on_return: + if (ssock_serv) + pj_ssl_sock_close(ssock_serv); + if (ssock_cli && !state_cli.err && !state_cli.done) + pj_ssl_sock_close(ssock_cli); + if (ioqueue) + pj_ioqueue_destroy(ioqueue); + if (pool) + pj_pool_release(pool); + + return status; +} + + +static pj_bool_t asock_on_data_read(pj_activesock_t *asock, + void *data, + pj_size_t size, + pj_status_t status, + pj_size_t *remainder) +{ + struct test_state *st = (struct test_state*) + pj_activesock_get_user_data(asock); + + PJ_UNUSED_ARG(data); + PJ_UNUSED_ARG(size); + PJ_UNUSED_ARG(remainder); + + if (status != PJ_SUCCESS) { + if (status == PJ_EEOF) { + status = PJ_SUCCESS; + st->done = PJ_TRUE; + } else { + app_perror("...ERROR asock_on_data_read()", status); + } + } + + st->err = status; + + if (st->err != PJ_SUCCESS || st->done) { + pj_activesock_close(asock); + if (!st->is_server) + clients_num--; + return PJ_FALSE; + } + + return PJ_TRUE; +} + + +static pj_bool_t asock_on_connect_complete(pj_activesock_t *asock, + pj_status_t status) +{ + struct test_state *st = (struct test_state*) + pj_activesock_get_user_data(asock); + + if (status == PJ_SUCCESS) { + void *read_buf[1]; + + /* Start reading data */ + read_buf[0] = st->read_buf; + status = pj_activesock_start_read2(asock, st->pool, sizeof(st->read_buf), (void**)read_buf, 0); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_start_read2()", status); + } + } + + st->err = status; + + if (st->err != PJ_SUCCESS) { + pj_activesock_close(asock); + if (!st->is_server) + clients_num--; + return PJ_FALSE; + } + + return PJ_TRUE; +} + +static pj_bool_t asock_on_accept_complete(pj_activesock_t *asock, + pj_sock_t newsock, + const pj_sockaddr_t *src_addr, + int src_addr_len) +{ + struct test_state *st; + void *read_buf[1]; + pj_activesock_t *new_asock; + pj_activesock_cb asock_cb = { 0 }; + pj_status_t status; + + PJ_UNUSED_ARG(src_addr); + PJ_UNUSED_ARG(src_addr_len); + + st = (struct test_state*) pj_activesock_get_user_data(asock); + + asock_cb.on_data_read = &asock_on_data_read; + status = pj_activesock_create(st->pool, newsock, pj_SOCK_STREAM(), NULL, + st->ioqueue, &asock_cb, st, &new_asock); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Start reading data */ + read_buf[0] = st->read_buf; + status = pj_activesock_start_read2(new_asock, st->pool, + sizeof(st->read_buf), + (void**)read_buf, 0); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_start_read2()", status); + } + +on_return: + st->err = status; + + if (st->err != PJ_SUCCESS) + pj_activesock_close(new_asock); + + return PJ_TRUE; +} + + +/* Raw TCP socket try to connect to SSL socket server, once + * connection established, it will just do nothing, SSL socket + * server should be able to close the connection after specified + * timeout period (set ms_timeout to 0 to disable timer). + */ +static int client_non_ssl(unsigned ms_timeout) +{ + pj_pool_t *pool = NULL; + pj_ioqueue_t *ioqueue = NULL; + pj_timer_heap_t *timer = NULL; + pj_ssl_sock_t *ssock_serv = NULL; + pj_activesock_t *asock_cli = NULL; + pj_activesock_cb asock_cb = { 0 }; + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_ssl_sock_param param; + struct test_state state_serv = { 0 }; + struct test_state state_cli = { 0 }; + pj_sockaddr listen_addr; + pj_ssl_cert_t *cert = NULL; + pj_status_t status; + + pool = pj_pool_create(mem, "ssl_accept_raw_tcp", 256, 256, NULL); + + status = pj_ioqueue_create(pool, 4, &ioqueue); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_timer_heap_create(pool, 4, &timer); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Set cert */ + { + pj_str_t tmp1, tmp2, tmp3, tmp4; + status = pj_ssl_cert_load_from_files(pool, + pj_strset2(&tmp1, (char*)CERT_CA_FILE), + pj_strset2(&tmp2, (char*)CERT_FILE), + pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE), + pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS), + &cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } + + pj_ssl_sock_param_default(¶m); + param.cb.on_accept_complete = &ssl_on_accept_complete; + param.cb.on_data_read = &ssl_on_data_read; + param.cb.on_data_sent = &ssl_on_data_sent; + param.ioqueue = ioqueue; + param.timer_heap = timer; + param.timeout.sec = 0; + param.timeout.msec = ms_timeout; + pj_time_val_normalize(¶m.timeout); + + /* SERVER */ + param.user_data = &state_serv; + state_serv.pool = pool; + state_serv.is_server = PJ_TRUE; + state_serv.is_verbose = PJ_TRUE; + + status = pj_ssl_sock_create(pool, ¶m, &ssock_serv); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Init bind address */ + { + pj_str_t tmp_st; + pj_sockaddr_init(PJ_AF_INET, &listen_addr, pj_strset2(&tmp_st, "127.0.0.1"), 0); + } + + status = pj_ssl_sock_start_accept(ssock_serv, pool, &listen_addr, pj_sockaddr_get_len(&listen_addr)); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Update listener address */ + { + pj_ssl_sock_info info; + + pj_ssl_sock_get_info(ssock_serv, &info); + pj_sockaddr_cp(&listen_addr, &info.local_addr); + } + + /* CLIENT */ + state_cli.pool = pool; + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) { + goto on_return; + } + + asock_cb.on_connect_complete = &asock_on_connect_complete; + asock_cb.on_data_read = &asock_on_data_read; + status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), NULL, + ioqueue, &asock_cb, &state_cli, &asock_cli); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_activesock_start_connect(asock_cli, pool, (pj_sockaddr_t*)&listen_addr, + pj_sockaddr_get_len(&listen_addr)); + if (status == PJ_SUCCESS) { + asock_on_connect_complete(asock_cli, PJ_SUCCESS); + } else if (status == PJ_EPENDING) { + status = PJ_SUCCESS; + } else { + goto on_return; + } + + /* Wait until everything has been sent/received or error */ + while (!state_serv.err && !state_cli.err && !state_serv.done && !state_cli.done) + { +#ifdef PJ_SYMBIAN + pj_symbianos_poll(-1, 1000); +#else + pj_time_val delay = {0, 100}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer, &delay); +#endif + } + + if (state_serv.err || state_cli.err) { + if (state_serv.err != PJ_SUCCESS) + status = state_serv.err; + else + status = state_cli.err; + + goto on_return; + } + + PJ_LOG(3, ("", "...Done!")); + +on_return: + if (ssock_serv) + pj_ssl_sock_close(ssock_serv); + if (asock_cli && !state_cli.err && !state_cli.done) + pj_activesock_close(asock_cli); + if (timer) + pj_timer_heap_destroy(timer); + if (ioqueue) + pj_ioqueue_destroy(ioqueue); + if (pool) + pj_pool_release(pool); + + return status; +} + + +/* SSL socket try to connect to raw TCP socket server, once + * connection established, SSL socket will try to perform SSL + * handshake. SSL client socket should be able to close the + * connection after specified timeout period (set ms_timeout to + * 0 to disable timer). + */ +static int server_non_ssl(unsigned ms_timeout) +{ + pj_pool_t *pool = NULL; + pj_ioqueue_t *ioqueue = NULL; + pj_timer_heap_t *timer = NULL; + pj_activesock_t *asock_serv = NULL; + pj_ssl_sock_t *ssock_cli = NULL; + pj_activesock_cb asock_cb = { 0 }; + pj_sock_t sock = PJ_INVALID_SOCKET; + pj_ssl_sock_param param; + struct test_state state_serv = { 0 }; + struct test_state state_cli = { 0 }; + pj_sockaddr addr, listen_addr; + pj_status_t status; + + pool = pj_pool_create(mem, "ssl_connect_raw_tcp", 256, 256, NULL); + + status = pj_ioqueue_create(pool, 4, &ioqueue); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_timer_heap_create(pool, 4, &timer); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* SERVER */ + state_serv.pool = pool; + state_serv.ioqueue = ioqueue; + + status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Init bind address */ + { + pj_str_t tmp_st; + pj_sockaddr_init(PJ_AF_INET, &listen_addr, pj_strset2(&tmp_st, "127.0.0.1"), 0); + } + + status = pj_sock_bind(sock, (pj_sockaddr_t*)&listen_addr, + pj_sockaddr_get_len((pj_sockaddr_t*)&listen_addr)); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_sock_listen(sock, PJ_SOMAXCONN); + if (status != PJ_SUCCESS) { + goto on_return; + } + + asock_cb.on_accept_complete = &asock_on_accept_complete; + status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), NULL, + ioqueue, &asock_cb, &state_serv, &asock_serv); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_activesock_start_accept(asock_serv, pool); + if (status != PJ_SUCCESS) + goto on_return; + + /* Update listener address */ + { + int addr_len; + + addr_len = sizeof(listen_addr); + pj_sock_getsockname(sock, (pj_sockaddr_t*)&listen_addr, &addr_len); + } + + /* CLIENT */ + pj_ssl_sock_param_default(¶m); + param.cb.on_connect_complete = &ssl_on_connect_complete; + param.cb.on_data_read = &ssl_on_data_read; + param.cb.on_data_sent = &ssl_on_data_sent; + param.ioqueue = ioqueue; + param.timer_heap = timer; + param.timeout.sec = 0; + param.timeout.msec = ms_timeout; + pj_time_val_normalize(¶m.timeout); + param.user_data = &state_cli; + + state_cli.pool = pool; + state_cli.is_server = PJ_FALSE; + state_cli.is_verbose = PJ_TRUE; + + status = pj_ssl_sock_create(pool, ¶m, &ssock_cli); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Init default bind address */ + { + pj_str_t tmp_st; + pj_sockaddr_init(PJ_AF_INET, &addr, pj_strset2(&tmp_st, "127.0.0.1"), 0); + } + + status = pj_ssl_sock_start_connect(ssock_cli, pool, + (pj_sockaddr_t*)&addr, + (pj_sockaddr_t*)&listen_addr, + pj_sockaddr_get_len(&listen_addr)); + if (status != PJ_EPENDING) { + goto on_return; + } + + /* Wait until everything has been sent/received or error */ + while ((!state_serv.err && !state_serv.done) || (!state_cli.err && !state_cli.done)) + { +#ifdef PJ_SYMBIAN + pj_symbianos_poll(-1, 1000); +#else + pj_time_val delay = {0, 100}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer, &delay); +#endif + } + + if (state_serv.err || state_cli.err) { + if (state_cli.err != PJ_SUCCESS) + status = state_cli.err; + else + status = state_serv.err; + + goto on_return; + } + + PJ_LOG(3, ("", "...Done!")); + +on_return: + if (asock_serv) + pj_activesock_close(asock_serv); + if (ssock_cli && !state_cli.err && !state_cli.done) + pj_ssl_sock_close(ssock_cli); + if (timer) + pj_timer_heap_destroy(timer); + if (ioqueue) + pj_ioqueue_destroy(ioqueue); + if (pool) + pj_pool_release(pool); + + return status; +} + + +/* Test will perform multiple clients trying to connect to single server. + * Once SSL connection established, echo test will be performed. + */ +static int perf_test(unsigned clients, unsigned ms_handshake_timeout) +{ + pj_pool_t *pool = NULL; + pj_ioqueue_t *ioqueue = NULL; + pj_timer_heap_t *timer = NULL; + pj_ssl_sock_t *ssock_serv = NULL; + pj_ssl_sock_t **ssock_cli = NULL; + pj_ssl_sock_param param; + struct test_state state_serv = { 0 }; + struct test_state *state_cli = NULL; + pj_sockaddr addr, listen_addr; + pj_ssl_cert_t *cert = NULL; + pj_status_t status; + unsigned i, cli_err = 0, tot_sent = 0, tot_recv = 0; + pj_time_val start; + + pool = pj_pool_create(mem, "ssl_perf", 256, 256, NULL); + + status = pj_ioqueue_create(pool, PJ_IOQUEUE_MAX_HANDLES, &ioqueue); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_timer_heap_create(pool, PJ_IOQUEUE_MAX_HANDLES, &timer); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Set cert */ + { + pj_str_t tmp1, tmp2, tmp3, tmp4; + + status = pj_ssl_cert_load_from_files(pool, + pj_strset2(&tmp1, (char*)CERT_CA_FILE), + pj_strset2(&tmp2, (char*)CERT_FILE), + pj_strset2(&tmp3, (char*)CERT_PRIVKEY_FILE), + pj_strset2(&tmp4, (char*)CERT_PRIVKEY_PASS), + &cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + } + + pj_ssl_sock_param_default(¶m); + param.cb.on_accept_complete = &ssl_on_accept_complete; + param.cb.on_connect_complete = &ssl_on_connect_complete; + param.cb.on_data_read = &ssl_on_data_read; + param.cb.on_data_sent = &ssl_on_data_sent; + param.ioqueue = ioqueue; + param.timer_heap = timer; + param.timeout.sec = 0; + param.timeout.msec = ms_handshake_timeout; + pj_time_val_normalize(¶m.timeout); + + /* Init default bind address */ + { + pj_str_t tmp_st; + pj_sockaddr_init(PJ_AF_INET, &addr, pj_strset2(&tmp_st, "127.0.0.1"), 0); + } + + /* SERVER */ + param.user_data = &state_serv; + + state_serv.pool = pool; + state_serv.echo = PJ_TRUE; + state_serv.is_server = PJ_TRUE; + + status = pj_ssl_sock_create(pool, ¶m, &ssock_serv); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_ssl_sock_set_certificate(ssock_serv, pool, cert); + if (status != PJ_SUCCESS) { + goto on_return; + } + + status = pj_ssl_sock_start_accept(ssock_serv, pool, &addr, pj_sockaddr_get_len(&addr)); + if (status != PJ_SUCCESS) { + goto on_return; + } + + /* Get listening address for clients to connect to */ + { + pj_ssl_sock_info info; + char buf[64]; + + pj_ssl_sock_get_info(ssock_serv, &info); + pj_sockaddr_cp(&listen_addr, &info.local_addr); + + pj_sockaddr_print((pj_sockaddr_t*)&listen_addr, buf, sizeof(buf), 1); + PJ_LOG(3, ("", "...Listener ready at %s", buf)); + } + + + /* CLIENTS */ + clients_num = clients; + param.timeout.sec = 0; + param.timeout.msec = 0; + + /* Init random seed */ + { + pj_time_val now; + + pj_gettimeofday(&now); + pj_srand((unsigned)now.sec); + } + + /* Allocate SSL socket pointers and test state */ + ssock_cli = (pj_ssl_sock_t**)pj_pool_calloc(pool, clients, sizeof(pj_ssl_sock_t*)); + state_cli = (struct test_state*)pj_pool_calloc(pool, clients, sizeof(struct test_state)); + + /* Get start timestamp */ + pj_gettimeofday(&start); + + /* Setup clients */ + for (i = 0; i < clients; ++i) { + param.user_data = &state_cli[i]; + + state_cli[i].pool = pool; + state_cli[i].check_echo = PJ_TRUE; + state_cli[i].send_str_len = (pj_rand() % 5 + 1) * 1024 + pj_rand() % 1024; + state_cli[i].send_str = (char*)pj_pool_alloc(pool, state_cli[i].send_str_len); + { + unsigned j; + for (j = 0; j < state_cli[i].send_str_len; ++j) + state_cli[i].send_str[j] = (char)(pj_rand() % 256); + } + + status = pj_ssl_sock_create(pool, ¶m, &ssock_cli[i]); + if (status != PJ_SUCCESS) { + app_perror("...ERROR pj_ssl_sock_create()", status); + cli_err++; + clients_num--; + continue; + } + + status = pj_ssl_sock_start_connect(ssock_cli[i], pool, &addr, &listen_addr, pj_sockaddr_get_len(&addr)); + if (status == PJ_SUCCESS) { + ssl_on_connect_complete(ssock_cli[i], PJ_SUCCESS); + } else if (status == PJ_EPENDING) { + status = PJ_SUCCESS; + } else { + app_perror("...ERROR pj_ssl_sock_create()", status); + pj_ssl_sock_close(ssock_cli[i]); + ssock_cli[i] = NULL; + clients_num--; + cli_err++; + continue; + } + + /* Give chance to server to accept this client */ + { + unsigned n = 5; + +#ifdef PJ_SYMBIAN + while(n && pj_symbianos_poll(-1, 1000)) + n--; +#else + pj_time_val delay = {0, 100}; + while(n && pj_ioqueue_poll(ioqueue, &delay) > 0) + n--; +#endif + } + } + + /* Wait until everything has been sent/received or error */ + while (clients_num) + { +#ifdef PJ_SYMBIAN + pj_symbianos_poll(-1, 1000); +#else + pj_time_val delay = {0, 100}; + pj_ioqueue_poll(ioqueue, &delay); + pj_timer_heap_poll(timer, &delay); +#endif + } + + /* Clean up sockets */ + { + pj_time_val delay = {0, 500}; + while (pj_ioqueue_poll(ioqueue, &delay) > 0); + } + + if (state_serv.err != PJ_SUCCESS) { + status = state_serv.err; + goto on_return; + } + + PJ_LOG(3, ("", "...Done!")); + + /* SSL setup and data transfer duration */ + { + pj_time_val stop; + + pj_gettimeofday(&stop); + PJ_TIME_VAL_SUB(stop, start); + + PJ_LOG(3, ("", ".....Setup & data transfer duration: %d.%03ds", stop.sec, stop.msec)); + } + + /* Check clients status */ + for (i = 0; i < clients; ++i) { + if (state_cli[i].err != PJ_SUCCESS) + cli_err++; + + tot_sent += state_cli[1].sent; + tot_recv += state_cli[1].recv; + } + + PJ_LOG(3, ("", ".....Clients: %d (%d errors)", clients, cli_err)); + PJ_LOG(3, ("", ".....Total sent/recv: %d/%d bytes", tot_sent, tot_recv)); + +on_return: + if (ssock_serv) + pj_ssl_sock_close(ssock_serv); + + for (i = 0; i < clients; ++i) { + if (ssock_cli[i] && !state_cli[i].err && !state_cli[i].done) + pj_ssl_sock_close(ssock_cli[i]); + } + if (ioqueue) + pj_ioqueue_destroy(ioqueue); + if (pool) + pj_pool_release(pool); + + return status; +} + + +int ssl_sock_test(void) +{ + int ret; + + PJ_LOG(3,("", "..get cipher list test")); + ret = get_cipher_list(); + if (ret != 0) + return ret; + + PJ_LOG(3,("", "..https client test")); + ret = https_client_test(30000); + // Ignore test result as internet connection may not be available. + //if (ret != 0) + //return ret; + +#ifndef PJ_SYMBIAN + + /* On Symbian platforms, SSL socket is implemented using CSecureSocket, + * and it hasn't supported server mode, so exclude the following tests, + * which require SSL server, for now. + */ + + PJ_LOG(3,("", "..echo test w/ TLSv1 and PJ_TLS_RSA_WITH_DES_CBC_SHA cipher")); + ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, PJ_SSL_SOCK_PROTO_TLS1, + PJ_TLS_RSA_WITH_DES_CBC_SHA, PJ_TLS_RSA_WITH_DES_CBC_SHA, + PJ_FALSE, PJ_FALSE); + if (ret != 0) + return ret; + + PJ_LOG(3,("", "..echo test w/ SSLv23 and PJ_TLS_RSA_WITH_AES_256_CBC_SHA cipher")); + ret = echo_test(PJ_SSL_SOCK_PROTO_SSL23, PJ_SSL_SOCK_PROTO_SSL23, + PJ_TLS_RSA_WITH_AES_256_CBC_SHA, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, + PJ_FALSE, PJ_FALSE); + if (ret != 0) + return ret; + + PJ_LOG(3,("", "..echo test w/ incompatible proto")); + ret = echo_test(PJ_SSL_SOCK_PROTO_TLS1, PJ_SSL_SOCK_PROTO_SSL3, + PJ_TLS_RSA_WITH_DES_CBC_SHA, PJ_TLS_RSA_WITH_DES_CBC_SHA, + PJ_FALSE, PJ_FALSE); + if (ret == 0) + return PJ_EBUG; + + PJ_LOG(3,("", "..echo test w/ incompatible ciphers")); + ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, + PJ_TLS_RSA_WITH_DES_CBC_SHA, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, + PJ_FALSE, PJ_FALSE); + if (ret == 0) + return PJ_EBUG; + + PJ_LOG(3,("", "..echo test w/ client cert required but not provided")); + ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, + PJ_TLS_RSA_WITH_AES_256_CBC_SHA, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, + PJ_TRUE, PJ_FALSE); + if (ret == 0) + return PJ_EBUG; + + PJ_LOG(3,("", "..echo test w/ client cert required and provided")); + ret = echo_test(PJ_SSL_SOCK_PROTO_DEFAULT, PJ_SSL_SOCK_PROTO_DEFAULT, + PJ_TLS_RSA_WITH_AES_256_CBC_SHA, PJ_TLS_RSA_WITH_AES_256_CBC_SHA, + PJ_TRUE, PJ_TRUE); + if (ret != 0) + return ret; + + PJ_LOG(3,("", "..performance test")); + ret = perf_test(PJ_IOQUEUE_MAX_HANDLES/2 - 1, 0); + if (ret != 0) + return ret; + + PJ_LOG(3,("", "..client non-SSL (handshake timeout 5 secs)")); + ret = client_non_ssl(5000); + /* PJ_TIMEDOUT won't be returned as accepted socket is deleted silently */ + if (ret != 0) + return ret; + +#endif + + PJ_LOG(3,("", "..server non-SSL (handshake timeout 5 secs)")); + ret = server_non_ssl(5000); + if (ret != PJ_ETIMEDOUT) + return ret; + + return 0; +} + +#else /* INCLUDE_SSLSOCK_TEST */ +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_ssl_sock_test; +#endif /* INCLUDE_SSLSOCK_TEST */ + diff --git a/pjlib/src/pjlib-test/string.c b/pjlib/src/pjlib-test/string.c new file mode 100644 index 0000000..b7f73b9 --- /dev/null +++ b/pjlib/src/pjlib-test/string.c @@ -0,0 +1,438 @@ +/* $Id: string.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 <pj/string.h> +#include <pj/pool.h> +#include <pj/log.h> +#include <pj/os.h> +#include "test.h" + +#define THIS_FILE "string.c" + +/** + * \page page_pjlib_string_test Test: String + * + * This file provides implementation of \b string_test(). It tests the + * functionality of the string API. + * + * \section sleep_test_sec Scope of the Test + * + * API tested: + * - pj_str() + * - pj_strcmp() + * - pj_strcmp2() + * - pj_stricmp() + * - pj_strlen() + * - pj_strncmp() + * - pj_strnicmp() + * - pj_strchr() + * - pj_strdup() + * - pj_strdup2() + * - pj_strcpy() + * - pj_strcat() + * - pj_strtrim() + * - pj_utoa() + * - pj_strtoul() + * - pj_strtoul2() + * - pj_create_random_string() + * - ... and mode.. + * + * This file is <b>pjlib-test/string.c</b> + * + * \include pjlib-test/string.c + */ + +#if INCLUDE_STRING_TEST + +#ifdef _MSC_VER +# pragma warning(disable: 4204) +#endif + +#define HELLO_WORLD "Hello World" +#define HELLO_WORLD_LEN 11 +#define JUST_HELLO "Hello" +#define JUST_HELLO_LEN 5 +#define UL_VALUE 3456789012UL + +#if 1 +/* See if both integers have the same sign */ +PJ_INLINE(int) cmp(const char *expr, int i, int j) +{ + int r = !((i>0 && j>0) || (i<0 && j<0) || (i==0 && j==0)); + if (r) { + PJ_LOG(3,(THIS_FILE," error: %s: expecting %d, got %d", expr, j, i)); + } + return r; +} +#else +/* For strict comparison, must be equal */ +PJ_INLINE(int) cmp(const char *expr, int i, int j) +{ + PJ_UNUSED_ARG(expr); + return i!=j; +} +#endif + +#define C(expr, res) cmp(#expr, expr, res) + +static int stricmp_test(void) +{ +/* This specificly tests and benchmark pj_stricmp(), pj_stricmp_alnum(). + * In addition, it also tests pj_stricmp2(), pj_strnicmp(), and + * pj_strnicmp2(). + */ +#define STRTEST(res,res2,S1,S2,code) \ + do { \ + s1.ptr=S1; s1.slen=(S1)?len:0; \ + s2.ptr=S2; s2.slen=(S2)?len:0; \ + pj_get_timestamp(&t1); \ + if (C(pj_stricmp(&s1,&s2),res)) return code; \ + pj_get_timestamp(&t2); \ + pj_sub_timestamp(&t2, &t1); \ + pj_add_timestamp(&e1, &t2); \ + pj_get_timestamp(&t1); \ + if (C(pj_stricmp_alnum(&s1,&s2),res)) return code-1; \ + pj_get_timestamp(&t2); \ + pj_sub_timestamp(&t2, &t1); \ + pj_add_timestamp(&e2, &t2); \ + if (C(pj_stricmp2(&s1,S2),res2)) return code*10; \ + if (C(pj_strnicmp(&s1,&s2,len),res)) return code*100; \ + if (C(pj_strnicmp2(&s1,S2,len),res)) return code*1000; \ + } while (0) + + char *buf; + pj_str_t s1, s2; + pj_timestamp t1, t2, e1, e2, zero; + pj_uint32_t c1, c2; + int len; + + e1.u32.hi = e1.u32.lo = e2.u32.hi = e2.u32.lo = 0; + + pj_thread_sleep(0); + +#define SNULL 0 + + /* Compare empty strings. */ + len=0; + STRTEST( 0, 0, "","",-500); + STRTEST( 0, 0, SNULL,"",-502); + STRTEST( 0, 0, "",SNULL,-504); + STRTEST( 0, 0, SNULL,SNULL,-506); + STRTEST( 0, -1, "hello","world",-508); + + /* equal, length=1 + * use buffer to simulate non-aligned string. + */ + buf = "a""A"; + len=1; + STRTEST( 0, -1, "a",buf+0,-510); + STRTEST( 0, 0, "a",buf+1,-512); + STRTEST(-1, -1, "O", "P", -514); + STRTEST(-1, -1, SNULL, "a", -516); + STRTEST( 1, 1, "a", SNULL, -518); + + /* equal, length=2 + * use buffer to simulate non-aligned string. + */ + buf = "aa""Aa""aA""AA"; + len=2; + STRTEST( 0, -1, "aa",buf+0,-520); + STRTEST( 0, -1, "aa",buf+2,-522); + STRTEST( 0, -1, "aa",buf+4,-524); + STRTEST( 0, 0, "aa",buf+6,-524); + + /* equal, length=3 + * use buffer to simulate non-aligned string. + */ + buf = "aaa""Aaa""aAa""aaA""AAa""aAA""AaA""AAA"; + len=3; + STRTEST( 0, -1, "aaa",buf+0,-530); + STRTEST( 0, -1, "aaa",buf+3,-532); + STRTEST( 0, -1, "aaa",buf+6,-534); + STRTEST( 0, -1, "aaa",buf+9,-536); + STRTEST( 0, -1, "aaa",buf+12,-538); + STRTEST( 0, -1, "aaa",buf+15,-540); + STRTEST( 0, -1, "aaa",buf+18,-542); + STRTEST( 0, 0, "aaa",buf+21,-534); + + /* equal, length=4 */ + len=4; + STRTEST( 0, 0, "aaaa","aaaa",-540); + STRTEST( 0, 0, "aaaa","Aaaa",-542); + STRTEST( 0, 0, "aaaa","aAaa",-544); + STRTEST( 0, 0, "aaaa","aaAa",-546); + STRTEST( 0, 0, "aaaa","aaaA",-548); + STRTEST( 0, 0, "aaaa","AAaa",-550); + STRTEST( 0, 0, "aaaa","aAAa",-552); + STRTEST( 0, 0, "aaaa","aaAA",-554); + STRTEST( 0, 0, "aaaa","AaAa",-556); + STRTEST( 0, 0, "aaaa","aAaA",-558); + STRTEST( 0, 0, "aaaa","AaaA",-560); + STRTEST( 0, 0, "aaaa","AAAa",-562); + STRTEST( 0, 0, "aaaa","aAAA",-564); + STRTEST( 0, 0, "aaaa","AAaA",-566); + STRTEST( 0, 0, "aaaa","AaAA",-568); + STRTEST( 0, 0, "aaaa","AAAA",-570); + + /* equal, length=5 */ + buf = "aaaAa""AaaaA""AaAaA""AAAAA"; + len=5; + STRTEST( 0, -1, "aaaaa",buf+0,-580); + STRTEST( 0, -1, "aaaaa",buf+5,-582); + STRTEST( 0, -1, "aaaaa",buf+10,-584); + STRTEST( 0, 0, "aaaaa",buf+15,-586); + + /* not equal, length=1 */ + len=1; + STRTEST( -1, -1, "a", "b", -600); + + /* not equal, length=2 */ + buf = "ab""ba"; + len=2; + STRTEST( -1, -1, "aa", buf+0, -610); + STRTEST( -1, -1, "aa", buf+2, -612); + + /* not equal, length=3 */ + buf = "aab""aba""baa"; + len=3; + STRTEST( -1, -1, "aaa", buf+0, -620); + STRTEST( -1, -1, "aaa", buf+3, -622); + STRTEST( -1, -1, "aaa", buf+6, -624); + + /* not equal, length=4 */ + buf = "aaab""aaba""abaa""baaa"; + len=4; + STRTEST( -1, -1, "aaaa", buf+0, -630); + STRTEST( -1, -1, "aaaa", buf+4, -632); + STRTEST( -1, -1, "aaaa", buf+8, -634); + STRTEST( -1, -1, "aaaa", buf+12, -636); + + /* not equal, length=5 */ + buf="aaaab""aaaba""aabaa""abaaa""baaaa"; + len=5; + STRTEST( -1, -1, "aaaaa", buf+0, -640); + STRTEST( -1, -1, "aaaaa", buf+5, -642); + STRTEST( -1, -1, "aaaaa", buf+10, -644); + STRTEST( -1, -1, "aaaaa", buf+15, -646); + STRTEST( -1, -1, "aaaaa", buf+20, -648); + + zero.u32.hi = zero.u32.lo = 0; + c1 = pj_elapsed_cycle(&zero, &e1); + c2 = pj_elapsed_cycle(&zero, &e2); + + if (c1 < c2) { + PJ_LOG(3,("", " info: pj_stricmp_alnum is slower than pj_stricmp!")); + //return -700; + } + + /* Avoid division by zero */ + if (c2 == 0) c2=1; + + PJ_LOG(3, ("", " time: stricmp=%u, stricmp_alnum=%u (speedup=%d.%02dx)", + c1, c2, + (c1 * 100 / c2) / 100, + (c1 * 100 / c2) % 100)); + return 0; +#undef STRTEST +} + +/* This tests pj_strcmp(), pj_strcmp2(), pj_strncmp(), pj_strncmp2() */ +static int strcmp_test(void) +{ +#define STR_TEST(res,S1,S2,code) \ + do { \ + s1.ptr=S1; s1.slen=S1?len:0; \ + s2.ptr=S2; s2.slen=S2?len:0; \ + if (C(pj_strcmp(&s1,&s2),res)) return code; \ + if (C(pj_strcmp2(&s1,S2),res)) return code-1; \ + if (C(pj_strncmp(&s1,&s2,len),res)) return code-2; \ + if (C(pj_strncmp2(&s1,S2,len),res)) return code-3; \ + } while (0) + + pj_str_t s1, s2; + int len; + + /* Test with length == 0 */ + len=0; + STR_TEST(0, "", "", -400); + STR_TEST(0, SNULL, "", -405); + STR_TEST(0, "", SNULL, -410); + STR_TEST(0, SNULL, SNULL, -415); + STR_TEST(0, "hello", "", -420); + STR_TEST(0, "hello", SNULL, -425); + + /* Test with length != 0 */ + len = 2; + STR_TEST(0, "12", "12", -430); + STR_TEST(1, "12", "1", -435); + STR_TEST(-1, "1", "12", -440); + STR_TEST(-1, SNULL, "12", -445); + STR_TEST(1, "12", SNULL, -450); + + return 0; + +#undef STR_TEST +} + +int string_test(void) +{ + const pj_str_t hello_world = { HELLO_WORLD, HELLO_WORLD_LEN }; + const pj_str_t just_hello = { JUST_HELLO, JUST_HELLO_LEN }; + pj_str_t s1, s2, s3, s4, s5; + enum { RCOUNT = 10, RLEN = 16 }; + pj_str_t random[RCOUNT]; + pj_pool_t *pool; + int i; + + pool = pj_pool_create(mem, SNULL, 4096, 0, SNULL); + if (!pool) return -5; + + /* + * pj_str(), pj_strcmp(), pj_stricmp(), pj_strlen(), + * pj_strncmp(), pj_strchr() + */ + s1 = pj_str(HELLO_WORLD); + if (pj_strcmp(&s1, &hello_world) != 0) + return -10; + if (pj_stricmp(&s1, &hello_world) != 0) + return -20; + if (pj_strcmp(&s1, &just_hello) <= 0) + return -30; + if (pj_stricmp(&s1, &just_hello) <= 0) + return -40; + if (pj_strlen(&s1) != strlen(HELLO_WORLD)) + return -50; + if (pj_strncmp(&s1, &hello_world, 5) != 0) + return -60; + if (pj_strnicmp(&s1, &hello_world, 5) != 0) + return -70; + if (pj_strchr(&s1, HELLO_WORLD[1]) != s1.ptr+1) + return -80; + + /* + * pj_strdup() + */ + if (!pj_strdup(pool, &s2, &s1)) + return -100; + if (pj_strcmp(&s1, &s2) != 0) + return -110; + + /* + * pj_strcpy(), pj_strcat() + */ + s3.ptr = (char*) pj_pool_alloc(pool, 256); + if (!s3.ptr) + return -200; + pj_strcpy(&s3, &s2); + pj_strcat(&s3, &just_hello); + + if (pj_strcmp2(&s3, HELLO_WORLD JUST_HELLO) != 0) + return -210; + + /* + * pj_strdup2(), pj_strtrim(). + */ + pj_strdup2(pool, &s4, " " HELLO_WORLD "\t "); + pj_strtrim(&s4); + if (pj_strcmp2(&s4, HELLO_WORLD) != 0) + return -250; + + /* + * pj_utoa() + */ + s5.ptr = (char*) pj_pool_alloc(pool, 16); + if (!s5.ptr) + return -270; + s5.slen = pj_utoa(UL_VALUE, s5.ptr); + + /* + * pj_strtoul() + */ + if (pj_strtoul(&s5) != UL_VALUE) + return -280; + + /* + * pj_strtoul2() + */ + s5 = pj_str("123456"); + + pj_strtoul2(&s5, SNULL, 10); /* Crash test */ + + if (pj_strtoul2(&s5, &s4, 10) != 123456UL) + return -290; + if (s4.slen != 0) + return -291; + if (pj_strtoul2(&s5, &s4, 16) != 0x123456UL) + return -292; + + s5 = pj_str("0123ABCD"); + if (pj_strtoul2(&s5, &s4, 10) != 123) + return -293; + if (s4.slen != 4) + return -294; + if (s4.ptr == SNULL || *s4.ptr != 'A') + return -295; + if (pj_strtoul2(&s5, &s4, 16) != 0x123ABCDUL) + return -296; + if (s4.slen != 0) + return -297; + + /* + * pj_create_random_string() + * Check that no duplicate strings are returned. + */ + for (i=0; i<RCOUNT; ++i) { + int j; + + random[i].ptr = (char*) pj_pool_alloc(pool, RLEN); + if (!random[i].ptr) + return -320; + + random[i].slen = RLEN; + pj_create_random_string(random[i].ptr, RLEN); + + for (j=0; j<i; ++j) { + if (pj_strcmp(&random[i], &random[j])==0) + return -330; + } + } + + /* Done. */ + pj_pool_release(pool); + + /* Case sensitive comparison test. */ + i = strcmp_test(); + if (i != 0) + return i; + + /* Caseless comparison test. */ + i = stricmp_test(); + if (i != 0) + return i; + + return 0; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_string_test; +#endif /* INCLUDE_STRING_TEST */ + diff --git a/pjlib/src/pjlib-test/test.c b/pjlib/src/pjlib-test/test.c new file mode 100644 index 0000000..5c6f57d --- /dev/null +++ b/pjlib/src/pjlib-test/test.c @@ -0,0 +1,234 @@ +/* $Id: 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 "test.h" +#include <pjlib.h> +#ifdef _MSC_VER +# pragma warning(disable:4127) +#endif + +#define DO_TEST(test) do { \ + PJ_LOG(3, ("test", "Running %s...", #test)); \ + rc = test; \ + PJ_LOG(3, ("test", \ + "%s(%d)", \ + (rc ? "..ERROR" : "..success"), rc)); \ + if (rc!=0) goto on_return; \ + } while (0) + + +pj_pool_factory *mem; + +int param_echo_sock_type; +const char *param_echo_server = ECHO_SERVER_ADDRESS; +int param_echo_port = ECHO_SERVER_START_PORT; +int param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC; + +int null_func() +{ + return 0; +} + +int test_inner(void) +{ + pj_caching_pool caching_pool; + const char *filename; + int line; + 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; + } + + //pj_dump_config(); + pj_caching_pool_init( &caching_pool, NULL, 0 ); + +#if INCLUDE_ERRNO_TEST + DO_TEST( errno_test() ); +#endif + +#if INCLUDE_EXCEPTION_TEST + DO_TEST( exception_test() ); +#endif + +#if INCLUDE_OS_TEST + DO_TEST( os_test() ); +#endif + +#if INCLUDE_RAND_TEST + DO_TEST( rand_test() ); +#endif + +#if INCLUDE_LIST_TEST + DO_TEST( list_test() ); +#endif + +#if INCLUDE_POOL_TEST + DO_TEST( pool_test() ); +#endif + +#if INCLUDE_POOL_PERF_TEST + DO_TEST( pool_perf_test() ); +#endif + +#if INCLUDE_STRING_TEST + DO_TEST( string_test() ); +#endif + +#if INCLUDE_FIFOBUF_TEST + DO_TEST( fifobuf_test() ); +#endif + +#if INCLUDE_RBTREE_TEST + DO_TEST( rbtree_test() ); +#endif + +#if INCLUDE_HASH_TEST + DO_TEST( hash_test() ); +#endif + +#if INCLUDE_TIMESTAMP_TEST + DO_TEST( timestamp_test() ); +#endif + +#if INCLUDE_ATOMIC_TEST + DO_TEST( atomic_test() ); +#endif + +#if INCLUDE_MUTEX_TEST + DO_TEST( mutex_test() ); +#endif + +#if INCLUDE_TIMER_TEST + DO_TEST( timer_test() ); +#endif + +#if INCLUDE_SLEEP_TEST + DO_TEST( sleep_test() ); +#endif + +#if INCLUDE_THREAD_TEST + DO_TEST( thread_test() ); +#endif + +#if INCLUDE_SOCK_TEST + DO_TEST( sock_test() ); +#endif + +#if INCLUDE_SOCK_PERF_TEST + DO_TEST( sock_perf_test() ); +#endif + +#if INCLUDE_SELECT_TEST + DO_TEST( select_test() ); +#endif + +#if INCLUDE_UDP_IOQUEUE_TEST + DO_TEST( udp_ioqueue_test() ); +#endif + +#if PJ_HAS_TCP && INCLUDE_TCP_IOQUEUE_TEST + DO_TEST( tcp_ioqueue_test() ); +#endif + +#if INCLUDE_IOQUEUE_PERF_TEST + DO_TEST( ioqueue_perf_test() ); +#endif + +#if INCLUDE_IOQUEUE_UNREG_TEST + DO_TEST( udp_ioqueue_unreg_test() ); +#endif + +#if INCLUDE_ACTIVESOCK_TEST + DO_TEST( activesock_test() ); +#endif + +#if INCLUDE_FILE_TEST + DO_TEST( file_test() ); +#endif + +#if INCLUDE_SSLSOCK_TEST + DO_TEST( ssl_sock_test() ); +#endif + +#if INCLUDE_ECHO_SERVER + //echo_server(); + //echo_srv_sync(); + udp_echo_srv_ioqueue(); + +#elif INCLUDE_ECHO_CLIENT + if (param_echo_sock_type == 0) + param_echo_sock_type = pj_SOCK_DGRAM(); + + echo_client( param_echo_sock_type, + param_echo_server, + param_echo_port); +#endif + + goto on_return; + +on_return: + + pj_caching_pool_destroy( &caching_pool ); + + PJ_LOG(3,("test", "")); + + pj_thread_get_stack_info(pj_thread_this(), &filename, &line); + PJ_LOG(3,("test", "Stack max usage: %u, deepest: %s:%u", + pj_thread_get_stack_max_usage(pj_thread_this()), + filename, line)); + if (rc == 0) + PJ_LOG(3,("test", "Looks like everything is okay!..")); + else + PJ_LOG(3,("test", "Test completed with error(s)")); + + pj_shutdown(); + + return 0; +} + +#include <pj/sock.h> + +int test_main(void) +{ + int i; + PJ_USE_EXCEPTION; + + i = pj_AF_INET(); + + 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/src/pjlib-test/test.h b/pjlib/src/pjlib-test/test.h new file mode 100644 index 0000000..de69ea4 --- /dev/null +++ b/pjlib/src/pjlib-test/test.h @@ -0,0 +1,129 @@ +/* $Id: test.h 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 + */ +#ifndef __PJLIB_TEST_H__ +#define __PJLIB_TEST_H__ + +#include <pj/types.h> + +#define GROUP_LIBC 1 +#define GROUP_OS 1 +#define GROUP_DATA_STRUCTURE 1 +#define GROUP_NETWORK 1 +#if defined(PJ_SYMBIAN) +# define GROUP_FILE 0 +#else +# define GROUP_FILE 1 +#endif + +#define INCLUDE_ERRNO_TEST GROUP_LIBC +#define INCLUDE_TIMESTAMP_TEST GROUP_OS +#define INCLUDE_EXCEPTION_TEST GROUP_LIBC +#define INCLUDE_RAND_TEST GROUP_LIBC +#define INCLUDE_LIST_TEST GROUP_DATA_STRUCTURE +#define INCLUDE_HASH_TEST GROUP_DATA_STRUCTURE +#define INCLUDE_POOL_TEST GROUP_LIBC +#define INCLUDE_POOL_PERF_TEST GROUP_LIBC +#define INCLUDE_STRING_TEST GROUP_DATA_STRUCTURE +#define INCLUDE_FIFOBUF_TEST 0 // GROUP_DATA_STRUCTURE +#define INCLUDE_RBTREE_TEST GROUP_DATA_STRUCTURE +#define INCLUDE_TIMER_TEST GROUP_DATA_STRUCTURE +#define INCLUDE_ATOMIC_TEST GROUP_OS +#define INCLUDE_MUTEX_TEST (PJ_HAS_THREADS && GROUP_OS) +#define INCLUDE_SLEEP_TEST GROUP_OS +#define INCLUDE_OS_TEST GROUP_OS +#define INCLUDE_THREAD_TEST (PJ_HAS_THREADS && GROUP_OS) +#define INCLUDE_SOCK_TEST GROUP_NETWORK +#define INCLUDE_SOCK_PERF_TEST GROUP_NETWORK +#define INCLUDE_SELECT_TEST GROUP_NETWORK +#define INCLUDE_UDP_IOQUEUE_TEST GROUP_NETWORK +#define INCLUDE_TCP_IOQUEUE_TEST GROUP_NETWORK +#define INCLUDE_ACTIVESOCK_TEST GROUP_NETWORK +#define INCLUDE_SSLSOCK_TEST (PJ_HAS_SSL_SOCK && GROUP_NETWORK) +#define INCLUDE_IOQUEUE_PERF_TEST (PJ_HAS_THREADS && GROUP_NETWORK) +#define INCLUDE_IOQUEUE_UNREG_TEST (PJ_HAS_THREADS && GROUP_NETWORK) +#define INCLUDE_FILE_TEST GROUP_FILE + +#define INCLUDE_ECHO_SERVER 0 +#define INCLUDE_ECHO_CLIENT 0 + + +#define ECHO_SERVER_MAX_THREADS 2 +#define ECHO_SERVER_START_PORT 65000 +#define ECHO_SERVER_ADDRESS "compaq.home" +#define ECHO_SERVER_DURATION_MSEC (60*60*1000) + +#define ECHO_CLIENT_MAX_THREADS 6 + +PJ_BEGIN_DECL + +extern int errno_test(void); +extern int timestamp_test(void); +extern int exception_test(void); +extern int rand_test(void); +extern int list_test(void); +extern int hash_test(void); +extern int os_test(void); +extern int pool_test(void); +extern int pool_perf_test(void); +extern int string_test(void); +extern int fifobuf_test(void); +extern int timer_test(void); +extern int rbtree_test(void); +extern int atomic_test(void); +extern int mutex_test(void); +extern int sleep_test(void); +extern int thread_test(void); +extern int sock_test(void); +extern int sock_perf_test(void); +extern int select_test(void); +extern int udp_ioqueue_test(void); +extern int udp_ioqueue_unreg_test(void); +extern int tcp_ioqueue_test(void); +extern int ioqueue_perf_test(void); +extern int activesock_test(void); +extern int file_test(void); +extern int ssl_sock_test(void); + +extern int echo_server(void); +extern int echo_client(int sock_type, const char *server, int port); + +extern int echo_srv_sync(void); +extern int udp_echo_srv_ioqueue(void); +extern int echo_srv_common_loop(pj_atomic_t *bytes_counter); + + +extern pj_pool_factory *mem; + +extern int test_main(void); +extern void app_perror(const char *msg, pj_status_t err); +extern pj_status_t app_socket(int family, int type, int proto, int port, + pj_sock_t *ptr_sock); +extern pj_status_t app_socketpair(int family, int type, int protocol, + pj_sock_t *server, pj_sock_t *client); +extern int null_func(void); + +//#define TRACE_(expr) PJ_LOG(3,expr) +#define TRACE_(expr) +#define HALT(msg) { PJ_LOG(3,(THIS_FILE,"%s halted",msg)); for(;;) sleep(1); } + +PJ_END_DECL + +#endif /* __PJLIB_TEST_H__ */ + diff --git a/pjlib/src/pjlib-test/test_wrap.cpp b/pjlib/src/pjlib-test/test_wrap.cpp new file mode 100644 index 0000000..7341204 --- /dev/null +++ b/pjlib/src/pjlib-test/test_wrap.cpp @@ -0,0 +1,24 @@ +/* $Id: test_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * Copyright (C) 2009-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 + */ + +/* + * This file is a C++ wrapper, see ticket #886 for details. + */ + +#include "test.c" diff --git a/pjlib/src/pjlib-test/thread.c b/pjlib/src/pjlib-test/thread.c new file mode 100644 index 0000000..976317f --- /dev/null +++ b/pjlib/src/pjlib-test/thread.c @@ -0,0 +1,336 @@ +/* $Id: thread.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" + +/** + * \page page_pjlib_thread_test Test: Thread Test + * + * This file contains \a thread_test() definition. + * + * \section thread_test_scope_sec Scope of Test + * This tests: + * - whether PJ_THREAD_SUSPENDED flag works. + * - whether multithreading works. + * - whether thread timeslicing works, and threads have equal + * time-slice proportion. + * + * APIs tested: + * - pj_thread_create() + * - pj_thread_register() + * - pj_thread_this() + * - pj_thread_get_name() + * - pj_thread_destroy() + * - pj_thread_resume() + * - pj_thread_sleep() + * - pj_thread_join() + * - pj_thread_destroy() + * + * + * This file is <b>pjlib-test/thread.c</b> + * + * \include pjlib-test/thread.c + */ +#if INCLUDE_THREAD_TEST + +#include <pjlib.h> + +#define THIS_FILE "thread_test" + +static volatile int quit_flag=0; + +#if 0 +# define TRACE__(args) PJ_LOG(3,args) +#else +# define TRACE__(args) +#endif + + +/* + * The thread's entry point. + * + * Each of the thread mainly will just execute the loop which + * increments a variable. + */ +static void* thread_proc(pj_uint32_t *pcounter) +{ + /* Test that pj_thread_register() works. */ + pj_thread_desc desc; + pj_thread_t *this_thread; + unsigned id; + pj_status_t rc; + + id = *pcounter; + TRACE__((THIS_FILE, " thread %d running..", id)); + + pj_bzero(desc, sizeof(desc)); + + rc = pj_thread_register("thread", desc, &this_thread); + if (rc != PJ_SUCCESS) { + app_perror("...error in pj_thread_register", rc); + return NULL; + } + + /* Test that pj_thread_this() works */ + this_thread = pj_thread_this(); + if (this_thread == NULL) { + PJ_LOG(3,(THIS_FILE, "...error: pj_thread_this() returns NULL!")); + return NULL; + } + + /* Test that pj_thread_get_name() works */ + if (pj_thread_get_name(this_thread) == NULL) { + PJ_LOG(3,(THIS_FILE, "...error: pj_thread_get_name() returns NULL!")); + return NULL; + } + + /* Main loop */ + for (;!quit_flag;) { + (*pcounter)++; + //Must sleep if platform doesn't do time-slicing. + //pj_thread_sleep(0); + } + + TRACE__((THIS_FILE, " thread %d quitting..", id)); + return NULL; +} + +/* + * simple_thread() + */ +static int simple_thread(const char *title, unsigned flags) +{ + pj_pool_t *pool; + pj_thread_t *thread; + pj_status_t rc; + pj_uint32_t counter = 0; + + PJ_LOG(3,(THIS_FILE, "..%s", title)); + + pool = pj_pool_create(mem, NULL, 4000, 4000, NULL); + if (!pool) + return -1000; + + quit_flag = 0; + + TRACE__((THIS_FILE, " Creating thread 0..")); + rc = pj_thread_create(pool, "thread", (pj_thread_proc*)&thread_proc, + &counter, + PJ_THREAD_DEFAULT_STACK_SIZE, + flags, + &thread); + + if (rc != PJ_SUCCESS) { + app_perror("...error: unable to create thread", rc); + return -1010; + } + + TRACE__((THIS_FILE, " Main thread waiting..")); + pj_thread_sleep(1500); + TRACE__((THIS_FILE, " Main thread resuming..")); + + if (flags & PJ_THREAD_SUSPENDED) { + + /* Check that counter is still zero */ + if (counter != 0) { + PJ_LOG(3,(THIS_FILE, "...error: thread is not suspended")); + return -1015; + } + + rc = pj_thread_resume(thread); + if (rc != PJ_SUCCESS) { + app_perror("...error: resume thread error", rc); + return -1020; + } + } + + PJ_LOG(3,(THIS_FILE, "..waiting for thread to quit..")); + + pj_thread_sleep(1500); + + quit_flag = 1; + pj_thread_join(thread); + + pj_pool_release(pool); + + if (counter == 0) { + PJ_LOG(3,(THIS_FILE, "...error: thread is not running")); + return -1025; + } + + PJ_LOG(3,(THIS_FILE, "...%s success", title)); + return PJ_SUCCESS; +} + + +/* + * timeslice_test() + */ +static int timeslice_test(void) +{ + enum { NUM_THREADS = 4 }; + pj_pool_t *pool; + pj_uint32_t counter[NUM_THREADS], lowest, highest, diff; + pj_thread_t *thread[NUM_THREADS]; + unsigned i; + pj_status_t rc; + + quit_flag = 0; + + pool = pj_pool_create(mem, NULL, 4000, 4000, NULL); + if (!pool) + return -10; + + PJ_LOG(3,(THIS_FILE, "..timeslice testing with %d threads", NUM_THREADS)); + + /* Create all threads in suspended mode. */ + for (i=0; i<NUM_THREADS; ++i) { + counter[i] = i; + rc = pj_thread_create(pool, "thread", (pj_thread_proc*)&thread_proc, + &counter[i], + PJ_THREAD_DEFAULT_STACK_SIZE, + PJ_THREAD_SUSPENDED, + &thread[i]); + if (rc!=PJ_SUCCESS) { + app_perror("...ERROR in pj_thread_create()", rc); + return -20; + } + } + + /* Sleep for 1 second. + * The purpose of this is to test whether all threads are suspended. + */ + TRACE__((THIS_FILE, " Main thread waiting..")); + pj_thread_sleep(1000); + TRACE__((THIS_FILE, " Main thread resuming..")); + + /* Check that all counters are still zero. */ + for (i=0; i<NUM_THREADS; ++i) { + if (counter[i] > i) { + PJ_LOG(3,(THIS_FILE, "....ERROR! Thread %d-th is not suspended!", + i)); + return -30; + } + } + + /* Now resume all threads. */ + for (i=0; i<NUM_THREADS; ++i) { + TRACE__((THIS_FILE, " Resuming thread %d [%p]..", i, thread[i])); + rc = pj_thread_resume(thread[i]); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_thread_resume()", rc); + return -40; + } + } + + /* Main thread sleeps for some time to allow threads to run. + * The longer we sleep, the more accurate the calculation will be, + * but it'll make user waits for longer for the test to finish. + */ + TRACE__((THIS_FILE, " Main thread waiting (5s)..")); + pj_thread_sleep(5000); + TRACE__((THIS_FILE, " Main thread resuming..")); + + /* Signal all threads to quit. */ + quit_flag = 1; + + /* Wait until all threads quit, then destroy. */ + for (i=0; i<NUM_THREADS; ++i) { + TRACE__((THIS_FILE, " Main thread joining thread %d [%p]..", + i, thread[i])); + rc = pj_thread_join(thread[i]); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_thread_join()", rc); + return -50; + } + TRACE__((THIS_FILE, " Destroying thread %d [%p]..", i, thread[i])); + rc = pj_thread_destroy(thread[i]); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR in pj_thread_destroy()", rc); + return -60; + } + } + + TRACE__((THIS_FILE, " Main thread calculating time slices..")); + + /* Now examine the value of the counters. + * Check that all threads had equal proportion of processing. + */ + lowest = 0xFFFFFFFF; + highest = 0; + for (i=0; i<NUM_THREADS; ++i) { + if (counter[i] < lowest) + lowest = counter[i]; + if (counter[i] > highest) + highest = counter[i]; + } + + /* Check that all threads are running. */ + if (lowest < 2) { + PJ_LOG(3,(THIS_FILE, "...ERROR: not all threads were running!")); + return -70; + } + + /* The difference between lowest and higest should be lower than 50%. + */ + diff = (highest-lowest)*100 / ((highest+lowest)/2); + if ( diff >= 50) { + PJ_LOG(3,(THIS_FILE, + "...ERROR: thread didn't have equal timeslice!")); + PJ_LOG(3,(THIS_FILE, + ".....lowest counter=%u, highest counter=%u, diff=%u%%", + lowest, highest, diff)); + return -80; + } else { + PJ_LOG(3,(THIS_FILE, + "...info: timeslice diff between lowest & highest=%u%%", + diff)); + } + + pj_pool_release(pool); + return 0; +} + +int thread_test(void) +{ + int rc; + + rc = simple_thread("simple thread test", 0); + if (rc != PJ_SUCCESS) + return rc; + + rc = simple_thread("suspended thread test", PJ_THREAD_SUSPENDED); + if (rc != PJ_SUCCESS) + return rc; + + rc = timeslice_test(); + if (rc != PJ_SUCCESS) + return rc; + + return rc; +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_thread_test; +#endif /* INCLUDE_THREAD_TEST */ + + diff --git a/pjlib/src/pjlib-test/timer.c b/pjlib/src/pjlib-test/timer.c new file mode 100644 index 0000000..371a397 --- /dev/null +++ b/pjlib/src/pjlib-test/timer.c @@ -0,0 +1,196 @@ +/* $Id: timer.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" + +/** + * \page page_pjlib_timer_test Test: Timer + * + * This file provides implementation of \b timer_test(). It tests the + * functionality of the timer heap. + * + * + * This file is <b>pjlib-test/timer.c</b> + * + * \include pjlib-test/timer.c + */ + + +#if INCLUDE_TIMER_TEST + +#include <pjlib.h> + +#define LOOP 16 +#define MIN_COUNT 250 +#define MAX_COUNT (LOOP * MIN_COUNT) +#define MIN_DELAY 2 +#define D (MAX_COUNT / 32000) +#define DELAY (D < MIN_DELAY ? MIN_DELAY : D) +#define THIS_FILE "timer_test" + + +static void timer_callback(pj_timer_heap_t *ht, pj_timer_entry *e) +{ + PJ_UNUSED_ARG(ht); + PJ_UNUSED_ARG(e); +} + +static int test_timer_heap(void) +{ + int i, j; + pj_timer_entry *entry; + pj_pool_t *pool; + pj_timer_heap_t *timer; + pj_time_val delay; + pj_status_t rc; int err=0; + unsigned size, count; + + size = pj_timer_heap_mem_size(MAX_COUNT)+MAX_COUNT*sizeof(pj_timer_entry); + pool = pj_pool_create( mem, NULL, size, 4000, NULL); + if (!pool) { + PJ_LOG(3,("test", "...error: unable to create pool of %u bytes", + size)); + return -10; + } + + entry = (pj_timer_entry*)pj_pool_calloc(pool, MAX_COUNT, sizeof(*entry)); + if (!entry) + return -20; + + for (i=0; i<MAX_COUNT; ++i) { + entry[i].cb = &timer_callback; + } + rc = pj_timer_heap_create(pool, MAX_COUNT, &timer); + if (rc != PJ_SUCCESS) { + app_perror("...error: unable to create timer heap", rc); + return -30; + } + + count = MIN_COUNT; + for (i=0; i<LOOP; ++i) { + int early = 0; + int done=0; + int cancelled=0; + int rc; + pj_timestamp t1, t2, t_sched, t_cancel, t_poll; + pj_time_val now, expire; + + pj_gettimeofday(&now); + pj_srand(now.sec); + t_sched.u32.lo = t_cancel.u32.lo = t_poll.u32.lo = 0; + + // Register timers + for (j=0; j<(int)count; ++j) { + delay.sec = pj_rand() % DELAY; + delay.msec = pj_rand() % 1000; + + // Schedule timer + pj_get_timestamp(&t1); + rc = pj_timer_heap_schedule(timer, &entry[j], &delay); + if (rc != 0) + return -40; + pj_get_timestamp(&t2); + + t_sched.u32.lo += (t2.u32.lo - t1.u32.lo); + + // Poll timers. + pj_get_timestamp(&t1); + rc = pj_timer_heap_poll(timer, NULL); + pj_get_timestamp(&t2); + if (rc > 0) { + t_poll.u32.lo += (t2.u32.lo - t1.u32.lo); + early += rc; + } + } + + // Set the time where all timers should finish + pj_gettimeofday(&expire); + delay.sec = DELAY; + delay.msec = 0; + PJ_TIME_VAL_ADD(expire, delay); + + // Wait unfil all timers finish, cancel some of them. + do { + int index = pj_rand() % count; + pj_get_timestamp(&t1); + rc = pj_timer_heap_cancel(timer, &entry[index]); + pj_get_timestamp(&t2); + if (rc > 0) { + cancelled += rc; + t_cancel.u32.lo += (t2.u32.lo - t1.u32.lo); + } + + pj_gettimeofday(&now); + + pj_get_timestamp(&t1); +#if defined(PJ_SYMBIAN) && PJ_SYMBIAN!=0 + /* On Symbian, we must use OS poll (Active Scheduler poll) since + * timer is implemented using Active Object. + */ + rc = 0; + while (pj_symbianos_poll(-1, 0)) + ++rc; +#else + rc = pj_timer_heap_poll(timer, NULL); +#endif + pj_get_timestamp(&t2); + if (rc > 0) { + done += rc; + t_poll.u32.lo += (t2.u32.lo - t1.u32.lo); + } + + } while (PJ_TIME_VAL_LTE(now, expire)&&pj_timer_heap_count(timer) > 0); + + if (pj_timer_heap_count(timer)) { + PJ_LOG(3, (THIS_FILE, "ERROR: %d timers left", + pj_timer_heap_count(timer))); + ++err; + } + t_sched.u32.lo /= count; + t_cancel.u32.lo /= count; + t_poll.u32.lo /= count; + PJ_LOG(4, (THIS_FILE, + "...ok (count:%d, early:%d, cancelled:%d, " + "sched:%d, cancel:%d poll:%d)", + count, early, cancelled, t_sched.u32.lo, t_cancel.u32.lo, + t_poll.u32.lo)); + + count = count * 2; + if (count > MAX_COUNT) + break; + } + + pj_pool_release(pool); + return err; +} + + +int timer_test() +{ + return test_timer_heap(); +} + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_timer_test; +#endif /* INCLUDE_TIMER_TEST */ + + diff --git a/pjlib/src/pjlib-test/timestamp.c b/pjlib/src/pjlib-test/timestamp.c new file mode 100644 index 0000000..3b04000 --- /dev/null +++ b/pjlib/src/pjlib-test/timestamp.c @@ -0,0 +1,235 @@ +/* $Id: timestamp.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 <pj/os.h> +#include <pj/log.h> +#include <pj/rand.h> + + +/** + * \page page_pjlib_timestamp_test Test: Timestamp + * + * This file provides implementation of timestamp_test() + * + * \section timestamp_test_sec Scope of the Test + * + * This tests whether timestamp API works. + * + * API tested: + * - pj_get_timestamp_freq() + * - pj_get_timestamp() + * - pj_elapsed_usec() + * - PJ_LOG() + * + * + * This file is <b>pjlib-test/timestamp.c</b> + * + * \include pjlib-test/timestamp.c + */ + +#if INCLUDE_TIMESTAMP_TEST + +#define THIS_FILE "timestamp" + +static int timestamp_accuracy() +{ + pj_timestamp freq, t1, t2; + pj_time_val tv1, tv2, tvtmp; + pj_int64_t msec, tics; + pj_int64_t diff; + + PJ_LOG(3,(THIS_FILE, "...testing frequency accuracy (pls wait)")); + + pj_get_timestamp_freq(&freq); + + /* Get the start time */ + pj_gettimeofday(&tvtmp); + do { + pj_gettimeofday(&tv1); + } while (PJ_TIME_VAL_EQ(tvtmp, tv1)); + pj_get_timestamp(&t1); + + /* Sleep for 10 seconds */ + pj_thread_sleep(10000); + + /* Get end time */ + pj_gettimeofday(&tvtmp); + do { + pj_gettimeofday(&tv2); + } while (PJ_TIME_VAL_EQ(tvtmp, tv2)); + pj_get_timestamp(&t2); + + /* Get the elapsed time */ + PJ_TIME_VAL_SUB(tv2, tv1); + msec = PJ_TIME_VAL_MSEC(tv2); + + /* Check that the frequency match the elapsed time */ + tics = t2.u64 - t1.u64; + diff = tics - (msec * freq.u64 / 1000); + if (diff < 0) + diff = -diff; + + /* Only allow 1 msec mismatch */ + if (diff > (pj_int64_t)(freq.u64 / 1000)) { + PJ_LOG(3,(THIS_FILE, "....error: timestamp drifted by %d usec after " + "%d msec", + (pj_uint32_t)(diff * 1000000 / freq.u64), + msec)); + return -2000; + + /* Otherwise just print warning if timestamp drifted by >1 usec */ + } else if (diff > (pj_int64_t)(freq.u64 / 1000000)) { + PJ_LOG(3,(THIS_FILE, "....warning: timestamp drifted by %d usec after " + "%d msec", + (pj_uint32_t)(diff * 1000000 / freq.u64), + msec)); + } else { + PJ_LOG(3,(THIS_FILE, "....good. Timestamp is accurate down to" + " nearest usec.")); + } + + return 0; +} + + +int timestamp_test(void) +{ + enum { CONSECUTIVE_LOOP = 100 }; + volatile unsigned i; + pj_timestamp freq, t1, t2; + pj_time_val tv1, tv2; + unsigned elapsed; + pj_status_t rc; + + PJ_LOG(3,(THIS_FILE, "...Testing timestamp (high res time)")); + + /* Get and display timestamp frequency. */ + if ((rc=pj_get_timestamp_freq(&freq)) != PJ_SUCCESS) { + app_perror("...ERROR: get timestamp freq", rc); + return -1000; + } + + PJ_LOG(3,(THIS_FILE, "....frequency: hiword=%lu loword=%lu", + freq.u32.hi, freq.u32.lo)); + + PJ_LOG(3,(THIS_FILE, "...checking if time can run backwards (pls wait)..")); + + /* + * Check if consecutive readings should yield timestamp value + * that is bigger than previous value. + * First we get the first timestamp. + */ + rc = pj_get_timestamp(&t1); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR: pj_get_timestamp", rc); + return -1001; + } + rc = pj_gettimeofday(&tv1); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR: pj_gettimeofday", rc); + return -1002; + } + for (i=0; i<CONSECUTIVE_LOOP; ++i) { + + pj_thread_sleep(pj_rand() % 100); + + rc = pj_get_timestamp(&t2); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR: pj_get_timestamp", rc); + return -1003; + } + rc = pj_gettimeofday(&tv2); + if (rc != PJ_SUCCESS) { + app_perror("...ERROR: pj_gettimeofday", rc); + return -1004; + } + + /* compare t2 with t1, expecting t2 >= t1. */ + if (t2.u32.hi < t1.u32.hi || + (t2.u32.hi == t1.u32.hi && t2.u32.lo < t1.u32.lo)) + { + PJ_LOG(3,(THIS_FILE, "...ERROR: timestamp run backwards!")); + return -1005; + } + + /* compare tv2 with tv1, expecting tv2 >= tv1. */ + if (PJ_TIME_VAL_LT(tv2, tv1)) { + PJ_LOG(3,(THIS_FILE, "...ERROR: time run backwards!")); + return -1006; + } + } + + /* + * Simple test to time some loop. + */ + PJ_LOG(3,(THIS_FILE, "....testing simple 1000000 loop")); + + + /* Mark start time. */ + if ((rc=pj_get_timestamp(&t1)) != PJ_SUCCESS) { + app_perror("....error: cat't get timestamp", rc); + return -1010; + } + + /* Loop.. */ + for (i=0; i<1000000; ++i) { + /* Try to do something so that smart compilers wont + * remove this silly loop. + */ + null_func(); + } + + pj_thread_sleep(0); + + /* Mark end time. */ + pj_get_timestamp(&t2); + + /* Get elapsed time in usec. */ + elapsed = pj_elapsed_usec(&t1, &t2); + PJ_LOG(3,(THIS_FILE, "....elapsed: %u usec", (unsigned)elapsed)); + + /* See if elapsed time is "reasonable". + * This should be good even on 50Mhz embedded powerpc. + */ + if (elapsed < 1 || elapsed > 1000000) { + PJ_LOG(3,(THIS_FILE, "....error: elapsed time outside window (%u, " + "t1.u32.hi=%u, t1.u32.lo=%u, " + "t2.u32.hi=%u, t2.u32.lo=%u)", + elapsed, + t1.u32.hi, t1.u32.lo, t2.u32.hi, t2.u32.lo)); + return -1030; + } + + /* Testing time/timestamp accuracy */ + rc = timestamp_accuracy(); + if (rc != 0) + return rc; + + return 0; +} + + +#else +/* To prevent warning about "translation unit is empty" + * when this test is disabled. + */ +int dummy_timestamp_test; +#endif /* INCLUDE_TIMESTAMP_TEST */ + diff --git a/pjlib/src/pjlib-test/udp_echo_srv_ioqueue.c b/pjlib/src/pjlib-test/udp_echo_srv_ioqueue.c new file mode 100644 index 0000000..217a8d8 --- /dev/null +++ b/pjlib/src/pjlib-test/udp_echo_srv_ioqueue.c @@ -0,0 +1,214 @@ +/* $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 <pjlib.h> +#include "test.h" + +static pj_ioqueue_key_t *key; +static pj_atomic_t *total_bytes; +static pj_bool_t thread_quit_flag; + +struct op_key +{ + pj_ioqueue_op_key_t op_key_; + struct op_key *peer; + char *buffer; + pj_size_t size; + int is_pending; + pj_status_t last_err; + pj_sockaddr_in addr; + int addrlen; +}; + +static void on_read_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_received) +{ + pj_status_t rc; + struct op_key *recv_rec = (struct op_key *)op_key; + + for (;;) { + struct op_key *send_rec = recv_rec->peer; + recv_rec->is_pending = 0; + + if (bytes_received < 0) { + if (-bytes_received != recv_rec->last_err) { + recv_rec->last_err = -bytes_received; + app_perror("...error receiving data", -bytes_received); + } + } else if (bytes_received == 0) { + /* note: previous error, or write callback */ + } else { + pj_atomic_add(total_bytes, bytes_received); + + if (!send_rec->is_pending) { + pj_ssize_t sent = bytes_received; + pj_memcpy(send_rec->buffer, recv_rec->buffer, bytes_received); + pj_memcpy(&send_rec->addr, &recv_rec->addr, recv_rec->addrlen); + send_rec->addrlen = recv_rec->addrlen; + rc = pj_ioqueue_sendto(key, &send_rec->op_key_, + send_rec->buffer, &sent, 0, + &send_rec->addr, send_rec->addrlen); + send_rec->is_pending = (rc==PJ_EPENDING); + + if (rc!=PJ_SUCCESS && rc!=PJ_EPENDING) { + app_perror("...send error(1)", rc); + } + } + } + + if (!send_rec->is_pending) { + bytes_received = recv_rec->size; + rc = pj_ioqueue_recvfrom(key, &recv_rec->op_key_, + recv_rec->buffer, &bytes_received, 0, + &recv_rec->addr, &recv_rec->addrlen); + recv_rec->is_pending = (rc==PJ_EPENDING); + if (rc == PJ_SUCCESS) { + /* fall through next loop. */ + } else if (rc == PJ_EPENDING) { + /* quit callback. */ + break; + } else { + /* error */ + app_perror("...recv error", rc); + recv_rec->last_err = rc; + + bytes_received = 0; + /* fall through next loop. */ + } + } else { + /* recv will be done when write completion callback is called. */ + break; + } + } +} + +static void on_write_complete(pj_ioqueue_key_t *key, + pj_ioqueue_op_key_t *op_key, + pj_ssize_t bytes_sent) +{ + struct op_key *send_rec = (struct op_key*)op_key; + + if (bytes_sent <= 0) { + pj_status_t rc = -bytes_sent; + if (rc != send_rec->last_err) { + send_rec->last_err = rc; + app_perror("...send error(2)", rc); + } + } + + send_rec->is_pending = 0; + on_read_complete(key, &send_rec->peer->op_key_, 0); +} + +static int worker_thread(void *arg) +{ + pj_ioqueue_t *ioqueue = (pj_ioqueue_t*) arg; + struct op_key read_op, write_op; + char recv_buf[512], send_buf[512]; + pj_ssize_t length; + pj_status_t rc; + + read_op.peer = &write_op; + read_op.is_pending = 0; + read_op.last_err = 0; + read_op.buffer = recv_buf; + read_op.size = sizeof(recv_buf); + read_op.addrlen = sizeof(read_op.addr); + + write_op.peer = &read_op; + write_op.is_pending = 0; + write_op.last_err = 0; + write_op.buffer = send_buf; + write_op.size = sizeof(send_buf); + + length = sizeof(recv_buf); + rc = pj_ioqueue_recvfrom(key, &read_op.op_key_, recv_buf, &length, 0, + &read_op.addr, &read_op.addrlen); + if (rc == PJ_SUCCESS) { + read_op.is_pending = 1; + on_read_complete(key, &read_op.op_key_, length); + } + + while (!thread_quit_flag) { + pj_time_val timeout; + timeout.sec = 0; timeout.msec = 10; + rc = pj_ioqueue_poll(ioqueue, &timeout); + } + return 0; +} + +int udp_echo_srv_ioqueue(void) +{ + pj_pool_t *pool; + pj_sock_t sock; + pj_ioqueue_t *ioqueue; + pj_ioqueue_callback callback; + int i; + pj_thread_t *thread[ECHO_SERVER_MAX_THREADS]; + pj_status_t rc; + + pj_bzero(&callback, sizeof(callback)); + callback.on_read_complete = &on_read_complete; + callback.on_write_complete = &on_write_complete; + + pool = pj_pool_create(mem, NULL, 4000, 4000, NULL); + if (!pool) + return -10; + + rc = pj_ioqueue_create(pool, 2, &ioqueue); + if (rc != PJ_SUCCESS) { + app_perror("...pj_ioqueue_create error", rc); + return -20; + } + + rc = app_socket(pj_AF_INET(), pj_SOCK_DGRAM(), 0, + ECHO_SERVER_START_PORT, &sock); + if (rc != PJ_SUCCESS) { + app_perror("...app_socket error", rc); + return -30; + } + + rc = pj_ioqueue_register_sock(pool, ioqueue, sock, NULL, + &callback, &key); + if (rc != PJ_SUCCESS) { + app_perror("...error registering socket", rc); + return -40; + } + + rc = pj_atomic_create(pool, 0, &total_bytes); + if (rc != PJ_SUCCESS) { + app_perror("...error creating atomic variable", rc); + return -45; + } + + for (i=0; i<ECHO_SERVER_MAX_THREADS; ++i) { + rc = pj_thread_create(pool, NULL, &worker_thread, ioqueue, + PJ_THREAD_DEFAULT_STACK_SIZE, 0, + &thread[i]); + if (rc != PJ_SUCCESS) { + app_perror("...create thread error", rc); + return -50; + } + } + + echo_srv_common_loop(total_bytes); + + return 0; +} diff --git a/pjlib/src/pjlib-test/udp_echo_srv_sync.c b/pjlib/src/pjlib-test/udp_echo_srv_sync.c new file mode 100644 index 0000000..728e0b2 --- /dev/null +++ b/pjlib/src/pjlib-test/udp_echo_srv_sync.c @@ -0,0 +1,167 @@ +/* $Id: udp_echo_srv_sync.c 4108 2012-04-27 01:32:12Z bennylp $ */ +/* + * 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> + +static pj_atomic_t *total_bytes; +static pj_bool_t thread_quit_flag = 0; + +static int worker_thread(void *arg) +{ + pj_sock_t sock = (pj_sock_t)arg; + char buf[512]; + pj_status_t last_recv_err = PJ_SUCCESS, last_write_err = PJ_SUCCESS; + + while (!thread_quit_flag) { + pj_ssize_t len; + pj_status_t rc; + pj_sockaddr_in addr; + int addrlen; + + len = sizeof(buf); + addrlen = sizeof(addr); + rc = pj_sock_recvfrom(sock, buf, &len, 0, &addr, &addrlen); + if (rc != 0) { + if (rc != last_recv_err) { + app_perror("...recv error", rc); + last_recv_err = rc; + } + continue; + } + + pj_atomic_add(total_bytes, len); + + rc = pj_sock_sendto(sock, buf, &len, 0, &addr, addrlen); + if (rc != PJ_SUCCESS) { + if (rc != last_write_err) { + app_perror("...send error", rc); + last_write_err = rc; + } + continue; + } + } + return 0; +} + + +int echo_srv_sync(void) +{ + pj_pool_t *pool; + pj_sock_t sock; + pj_thread_t *thread[ECHO_SERVER_MAX_THREADS]; + pj_status_t rc; + int i; + + pool = pj_pool_create(mem, NULL, 4000, 4000, NULL); + if (!pool) + return -5; + + rc = pj_atomic_create(pool, 0, &total_bytes); + if (rc != PJ_SUCCESS) { + app_perror("...unable to create atomic_var", rc); + return -6; + } + + rc = app_socket(pj_AF_INET(), pj_SOCK_DGRAM(),0, ECHO_SERVER_START_PORT, &sock); + if (rc != PJ_SUCCESS) { + app_perror("...socket error", rc); + return -10; + } + + for (i=0; i<ECHO_SERVER_MAX_THREADS; ++i) { + rc = pj_thread_create(pool, NULL, &worker_thread, (void*)sock, + PJ_THREAD_DEFAULT_STACK_SIZE, 0, + &thread[i]); + if (rc != PJ_SUCCESS) { + app_perror("...unable to create thread", rc); + return -20; + } + } + + PJ_LOG(3,("", "...UDP echo server running with %d threads at port %d", + ECHO_SERVER_MAX_THREADS, ECHO_SERVER_START_PORT)); + PJ_LOG(3,("", "...Press Ctrl-C to abort")); + + echo_srv_common_loop(total_bytes); + return 0; +} + + +int echo_srv_common_loop(pj_atomic_t *bytes_counter) +{ + pj_highprec_t last_received, avg_bw, highest_bw; + pj_time_val last_print; + unsigned count; + const char *ioqueue_name; + + last_received = 0; + pj_gettimeofday(&last_print); + avg_bw = highest_bw = 0; + count = 0; + + ioqueue_name = pj_ioqueue_name(); + + for (;;) { + pj_highprec_t received, cur_received, bw; + unsigned msec; + pj_time_val now, duration; + + pj_thread_sleep(1000); + + received = cur_received = pj_atomic_get(bytes_counter); + cur_received = cur_received - last_received; + + pj_gettimeofday(&now); + duration = now; + PJ_TIME_VAL_SUB(duration, last_print); + msec = PJ_TIME_VAL_MSEC(duration); + + bw = cur_received; + pj_highprec_mul(bw, 1000); + pj_highprec_div(bw, msec); + + last_print = now; + last_received = received; + + avg_bw = avg_bw + bw; + count++; + + PJ_LOG(3,("", "%s UDP (%d threads): %u KB/s (avg=%u KB/s) %s", + ioqueue_name, + ECHO_SERVER_MAX_THREADS, + (unsigned)(bw / 1000), + (unsigned)(avg_bw / count / 1000), + (count==20 ? "<ses avg>" : ""))); + + if (count==20) { + if (avg_bw/count > highest_bw) + highest_bw = avg_bw/count; + + count = 0; + avg_bw = 0; + + PJ_LOG(3,("", "Highest average bandwidth=%u KB/s", + (unsigned)(highest_bw/1000))); + } + } + PJ_UNREACHED(return 0;) +} + + diff --git a/pjlib/src/pjlib-test/util.c b/pjlib/src/pjlib-test/util.c new file mode 100644 index 0000000..32aa938 --- /dev/null +++ b/pjlib/src/pjlib-test/util.c @@ -0,0 +1,140 @@ +/* $Id: util.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.h> + +#define THIS_FILE "util.c" + +void app_perror(const char *msg, pj_status_t rc) +{ + char errbuf[PJ_ERR_MSG_SIZE]; + + PJ_CHECK_STACK(); + + pj_strerror(rc, errbuf, sizeof(errbuf)); + PJ_LOG(3,("test", "%s: [pj_status_t=%d] %s", msg, rc, errbuf)); +} + +#define SERVER 0 +#define CLIENT 1 + +pj_status_t app_socket(int family, int type, int proto, int port, + pj_sock_t *ptr_sock) +{ + pj_sockaddr_in addr; + pj_sock_t sock; + pj_status_t rc; + + rc = pj_sock_socket(family, type, proto, &sock); + if (rc != PJ_SUCCESS) + return rc; + + pj_bzero(&addr, sizeof(addr)); + addr.sin_family = (pj_uint16_t)family; + addr.sin_port = (short)(port!=-1 ? pj_htons((pj_uint16_t)port) : 0); + rc = pj_sock_bind(sock, &addr, sizeof(addr)); + if (rc != PJ_SUCCESS) + return rc; + +#if PJ_HAS_TCP + if (type == pj_SOCK_STREAM()) { + rc = pj_sock_listen(sock, 5); + if (rc != PJ_SUCCESS) + return rc; + } +#endif + + *ptr_sock = sock; + return PJ_SUCCESS; +} + +pj_status_t app_socketpair(int family, int type, int protocol, + pj_sock_t *serverfd, pj_sock_t *clientfd) +{ + int i; + static unsigned short port = 11000; + pj_sockaddr_in addr; + pj_str_t s; + pj_status_t rc = 0; + pj_sock_t sock[2]; + + /* Create both sockets. */ + for (i=0; i<2; ++i) { + rc = pj_sock_socket(family, type, protocol, &sock[i]); + if (rc != PJ_SUCCESS) { + if (i==1) + pj_sock_close(sock[0]); + return rc; + } + } + + /* Retry bind */ + pj_bzero(&addr, sizeof(addr)); + addr.sin_family = pj_AF_INET(); + for (i=0; i<5; ++i) { + addr.sin_port = pj_htons(port++); + rc = pj_sock_bind(sock[SERVER], &addr, sizeof(addr)); + if (rc == PJ_SUCCESS) + break; + } + + if (rc != PJ_SUCCESS) + goto on_error; + + /* For TCP, listen the socket. */ +#if PJ_HAS_TCP + if (type == pj_SOCK_STREAM()) { + rc = pj_sock_listen(sock[SERVER], PJ_SOMAXCONN); + if (rc != PJ_SUCCESS) + goto on_error; + } +#endif + + /* Connect client socket. */ + addr.sin_addr = pj_inet_addr(pj_cstr(&s, "127.0.0.1")); + rc = pj_sock_connect(sock[CLIENT], &addr, sizeof(addr)); + if (rc != PJ_SUCCESS) + goto on_error; + + /* For TCP, must accept(), and get the new socket. */ +#if PJ_HAS_TCP + if (type == pj_SOCK_STREAM()) { + pj_sock_t newserver; + + rc = pj_sock_accept(sock[SERVER], &newserver, NULL, NULL); + if (rc != PJ_SUCCESS) + goto on_error; + + /* Replace server socket with new socket. */ + pj_sock_close(sock[SERVER]); + sock[SERVER] = newserver; + } +#endif + + *serverfd = sock[SERVER]; + *clientfd = sock[CLIENT]; + + return rc; + +on_error: + for (i=0; i<2; ++i) + pj_sock_close(sock[i]); + return rc; +} |