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