From 72a1098e21f5b7d797655afe7ddb275969a192bd Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Wed, 28 Jun 2006 16:46:49 +0000 Subject: Major improvements in PJSIP to support TCP. The changes fall into these categories: (1) the TCP transport implementation itself (*.[hc]), (2) bug-fix in SIP transaction when using reliable transports, (3) support for TCP transport in PJSUA-LIB/PJSUA, and (4) changes in PJSIP-TEST to support TCP testing. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@563 74dad513-b988-da41-8d7b-12977e46ad98 --- pjlib/src/pj/config.c | 2 +- pjsip-apps/src/pjsua/pjsua_app.c | 74 +++- pjsip/build/Makefile | 31 +- pjsip/build/test_pjsip.dsp | 8 + pjsip/include/pjsip.h | 1 + pjsip/include/pjsip/sip_transport.h | 7 +- pjsip/include/pjsip/sip_transport_tcp.h | 33 +- pjsip/include/pjsua-lib/pjsua_internal.h | 12 +- pjsip/src/pjsip/sip_transaction.c | 23 +- pjsip/src/pjsip/sip_transport.c | 50 ++- pjsip/src/pjsip/sip_transport_loop.c | 3 +- pjsip/src/pjsip/sip_transport_tcp.c | 674 +++++++++++++++++++++-------- pjsip/src/pjsua-lib/pjsua_acc.c | 34 +- pjsip/src/pjsua-lib/pjsua_core.c | 121 ++++-- pjsip/src/test-pjsip/main.c | 15 +- pjsip/src/test-pjsip/msg_logger.c | 12 +- pjsip/src/test-pjsip/msg_test.c | 191 ++++++-- pjsip/src/test-pjsip/test.c | 195 ++++++++- pjsip/src/test-pjsip/test.h | 37 +- pjsip/src/test-pjsip/transport_loop_test.c | 29 +- pjsip/src/test-pjsip/transport_tcp_test.c | 143 ++++++ pjsip/src/test-pjsip/transport_test.c | 13 +- pjsip/src/test-pjsip/transport_udp_test.c | 16 +- pjsip/src/test-pjsip/tsx_basic_test.c | 19 +- pjsip/src/test-pjsip/tsx_bench.c | 273 ++++++++++++ pjsip/src/test-pjsip/tsx_uac_test.c | 210 ++++++--- pjsip/src/test-pjsip/tsx_uas_test.c | 168 ++++--- pjsip/src/test-pjsip/txdata_test.c | 323 +++++++++++++- pjsip/src/test-pjsip/uri_test.c | 185 ++++++-- 29 files changed, 2430 insertions(+), 472 deletions(-) create mode 100644 pjsip/src/test-pjsip/transport_tcp_test.c create mode 100644 pjsip/src/test-pjsip/tsx_bench.c diff --git a/pjlib/src/pj/config.c b/pjlib/src/pj/config.c index 2a6aefd3..9da6c4b3 100644 --- a/pjlib/src/pj/config.c +++ b/pjlib/src/pj/config.c @@ -21,7 +21,7 @@ #include static const char *id = "config.c"; -const char *PJ_VERSION = "0.5.6.0"; +const char *PJ_VERSION = "0.5.6.1"; PJ_DEF(void) pj_dump_config(void) { diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index caf6f8b7..5468d236 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -29,6 +29,8 @@ static struct app_config pjsua_config cfg; pjsua_logging_config log_cfg; pjsua_media_config media_cfg; + pj_bool_t no_tcp; + pj_bool_t no_udp; pjsua_transport_config udp_cfg; pjsua_transport_config rtp_cfg; @@ -95,7 +97,11 @@ static void usage(void) puts (" --next-account Add more account"); puts (""); puts ("Transport Options:"); - puts (" --local-port=port Set TCP/UDP port"); + puts (" --local-port=port Set TCP/UDP port. This implicitly enables both "); + puts (" TCP and UDP transports on the specified port, unless"); + puts (" if TCP or UDP is disabled."); + puts (" --no-tcp Disable TCP transport."); + puts (" --no-udp Disable UDP transport."); puts (" --outbound=url Set the URL of global outbound proxy server"); puts (" May be specified multiple times"); puts (" --use-stun1=host[:port]"); @@ -239,7 +245,7 @@ static pj_status_t parse_args(int argc, char *argv[], OPT_PLAY_FILE, OPT_RTP_PORT, OPT_ADD_CODEC, OPT_COMPLEXITY, OPT_QUALITY, OPT_PTIME, OPT_NEXT_ACCOUNT, OPT_NEXT_CRED, OPT_MAX_CALLS, - OPT_DURATION, + OPT_DURATION, OPT_NO_TCP, OPT_NO_UDP, }; struct pj_getopt_option long_options[] = { { "config-file",1, 0, OPT_CONFIG_FILE}, @@ -251,6 +257,8 @@ static pj_status_t parse_args(int argc, char *argv[], { "clock-rate", 1, 0, OPT_CLOCK_RATE}, { "null-audio", 0, 0, OPT_NULL_AUDIO}, { "local-port", 1, 0, OPT_LOCAL_PORT}, + { "no-tcp", 0, 0, OPT_NO_TCP}, + { "no-udp", 0, 0, OPT_NO_UDP}, { "proxy", 1, 0, OPT_PROXY}, { "outbound", 1, 0, OPT_OUTBOUND_PROXY}, { "registrar", 1, 0, OPT_REGISTRAR}, @@ -384,6 +392,24 @@ static pj_status_t parse_args(int argc, char *argv[], cfg->udp_cfg.port = (pj_uint16_t)lval; break; + case OPT_NO_UDP: /* no-udp */ + if (cfg->no_tcp) { + PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP")); + return PJ_EINVAL; + } + + cfg->no_udp = PJ_TRUE; + break; + + case OPT_NO_TCP: /* no-tcp */ + if (cfg->no_udp) { + PJ_LOG(1,(THIS_FILE,"Error: can not disable both TCP and UDP")); + return PJ_EINVAL; + } + + cfg->no_tcp = PJ_TRUE; + break; + case OPT_PROXY: /* proxy */ if (pjsua_verify_sip_url(pj_optarg) != 0) { PJ_LOG(1,(THIS_FILE, @@ -2046,7 +2072,7 @@ on_exit: pj_status_t app_init(int argc, char *argv[]) { - pjsua_transport_id transport_id; + pjsua_transport_id transport_id = -1; unsigned i; pj_status_t status; @@ -2096,16 +2122,40 @@ pj_status_t app_init(int argc, char *argv[]) app_config.wav_port = pjsua_player_get_conf_port(app_config.wav_id); } - /* Add UDP transport */ - status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, - &app_config.udp_cfg, - &transport_id); - if (status != PJ_SUCCESS) - goto on_error; - /* Add local account */ - pjsua_acc_add_local(transport_id, PJ_TRUE, ¤t_acc); - pjsua_acc_set_online_status(current_acc, PJ_TRUE); + /* Add TCP transport unless it's disabled */ + if (!app_config.no_tcp) { + status = pjsua_transport_create(PJSIP_TRANSPORT_TCP, + &app_config.udp_cfg, + &transport_id); + if (status != PJ_SUCCESS) + goto on_error; + + /* Add local account */ + pjsua_acc_add_local(transport_id, PJ_TRUE, ¤t_acc); + pjsua_acc_set_online_status(current_acc, PJ_TRUE); + + } + + + /* Add UDP transport unless it's disabled. */ + if (!app_config.no_udp) { + status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, + &app_config.udp_cfg, + &transport_id); + if (status != PJ_SUCCESS) + goto on_error; + + /* Add local account */ + pjsua_acc_add_local(transport_id, PJ_TRUE, ¤t_acc); + pjsua_acc_set_online_status(current_acc, PJ_TRUE); + } + + if (transport_id == -1) { + PJ_LOG(3,(THIS_FILE, "Error: no transport is configured")); + status = -1; + goto on_error; + } /* Add accounts */ diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile index 30e61e10..15ed0fb9 100644 --- a/pjsip/build/Makefile +++ b/pjsip/build/Makefile @@ -25,7 +25,6 @@ export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJSUA_LIB_LIB)) \ $(subst /,$(HOST_PSEP),$(PJSIP_UA_LIB)) \ $(subst /,$(HOST_PSEP),$(PJSIP_SIMPLE_LIB)) \ $(subst /,$(HOST_PSEP),$(PJSIP_LIB)) \ - $(subst /,$(HOST_PSEP),$(PJMEDIA_CODEC_LIB)) \ $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \ $(subst /,$(HOST_PSEP),$(PJLIB_UTIL_LIB)) \ $(subst /,$(HOST_PSEP),$(PJLIB_LIB)) \ @@ -40,8 +39,10 @@ export PJSIP_SRCDIR = ../src/pjsip export PJSIP_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ sip_errno.o sip_msg.o sip_parser.o sip_tel_uri.o sip_uri.o \ sip_endpoint.o sip_util.o sip_util_proxy.o \ - sip_resolve.o sip_transport.o sip_transport_loop.o sip_transport_udp.o \ - sip_auth_client.o sip_auth_msg.o sip_auth_parser.o sip_auth_server.o \ + sip_resolve.o sip_transport.o sip_transport_loop.o \ + sip_transport_udp.o sip_transport_tcp.o \ + sip_auth_client.o sip_auth_msg.o sip_auth_parser.o \ + sip_auth_server.o \ sip_transaction.o sip_util_statefull.o \ sip_dialog.o sip_ua_layer.o export PJSIP_CFLAGS += $(_CFLAGS) @@ -75,12 +76,29 @@ export PJSUA_LIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ export PJSUA_LIB_CFLAGS += $(_CFLAGS) +export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT + +############################################################################### +# Defines for building test application +# +export TEST_SRCDIR = ../src/test-pjsip +export TEST_OBJS += dlg_core_test.o msg_err_test.o msg_logger.o msg_test.o \ + test.o transport_loop_test.o transport_tcp_test.o \ + transport_test.o transport_udp_test.o \ + tsx_basic_test.o tsx_bench.o tsx_uac_test.o \ + tsx_uas_test.o txdata_test.o uri_test.o +export TEST_OBJS += main.o +export TEST_CFLAGS += $(_CFLAGS) +export TEST_LDFLAGS += $(_LDFLAGS) +export TEST_EXE := ../bin/pjsip-test-$(TARGET_NAME)$(HOST_EXE) + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT ############################################################################### # Main entry # # -TARGETS := pjsip pjsip-ua pjsip-simple pjsua-lib +TARGETS := pjsip pjsip-ua pjsip-simple pjsua-lib pjsip-test .PHONY: $(TARGETS) @@ -106,6 +124,9 @@ pjsip-simple: pjsua-lib: $(MAKE) -f $(RULES_MAK) APP=PJSUA_LIB app=pjsua-lib $(PJSUA_LIB_LIB) +pjsip-test: + $(MAKE) -f $(RULES_MAK) APP=TEST app=pjsip-test $(TEST_EXE) + .PHONY: ../lib/pjsip.ko ../lib/pjsip.ko: echo Making $@ @@ -131,6 +152,7 @@ clean depend: $(MAKE) -f $(RULES_MAK) APP=PJSIP_UA app=pjsip-ua $@ $(MAKE) -f $(RULES_MAK) APP=PJSIP_SIMPLE app=pjsip-simple $@ $(MAKE) -f $(RULES_MAK) APP=PJSUA_LIB app=pjsua-lib $@ + $(MAKE) -f $(RULES_MAK) APP=TEST app=pjsip-test $@ realclean: $(subst @@,$(subst /,$(HOST_PSEP),.pjsip-$(TARGET_NAME).depend),$(HOST_RMR)) @@ -140,6 +162,7 @@ realclean: $(MAKE) -f $(RULES_MAK) APP=PJSIP app=pjsip $@ $(MAKE) -f $(RULES_MAK) APP=PJSIP_UA app=pjsip-ua $@ $(MAKE) -f $(RULES_MAK) APP=PJSIP_SIMPLE app=pjsip-simple $@ + $(MAKE) -f $(RULES_MAK) APP=TEST app=pjsip-test $@ $(MAKE) -f $(RULES_MAK) APP=PJSUA_LIB app=pjsua-lib $@ diff --git a/pjsip/build/test_pjsip.dsp b/pjsip/build/test_pjsip.dsp index bdf1a72d..418ee8a8 100644 --- a/pjsip/build/test_pjsip.dsp +++ b/pjsip/build/test_pjsip.dsp @@ -117,6 +117,10 @@ SOURCE="..\src\test-pjsip\transport_loop_test.c" # End Source File # Begin Source File +SOURCE="..\src\test-pjsip\transport_tcp_test.c" +# End Source File +# Begin Source File + SOURCE="..\src\test-pjsip\transport_test.c" # End Source File # Begin Source File @@ -129,6 +133,10 @@ SOURCE="..\src\test-pjsip\tsx_basic_test.c" # End Source File # Begin Source File +SOURCE="..\src\test-pjsip\tsx_bench.c" +# End Source File +# Begin Source File + SOURCE="..\src\test-pjsip\tsx_uac_test.c" # End Source File # Begin Source File diff --git a/pjsip/include/pjsip.h b/pjsip/include/pjsip.h index b7f2dd95..8da2d64c 100644 --- a/pjsip/include/pjsip.h +++ b/pjsip/include/pjsip.h @@ -39,6 +39,7 @@ #include #include #include +#include #include /* Authentication. */ diff --git a/pjsip/include/pjsip/sip_transport.h b/pjsip/include/pjsip/sip_transport.h index e09583db..002061cf 100644 --- a/pjsip/include/pjsip/sip_transport.h +++ b/pjsip/include/pjsip/sip_transport.h @@ -704,7 +704,7 @@ struct pjsip_tpfactory pj_lock_t *lock; /**< Lock object. */ pjsip_transport_type_e type; /**< Transport type. */ - char type_name[8]; /**< Type string name. */ + char *type_name; /**< Type string name. */ unsigned flag; /**< Transport flag. */ pj_sockaddr local_addr; /**< Bound address. */ @@ -722,6 +722,11 @@ struct pjsip_tpfactory int addr_len, pjsip_transport **transport); + /** + * Destroy the listener. + */ + pj_status_t (*destroy)(pjsip_tpfactory *factory); + /* * Application may extend this structure.. */ diff --git a/pjsip/include/pjsip/sip_transport_tcp.h b/pjsip/include/pjsip/sip_transport_tcp.h index 317d7772..4f5664d4 100644 --- a/pjsip/include/pjsip/sip_transport_tcp.h +++ b/pjsip/include/pjsip/sip_transport_tcp.h @@ -38,11 +38,35 @@ PJ_BEGIN_DECL */ /** - * Create, register, and start TCP transport. + * The TCP incoming connection backlog number. + * Default: 5 + */ +#ifndef PJSIP_TCP_TRANSPORT_BACKLOG +# define PJSIP_TCP_TRANSPORT_BACKLOG 5 +#endif + + +/** + * Register support for SIP TCP transport by creating TCP listener on + * the specified address and port. This function will create an + * instance of SIP TCP transport factory and register it to the + * transport manager. * * @param endpt The SIP endpoint. - * @param local Local address to bind. - * @param async_cnt Number of simultaneous async operations. + * @param local Optional local address to bind, or specify the + * address to bind the server socket to. Both IP + * interface address and port fields are optional. + * If IP interface address is not specified, socket + * will be bound to PJ_INADDR_ANY. If port is not + * specified, socket will be bound to any port + * selected by the operating system. + * @param async_cnt Number of simultaneous asynchronous accept() + * operations to be supported. It is recommended that + * the number here corresponds to the number of + * processors in the system (or the number of SIP + * worker threads). + * @param p_factory Optional pointer to receive the instance of the + * SIP TCP transport factory just created. * * @return PJ_SUCCESS when the transport has been successfully * started and registered to transport manager, or @@ -50,7 +74,8 @@ PJ_BEGIN_DECL */ PJ_DECL(pj_status_t) pjsip_tcp_transport_start(pjsip_endpoint *endpt, const pj_sockaddr_in *local, - unsigned async_cnt); + unsigned async_cnt, + pjsip_tpfactory **p_factory); PJ_END_DECL diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index 8b061c5b..004a41a0 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -104,8 +104,16 @@ typedef struct pjsua_acc */ typedef struct transport_data { - int index; - pjsip_transport *tp; + int index; + pjsip_transport_type_e type; + pjsip_host_port local_name; + + union { + pjsip_transport *tp; + pjsip_tpfactory *factory; + void *ptr; + } data; + } transport_data; diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c index 017f936d..2b29b91d 100644 --- a/pjsip/src/pjsip/sip_transaction.c +++ b/pjsip/src/pjsip/sip_transaction.c @@ -1299,6 +1299,7 @@ PJ_DEF(pj_status_t) pjsip_tsx_create_uas( pjsip_module *tsx_user, pjsip_transport_add_ref(tsx->transport); pj_memcpy(&tsx->addr, &tsx->res_addr.addr, tsx->res_addr.addr_len); tsx->addr_len = tsx->res_addr.addr_len; + tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport); } @@ -2130,9 +2131,25 @@ static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx, pjsip_tx_data_add_ref( tdata ); } - /* Start timer H for transaction termination */ - pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, - &timeout_timer_val); + /* For INVITE, start timer H for transaction termination + * regardless whether transport is reliable or not. + * For non-INVITE, start timer J with the value of 64*T1 for + * non-reliable transports, and zero for reliable transports. + */ + if (tsx->method.id == PJSIP_INVITE_METHOD) { + /* Start timer H for INVITE */ + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &timeout_timer_val); + } else if (!tsx->is_reliable) { + /* Start timer J on 64*T1 seconds for non-INVITE */ + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &timeout_timer_val); + } else { + /* Start timer J on zero seconds for non-INVITE */ + pj_time_val zero_time = { 0, 0 }; + pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer, + &zero_time); + } /* For INVITE, if unreliable transport is used, retransmission * timer G will be scheduled (retransmission). diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c index 9583e049..1e9a4f05 100644 --- a/pjsip/src/pjsip/sip_transport.c +++ b/pjsip/src/pjsip/sip_transport.c @@ -36,6 +36,12 @@ #define THIS_FILE "sip_transport.c" +#if 0 +# define TRACE_(x) PJ_LOG(5,x) +#else +# define TRACE_(x) +#endif + /* Prototype. */ static pj_status_t mod_on_tx_msg(pjsip_tx_data *tdata); @@ -601,6 +607,12 @@ PJ_DEF(pj_status_t) pjsip_transport_register( pjsip_tpmgr *mgr, pj_hash_set(tp->pool, mgr->table, &tp->key, key_len, 0, tp); pj_lock_release(mgr->lock); + TRACE_((THIS_FILE,"Transport %s registered: type=%s, remote=%s:%d", + tp->obj_name, + pjsip_transport_get_type_name(tp->key.type), + pj_inet_ntoa(((pj_sockaddr_in*)&tp->key.rem_addr)->sin_addr), + pj_ntohs(((pj_sockaddr_in*)&tp->key.rem_addr)->sin_port))); + return PJ_SUCCESS; } @@ -610,6 +622,8 @@ static pj_status_t destroy_transport( pjsip_tpmgr *mgr, { int key_len; + TRACE_((THIS_FILE, "Transport %s is being destroyed", tp->obj_name)); + pj_lock_acquire(tp->lock); pj_lock_acquire(mgr->lock); @@ -626,6 +640,7 @@ static pj_status_t destroy_transport( pjsip_tpmgr *mgr, * Unregister from hash table. */ key_len = sizeof(tp->key.type) + tp->addr_len; + pj_assert(pj_hash_get(mgr->table, &tp->key, key_len, NULL) != NULL); pj_hash_set(tp->pool, mgr->table, &tp->key, key_len, 0, NULL); pj_lock_release(mgr->lock); @@ -643,6 +658,8 @@ PJ_DEF(pj_status_t) pjsip_transport_shutdown(pjsip_transport *tp) pjsip_tpmgr *mgr; pj_status_t status; + TRACE_((THIS_FILE, "Transport %s shutting down", tp->obj_name)); + pj_lock_acquire(tp->lock); mgr = tp->tpmgr; @@ -807,12 +824,16 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_destroy( pjsip_tpmgr *mgr ) { pj_hash_iterator_t itr_val; pj_hash_iterator_t *itr; + pjsip_tpfactory *factory; pjsip_endpoint *endpt = mgr->endpt; PJ_LOG(5, (THIS_FILE, "Destroying transport manager")); pj_lock_acquire(mgr->lock); + /* + * Destroy all transports. + */ itr = pj_hash_first(mgr->table, &itr_val); while (itr != NULL) { pj_hash_iterator_t *next; @@ -827,6 +848,18 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_destroy( pjsip_tpmgr *mgr ) itr = next; } + /* + * Destroy all factories/listeners. + */ + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + pjsip_tpfactory *next = factory->next; + + factory->destroy(factory); + + factory = next; + } + pj_lock_release(mgr->lock); pj_lock_destroy(mgr->lock); @@ -842,7 +875,7 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_destroy( pjsip_tpmgr *mgr ) */ //pj_assert(pj_atomic_get(mgr->tdata_counter) == 0); if (pj_atomic_get(mgr->tdata_counter) != 0) { - PJ_LOG(4,(THIS_FILE, "Warning: %d transmit buffers are not freed!", + PJ_LOG(3,(THIS_FILE, "Warning: %d transmit buffer(s) not freed!", pj_atomic_get(mgr->tdata_counter))); } #endif @@ -879,10 +912,10 @@ PJ_DEF(pj_ssize_t) pjsip_tpmgr_receive_packet( pjsip_tpmgr *mgr, current_pkt[remaining_len] = '\0'; /* Process all message fragments. */ - while (total_processed < remaining_len) { + while (remaining_len > 0) { pjsip_msg *msg; - pj_size_t msg_fragment_size = 0; + pj_size_t msg_fragment_size; /* Initialize default fragment size. */ msg_fragment_size = remaining_len; @@ -1038,6 +1071,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, pjsip_tpfactory *factory; pj_status_t status; + TRACE_((THIS_FILE,"Acquiring transport type=%s, remote=%s:%d", + pjsip_transport_get_type_name(type), + pj_inet_ntoa(((pj_sockaddr_in*)remote)->sin_addr), + pj_ntohs(((pj_sockaddr_in*)remote)->sin_port))); + pj_lock_acquire(mgr->lock); key_len = sizeof(key.type) + addr_len; @@ -1083,6 +1121,8 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, pjsip_transport_add_ref(transport); pj_lock_release(mgr->lock); *tp = transport; + + TRACE_((THIS_FILE, "Transport %s acquired", transport->obj_name)); return PJ_SUCCESS; } @@ -1100,9 +1140,13 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr, if (factory == &mgr->factory_list) { /* No factory can create the transport! */ pj_lock_release(mgr->lock); + TRACE_((THIS_FILE, "No suitable factory was found either")); return PJSIP_EUNSUPTRANSPORT; } + TRACE_((THIS_FILE, "%s, creating new one from factory", + (transport?"Transport is shutdown":"No transport found"))); + /* Request factory to create transport. */ status = factory->create_transport(factory, mgr, mgr->endpt, remote, addr_len, tp); diff --git a/pjsip/src/pjsip/sip_transport_loop.c b/pjsip/src/pjsip/sip_transport_loop.c index d5ac676a..3dc19200 100644 --- a/pjsip/src/pjsip/sip_transport_loop.c +++ b/pjsip/src/pjsip/sip_transport_loop.c @@ -73,7 +73,8 @@ struct recv_list *create_incoming_packet( struct loop_transport *loop, struct recv_list *pkt; pool = pjsip_endpt_create_pool(loop->base.endpt, "rdata", - PJSIP_POOL_RDATA_LEN, PJSIP_POOL_RDATA_INC); + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_INC+5); if (!pool) return NULL; diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c index c168ccb0..42339c25 100644 --- a/pjsip/src/pjsip/sip_transport_tcp.c +++ b/pjsip/src/pjsip/sip_transport_tcp.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -33,15 +34,21 @@ #define MAX_ASYNC_CNT 16 #define POOL_LIS_INIT 4000 -#define POOL_LIS_INC 4000 +#define POOL_LIS_INC 4001 #define POOL_TP_INIT 4000 -#define POOL_TP_INC 4000 +#define POOL_TP_INC 4002 struct tcp_listener; struct tcp_transport; +/* + * This structure is "descendant" of pj_ioqueue_op_key_t, and it is used to + * track pending/asynchronous accept() operation. TCP transport may have + * more than one pending accept() operations, depending on the value of + * async_cnt. + */ struct pending_accept { pj_ioqueue_op_key_t op_key; @@ -52,18 +59,16 @@ struct pending_accept pj_sockaddr_in remote_addr; }; -struct pending_connect -{ - pj_ioqueue_op_key_t op_key; - struct tcp_transport *transport; -}; - +/* + * This is the TCP listener, which is a "descendant" of pjsip_tpfactory (the + * SIP transport factory). + */ struct tcp_listener { pjsip_tpfactory factory; - char name[PJ_MAX_OBJ_NAME]; - pj_bool_t active; + char obj_name[PJ_MAX_OBJ_NAME]; + pj_bool_t is_registered; pjsip_endpoint *endpt; pjsip_tpmgr *tpmgr; pj_sock_t sock; @@ -73,23 +78,35 @@ struct tcp_listener }; -struct pending_tdata +/* + * This structure is used to keep delayed transmit operation in a list. + * A delayed transmission occurs when application sends tx_data when + * the TCP connect/establishment is still in progress. These delayed + * transmission will be "flushed" once the socket is connected (either + * successfully or with errors). + */ +struct delayed_tdata { - PJ_DECL_LIST_MEMBER(struct pending_tdata); + PJ_DECL_LIST_MEMBER(struct delayed_tdata); pjsip_tx_data_op_key *tdata_op_key; }; +/* + * This structure describes the TCP transport, and it's descendant of + * pjsip_transport. + */ struct tcp_transport { pjsip_transport base; + pj_bool_t is_server; struct tcp_listener *listener; pj_bool_t is_registered; pj_bool_t is_closing; + pj_status_t close_reason; pj_sock_t sock; pj_ioqueue_key_t *key; pj_bool_t has_pending_connect; - struct pending_connect connect_op; /* TCP transport can only have one rdata! @@ -99,19 +116,24 @@ struct tcp_transport pjsip_rx_data rdata; /* Pending transmission list. */ - struct pending_tdata tx_list; + struct delayed_tdata delayed_list; }; -/* - * This callback is called when #pj_ioqueue_accept completes. +/**************************************************************************** + * PROTOTYPES */ + +/* This callback is called when pending accept() operation completes. */ static void on_accept_complete( pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_sock_t sock, pj_status_t status); -static pj_status_t lis_destroy(struct tcp_listener *listener); +/* This callback is called by transport manager to destroy listener */ +static pj_status_t lis_destroy(pjsip_tpfactory *factory); + +/* This callback is called by transport manager to create transport */ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, pjsip_tpmgr *mgr, pjsip_endpoint *endpt, @@ -119,12 +141,12 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, int addr_len, pjsip_transport **transport); - -static pj_status_t create_tcp_transport(struct tcp_listener *listener, - pj_sock_t sock, - const pj_sockaddr_in *local, - const pj_sockaddr_in *remote, - struct tcp_transport **p_tcp); +/* Common function to create and initialize transport */ +static pj_status_t tcp_create(struct tcp_listener *listener, + pj_sock_t sock, pj_bool_t is_server, + const pj_sockaddr_in *local, + const pj_sockaddr_in *remote, + struct tcp_transport **p_tcp); static void tcp_perror(const char *sender, const char *title, @@ -138,18 +160,41 @@ static void tcp_perror(const char *sender, const char *title, } +static void sockaddr_to_host_port( pj_pool_t *pool, + pjsip_host_port *host_port, + const pj_sockaddr_in *addr ) +{ + host_port->host.ptr = pj_pool_alloc(pool, 48); + host_port->host.slen = pj_ansi_sprintf( host_port->host.ptr, "%s", + pj_inet_ntoa(addr->sin_addr)); + host_port->port = pj_ntohs(addr->sin_port); +} + + + +/**************************************************************************** + * The TCP listener/transport factory. + */ + +/* + * This is the public API to create, initialize, register, and start the + * TCP listener. + */ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt, const pj_sockaddr_in *local, - unsigned async_cnt) + unsigned async_cnt, + pjsip_tpfactory **p_factory) { pj_pool_t *pool; struct tcp_listener *listener; pj_ioqueue_callback listener_cb; + pj_sockaddr_in *listener_addr; + int addr_len; unsigned i; pj_status_t status; /* Sanity check */ - PJ_ASSERT_RETURN(endpt && local && async_cnt, PJ_EINVAL); + PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL); pool = pjsip_endpt_create_pool(endpt, "tcplis", POOL_LIS_INIT, @@ -158,14 +203,15 @@ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt, listener = pj_pool_zalloc(pool, sizeof(struct tcp_listener)); - pj_ansi_sprintf(listener->name, "tcp:%d", (int)pj_ntohs(local->sin_port)); listener->factory.pool = pool; listener->factory.type = PJSIP_TRANSPORT_TCP; - pj_ansi_strcpy(listener->factory.type_name, "tcp"); + listener->factory.type_name = "tcp"; listener->factory.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TCP); listener->sock = PJ_INVALID_SOCKET; + pj_ansi_strcpy(listener->obj_name, "tcp"); + status = pj_lock_create_recursive_mutex(pool, "tcplis", &listener->factory.lock); if (status != PJ_SUCCESS) @@ -177,11 +223,52 @@ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt, if (status != PJ_SUCCESS) goto on_error; - pj_memcpy(&listener->factory.local_addr, local, sizeof(pj_sockaddr_in)); - status = pj_sock_bind(listener->sock, local, sizeof(*local)); + listener_addr = (pj_sockaddr_in*)&listener->factory.local_addr; + if (local) { + pj_memcpy(listener_addr, local, sizeof(pj_sockaddr_in)); + } else { + pj_sockaddr_in_init(listener_addr, NULL, 0); + } + + status = pj_sock_bind(listener->sock, listener_addr, + sizeof(pj_sockaddr_in)); + if (status != PJ_SUCCESS) + goto on_error; + + /* Retrieve the bound address */ + addr_len = sizeof(pj_sockaddr_in); + status = pj_sock_getsockname(listener->sock, listener_addr, &addr_len); if (status != PJ_SUCCESS) goto on_error; + /* If the address returns 0.0.0.0, use the first interface address + * as the transport's address. + */ + if (listener_addr->sin_addr.s_addr == 0) { + const pj_str_t *hostname; + struct pj_hostent he; + + hostname = pj_gethostname(); + status = pj_gethostbyname(hostname, &he); + if (status != PJ_SUCCESS) + goto on_error; + + listener_addr->sin_addr = *(pj_in_addr*)he.h_addr; + } + + pj_ansi_sprintf(listener->obj_name, "tcp:%d", + (int)pj_ntohs(listener_addr->sin_port)); + + /* Save the address name */ + sockaddr_to_host_port(listener->factory.pool, + &listener->factory.addr_name, listener_addr); + + /* Start listening to the address */ + status = pj_sock_listen(listener->sock, PJSIP_TCP_TRANSPORT_BACKLOG); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register socket to ioqeuue */ pj_memset(&listener_cb, 0, sizeof(listener_cb)); listener_cb.on_accept_complete = &on_accept_complete; @@ -191,7 +278,21 @@ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt, if (status != PJ_SUCCESS) goto on_error; - /* Start pending accept() operation */ + /* Register to transport manager */ + listener->endpt = endpt; + listener->tpmgr = pjsip_endpt_get_tpmgr(endpt); + listener->factory.create_transport = lis_create_transport; + listener->factory.destroy = lis_destroy; + listener->is_registered = PJ_TRUE; + status = pjsip_tpmgr_register_tpfactory(listener->tpmgr, + &listener->factory); + if (status != PJ_SUCCESS) { + listener->is_registered = PJ_FALSE; + goto on_error; + } + + + /* Start pending accept() operations */ if (async_cnt > MAX_ASYNC_CNT) async_cnt = MAX_ASYNC_CNT; listener->async_cnt = async_cnt; @@ -200,45 +301,34 @@ PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt, sizeof(listener->accept_op[i].op_key)); listener->accept_op[i].listener = listener; - status = pj_ioqueue_accept(listener->key, - &listener->accept_op[i].op_key, - &listener->accept_op[i].new_sock, - &listener->accept_op[i].local_addr, - &listener->accept_op[i].remote_addr, - &listener->accept_op[i].addr_len); - if (status != PJ_SUCCESS && status != PJ_EPENDING) - goto on_error; + on_accept_complete(listener->key, &listener->accept_op[i].op_key, + listener->sock, PJ_EPENDING); } - /* Register to transport manager */ - listener->endpt = endpt; - listener->tpmgr = pjsip_endpt_get_tpmgr(endpt); - listener->factory.create_transport = lis_create_transport; - status = pjsip_tpmgr_register_tpfactory(listener->tpmgr, - &listener->factory); - if (status != PJ_SUCCESS) - goto on_error; - - /* Done! */ - listener->active = PJ_TRUE; + PJ_LOG(4,(listener->obj_name, + "SIP TCP listener ready for incoming connections at %s:%d", + pj_inet_ntoa(listener_addr->sin_addr), + (int)pj_ntohs(listener_addr->sin_port))); - PJ_LOG(4,(listener->name, - "SIP TCP transport listening for incoming connections at %s:%d", - pj_inet_ntoa(local->sin_addr), (int)pj_ntohs(local->sin_port))); + /* Return the pointer to user */ + if (p_factory) *p_factory = &listener->factory; return PJ_SUCCESS; on_error: - lis_destroy(listener); + lis_destroy(&listener->factory); return status; } -static pj_status_t lis_destroy(struct tcp_listener *listener) +/* This callback is called by transport manager to destroy listener */ +static pj_status_t lis_destroy(pjsip_tpfactory *factory) { - if (listener->active) { + struct tcp_listener *listener = (struct tcp_listener *)factory; + + if (listener->is_registered) { pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory); - listener->active = PJ_FALSE; + listener->is_registered = PJ_FALSE; } if (listener->key) { @@ -258,9 +348,12 @@ static pj_status_t lis_destroy(struct tcp_listener *listener) } if (listener->factory.pool) { - PJ_LOG(4,(listener->name, "SIP TCP transport destroyed")); - pj_pool_release(listener->factory.pool); + pj_pool_t *pool = listener->factory.pool; + + PJ_LOG(4,(listener->obj_name, "SIP TCP listener destroyed")); + listener->factory.pool = NULL; + pj_pool_release(pool); } return PJ_SUCCESS; @@ -288,8 +381,12 @@ static pj_status_t tcp_send_msg(pjsip_transport *transport, /* Called by transport manager to shutdown */ static pj_status_t tcp_shutdown(pjsip_transport *transport); -/* Called by transport manager to destroy */ -static pj_status_t tcp_destroy(pjsip_transport *transport); +/* Called by transport manager to destroy transport */ +static pj_status_t tcp_destroy_transport(pjsip_transport *transport); + +/* Utility to destroy transport */ +static pj_status_t tcp_destroy(pjsip_transport *transport, + pj_status_t reason); /* Callback from ioqueue on incoming packet */ static void on_read_complete(pj_ioqueue_key_t *key, @@ -306,25 +403,15 @@ static void on_connect_complete(pj_ioqueue_key_t *key, pj_status_t status); -static void sockaddr_to_host_port( pj_pool_t *pool, - pjsip_host_port *host_port, - const pj_sockaddr_in *addr ) -{ - host_port->host.ptr = pj_pool_alloc(pool, 48); - host_port->host.slen = pj_ansi_sprintf( host_port->host.ptr, "%s", - pj_inet_ntoa(addr->sin_addr)); - host_port->port = pj_ntohs(addr->sin_port); -} - - /* - * Utilities to create TCP transport. + * Common function to create TCP transport, called when pending accept() and + * pending connect() complete. */ -static pj_status_t create_tcp_transport(struct tcp_listener *listener, - pj_sock_t sock, - const pj_sockaddr_in *local, - const pj_sockaddr_in *remote, - struct tcp_transport **p_tcp) +static pj_status_t tcp_create( struct tcp_listener *listener, + pj_sock_t sock, pj_bool_t is_server, + const pj_sockaddr_in *local, + const pj_sockaddr_in *remote, + struct tcp_transport **p_tcp) { struct tcp_transport *tcp; pj_pool_t *pool; @@ -332,28 +419,37 @@ static pj_status_t create_tcp_transport(struct tcp_listener *listener, pj_ioqueue_callback tcp_callback; pj_status_t status; - pool = pjsip_endpt_create_pool(listener->endpt, "tcp", + + PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_EINVAL); + + + pool = pjsip_endpt_create_pool(listener->endpt, "tcp", POOL_TP_INIT, POOL_TP_INC); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + /* * Create and initialize basic transport structure. */ tcp = pj_pool_zalloc(pool, sizeof(*tcp)); tcp->sock = sock; + tcp->is_server = is_server; tcp->listener = listener; - pj_list_init(&tcp->tx_list); - - - pj_ansi_snprintf(tcp->base.obj_name, PJ_MAX_OBJ_NAME, "tcp%p", tcp); + pj_list_init(&tcp->delayed_list); tcp->base.pool = pool; + pj_ansi_snprintf(tcp->base.obj_name, PJ_MAX_OBJ_NAME, + (is_server ? "tcps%p" :"tcpc%p"), tcp); + status = pj_atomic_create(pool, 0, &tcp->base.ref_cnt); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { goto on_error; + } status = pj_lock_create_recursive_mutex(pool, "tcp", &tcp->base.lock); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { goto on_error; + } tcp->base.key.type = PJSIP_TRANSPORT_TCP; pj_memcpy(&tcp->base.key.rem_addr, remote, sizeof(pj_sockaddr_in)); @@ -374,7 +470,7 @@ static pj_status_t create_tcp_transport(struct tcp_listener *listener, tcp->base.tpmgr = listener->tpmgr; tcp->base.send_msg = &tcp_send_msg; tcp->base.do_shutdown = &tcp_shutdown; - tcp->base.destroy = &tcp_destroy; + tcp->base.destroy = &tcp_destroy_transport; /* Register socket to ioqueue */ @@ -386,37 +482,44 @@ static pj_status_t create_tcp_transport(struct tcp_listener *listener, ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); status = pj_ioqueue_register_sock(pool, ioqueue, sock, tcp, &tcp_callback, &tcp->key); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { goto on_error; + } /* Register transport to transport manager */ status = pjsip_transport_register(listener->tpmgr, &tcp->base); - if (status != PJ_SUCCESS) + if (status != PJ_SUCCESS) { goto on_error; + } tcp->is_registered = PJ_TRUE; /* Done setting up basic transport. */ *p_tcp = tcp; + PJ_LOG(4,(tcp->base.obj_name, "TCP %s transport created", + (tcp->is_server ? "server" : "client"))); + + return PJ_SUCCESS; + on_error: - tcp_destroy(&tcp->base); + tcp_destroy(&tcp->base, status); return status; } -/* Flush all pending send operations */ -static tcp_flush_pending_tx(struct tcp_transport *tcp) +/* Flush all delayed transmision once the socket is connected. */ +static void tcp_flush_pending_tx(struct tcp_transport *tcp) { pj_lock_acquire(tcp->base.lock); - while (!pj_list_empty(&tcp->tx_list)) { - struct pending_tdata *pending_tx; + while (!pj_list_empty(&tcp->delayed_list)) { + struct delayed_tdata *pending_tx; pjsip_tx_data *tdata; pj_ioqueue_op_key_t *op_key; pj_ssize_t size; pj_status_t status; - pending_tx = tcp->tx_list.next; + pending_tx = tcp->delayed_list.next; pj_list_erase(pending_tx); tdata = pending_tx->tdata_op_key->tdata; @@ -436,29 +539,53 @@ static tcp_flush_pending_tx(struct tcp_transport *tcp) } +/* Called by transport manager to destroy transport */ +static pj_status_t tcp_destroy_transport(pjsip_transport *transport) +{ + struct tcp_transport *tcp = (struct tcp_transport*)transport; + + /* Transport would have been unregistered by now since this callback + * is called by transport manager. + */ + tcp->is_registered = PJ_FALSE; + + return tcp_destroy(transport, tcp->close_reason); +} + /* Destroy TCP transport */ -static pj_status_t tcp_destroy(pjsip_transport *transport) +static pj_status_t tcp_destroy(pjsip_transport *transport, + pj_status_t reason) { struct tcp_transport *tcp = (struct tcp_transport*)transport; - /* Cancel all pending transmits */ - while (!pj_list_empty(&tcp->tx_list)) { - struct pending_tdata *pending_tx; + if (tcp->close_reason == 0) + tcp->close_reason = reason; + + if (tcp->is_registered) { + tcp->is_registered = PJ_FALSE; + pjsip_transport_destroy(transport); + + /* pjsip_transport_destroy will recursively call this function + * again. + */ + return PJ_SUCCESS; + } + + /* Mark transport as closing */ + tcp->is_closing = PJ_TRUE; + + /* Cancel all delayed transmits */ + while (!pj_list_empty(&tcp->delayed_list)) { + struct delayed_tdata *pending_tx; pj_ioqueue_op_key_t *op_key; - pending_tx = tcp->tx_list.next; + pending_tx = tcp->delayed_list.next; pj_list_erase(pending_tx); op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; - on_write_complete(tcp->key, op_key, - -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN)); - } - - if (tcp->is_registered) { - pjsip_transport_destroy(transport); - tcp->is_registered = PJ_FALSE; + on_write_complete(tcp->key, op_key, -reason); } if (tcp->rdata.tp_info.pool) { @@ -469,6 +596,12 @@ static pj_status_t tcp_destroy(pjsip_transport *transport) if (tcp->key) { pj_ioqueue_unregister(tcp->key); tcp->key = NULL; + tcp->sock = PJ_INVALID_SOCKET; + } + + if (tcp->sock != PJ_INVALID_SOCKET) { + pj_sock_close(tcp->sock); + tcp->sock = PJ_INVALID_SOCKET; } if (tcp->base.lock) { @@ -482,9 +615,26 @@ static pj_status_t tcp_destroy(pjsip_transport *transport) } if (tcp->base.pool) { - PJ_LOG(4,(tcp->base.obj_name, "TCP transport destroyed")); - pj_pool_release(tcp->base.pool); + pj_pool_t *pool; + + if (reason != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(reason, errmsg, sizeof(errmsg)); + PJ_LOG(4,(tcp->base.obj_name, + "TCP transport destroyed with reason %d: %s", + reason, errmsg)); + + } else { + + PJ_LOG(4,(tcp->base.obj_name, + "TCP transport destroyed normally")); + + } + + pool = tcp->base.pool; tcp->base.pool = NULL; + pj_pool_release(pool); } return PJ_SUCCESS; @@ -493,7 +643,8 @@ static pj_status_t tcp_destroy(pjsip_transport *transport) /* * This utility function creates receive data buffers and start - * asynchronous recv() operations from the socket. + * asynchronous recv() operations from the socket. It is called after + * accept() or connect() operation complete. */ static pj_status_t tcp_start_read(struct tcp_transport *tcp) { @@ -531,7 +682,7 @@ static pj_status_t tcp_start_read(struct tcp_transport *tcp) status = pj_ioqueue_recv(tcp->key, &tcp->rdata.tp_info.op_key.op_key, tcp->rdata.pkt_info.packet, &size, PJ_IOQUEUE_ALWAYS_ASYNC); - if (status != PJ_SUCCESS) { + if (status != PJ_SUCCESS && status != PJ_EPENDING) { tcp_perror(tcp->base.obj_name, "ioqueue recv() error", status); return status; } @@ -593,19 +744,19 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, ((pj_sockaddr_in*)&listener->factory.local_addr)->sin_addr.s_addr; /* Create the transport descriptor */ - status = create_tcp_transport(listener, sock, &local_addr, - (pj_sockaddr_in*)rem_addr, &tcp); + status = tcp_create(listener, sock, PJ_FALSE, &local_addr, + (pj_sockaddr_in*)rem_addr, &tcp); if (status != PJ_SUCCESS) return status; - + + /* Start asynchronous connect() operation */ tcp->has_pending_connect = PJ_TRUE; - pj_ioqueue_op_key_init(&tcp->connect_op.op_key, - sizeof(tcp->connect_op.op_key)); - tcp->connect_op.transport = tcp; status = pj_ioqueue_connect(tcp->key, rem_addr, sizeof(pj_sockaddr_in)); - if (status != PJ_SUCCESS) { - tcp_destroy(&tcp->base); + if (status == PJ_SUCCESS) { + tcp->has_pending_connect = PJ_FALSE; + } else if (status != PJ_EPENDING) { + tcp_destroy(&tcp->base, status); return status; } @@ -629,6 +780,17 @@ static pj_status_t lis_create_transport(pjsip_tpfactory *factory, } } + if (tcp->has_pending_connect) { + PJ_LOG(4,(tcp->base.obj_name, + "TCP transport %.*s:%d is connecting to %.*s:%d...", + (int)tcp->base.local_name.host.slen, + tcp->base.local_name.host.ptr, + tcp->base.local_name.port, + (int)tcp->base.remote_name.host.slen, + tcp->base.remote_name.host.ptr, + tcp->base.remote_name.port)); + } + /* Done */ *p_transport = &tcp->base; @@ -653,31 +815,75 @@ static void on_accept_complete( pj_ioqueue_key_t *key, listener = pj_ioqueue_get_user_data(key); accept_op = (struct pending_accept*) op_key; + /* + * Loop while there is immediate connection or when there is error. + */ do { - if (status != PJ_SUCCESS) { - tcp_perror(listener->name, "Error in accept()", status); - + if (status == PJ_EPENDING) { + /* + * This can only happen when this function is called during + * initialization to kick off asynchronous accept(). + */ + + } else if (status != PJ_SUCCESS) { + + /* + * Error in accept(). + */ + tcp_perror(listener->obj_name, "Error in accept()", status); + + /* + * Prevent endless accept() error loop by limiting the + * number of consecutive errors. Once the number of errors + * is equal to maximum, we treat this as permanent error, and + * we stop the accept() operation. + */ ++err_cnt; - if (err_cnt >= 5) { - PJ_LOG(1, (listener->name, + if (err_cnt >= 10) { + PJ_LOG(1, (listener->obj_name, "Too many errors, listener stopping")); } - goto start_next_accept; - } + } else { - status = create_tcp_transport( listener, sock, - &accept_op->local_addr, - &accept_op->remote_addr, &tcp); - if (status == PJ_SUCCESS) { - status = tcp_start_read(tcp); - if (status != PJ_SUCCESS) { - PJ_LOG(3,(tcp->base.obj_name, "New transport cancelled")); - tcp_destroy(&tcp->base); + if (sock == PJ_INVALID_SOCKET) { + sock = accept_op->new_sock; + PJ_LOG(4,(listener->obj_name, + "Warning: ioqueue reports -1 in on_accept_complete()" + " sock argument")); + } + + PJ_LOG(4,(listener->obj_name, + "TCP listener %.*s:%d: got incoming TCP connection " + "from %s:%d, sock=%d", + (int)listener->factory.addr_name.host.slen, + listener->factory.addr_name.host.ptr, + listener->factory.addr_name.port, + pj_inet_ntoa(accept_op->remote_addr.sin_addr), + pj_ntohs(accept_op->remote_addr.sin_port), + sock)); + + /* + * Incoming connections! + * Create TCP transport for the new socket. + */ + status = tcp_create( listener, sock, PJ_TRUE, + &accept_op->local_addr, + &accept_op->remote_addr, &tcp); + if (status == PJ_SUCCESS) { + status = tcp_start_read(tcp); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(tcp->base.obj_name, "New transport cancelled")); + tcp_destroy(&tcp->base, status); + } } } -start_next_accept: + /* + * Start the next asynchronous accept() operation. + */ + accept_op->addr_len = sizeof(pj_sockaddr_in); + accept_op->new_sock = PJ_INVALID_SOCKET; status = pj_ioqueue_accept(listener->key, &accept_op->op_key, @@ -686,27 +892,51 @@ start_next_accept: &accept_op->remote_addr, &accept_op->addr_len); + /* + * Loop while we have immediate connection or when there is error. + */ + } while (status != PJ_EPENDING); } -/* Callback from ioqueue when packet is sent */ +/* + * Callback from ioqueue when packet is sent. + */ static void on_write_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_sent) { - struct tcp_transport *tp = pj_ioqueue_get_user_data(key); + struct tcp_transport *tcp = pj_ioqueue_get_user_data(key); pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key; tdata_op_key->tdata = NULL; + /* Check for error/closure */ + if (bytes_sent <= 0) { + pj_status_t status; + + PJ_LOG(5,(tcp->base.obj_name, "TCP send() error, sent=%d", + bytes_sent)); + + status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) : + -bytes_sent; + if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; + pjsip_transport_shutdown(&tcp->base); + } + if (tdata_op_key->callback) { - tdata_op_key->callback(&tp->base, tdata_op_key->token, bytes_sent); + /* + * Notify sip_transport.c that packet has been sent. + */ + tdata_op_key->callback(&tcp->base, tdata_op_key->token, bytes_sent); } } -/* This callback is called by transport manager to send SIP message */ +/* + * This callback is called by transport manager to send SIP message + */ static pj_status_t tcp_send_msg(pjsip_transport *transport, pjsip_tx_data *tdata, const pj_sockaddr_t *rem_addr, @@ -718,7 +948,8 @@ static pj_status_t tcp_send_msg(pjsip_transport *transport, { struct tcp_transport *tcp = (struct tcp_transport*)transport; pj_ssize_t size; - pj_status_t status; + pj_bool_t delayed = PJ_FALSE; + pj_status_t status = PJ_SUCCESS; /* Sanity check */ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL); @@ -737,38 +968,74 @@ static pj_status_t tcp_send_msg(pjsip_transport *transport, tdata->op_key.callback = callback; /* If asynchronous connect() has not completed yet, just put the - * transmit data in the pending transmission list. + * transmit data in the pending transmission list since we can not + * use the socket yet. */ - pj_lock_acquire(tcp->base.lock); - if (tcp->has_pending_connect) { - struct pending_tdata *pending_tdata; - /* Pust to list */ - pending_tdata = pj_pool_alloc(tdata->pool, sizeof(*pending_tdata)); - pending_tdata->tdata_op_key = &tdata->op_key; + /* + * Looks like connect() is still in progress. Check again (this time + * with holding the lock) to be sure. + */ + pj_lock_acquire(tcp->base.lock); - pj_list_push_back(&tcp->tx_list, pending_tdata); - status = PJ_EPENDING; + if (tcp->has_pending_connect) { + struct delayed_tdata *delayed_tdata; - } else { - /* send to ioqueue! */ + /* + * connect() is still in progress. Put the transmit data to + * the delayed list. + */ + delayed_tdata = pj_pool_alloc(tdata->pool, + sizeof(*delayed_tdata)); + delayed_tdata->tdata_op_key = &tdata->op_key; + + pj_list_push_back(&tcp->delayed_list, delayed_tdata); + status = PJ_EPENDING; + + /* Prevent pj_ioqueue_send() to be called below */ + delayed = PJ_TRUE; + } + + pj_lock_release(tcp->base.lock); + } + + if (!delayed) { + /* + * Transport is ready to go. Send the packet to ioqueue to be + * sent asynchronously. + */ size = tdata->buf.cur - tdata->buf.start; status = pj_ioqueue_send(tcp->key, (pj_ioqueue_op_key_t*)&tdata->op_key, tdata->buf.start, &size, 0); - if (status != PJ_EPENDING) + if (status != PJ_EPENDING) { + /* Not pending (could be immediate success or error) */ tdata->op_key.tdata = NULL; - } - pj_lock_release(tcp->base.lock); + /* Shutdown transport on closure/errors */ + if (size <= 0) { + + PJ_LOG(5,(tcp->base.obj_name, "TCP send() error, sent=%d", + size)); + + if (status == PJ_SUCCESS) + status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); + if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; + pjsip_transport_shutdown(&tcp->base); + } + } + } return status; } -/* This callback is called by transport manager to shutdown transport */ +/* + * This callback is called by transport manager to shutdown transport. + * This normally is only used by UDP transport. + */ static pj_status_t tcp_shutdown(pjsip_transport *transport) { @@ -779,7 +1046,9 @@ static pj_status_t tcp_shutdown(pjsip_transport *transport) } -/* Callback from ioqueue on incoming packet */ +/* + * Callback from ioqueue that an incoming data is received from the socket. + */ static void on_read_complete(pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) @@ -787,13 +1056,14 @@ static void on_read_complete(pj_ioqueue_key_t *key, enum { MAX_IMMEDIATE_PACKET = 10 }; pjsip_rx_data_op_key *rdata_op_key = (pjsip_rx_data_op_key*) op_key; pjsip_rx_data *rdata = rdata_op_key->rdata; - struct tcp_transport *tp = (struct tcp_transport*)rdata->tp_info.transport; + struct tcp_transport *tcp = + (struct tcp_transport*)rdata->tp_info.transport; int i; pj_status_t status; /* Don't do anything if transport is closing. */ - if (tp->is_closing) { - tp->is_closing++; + if (tcp->is_closing) { + tcp->is_closing++; return; } @@ -806,7 +1076,9 @@ static void on_read_complete(pj_ioqueue_key_t *key, for (i=0;; ++i) { pj_uint32_t flags; - /* Report the packet to transport manager. */ + /* Houston, we have packet! Report the packet to transport manager + * to be parsed. + */ if (bytes_read > 0) { pj_size_t size_eaten; @@ -815,6 +1087,10 @@ static void on_read_complete(pj_ioqueue_key_t *key, rdata->pkt_info.zero = 0; pj_gettimeofday(&rdata->pkt_info.timestamp); + /* Report to transport manager. + * The transport manager will tell us how many bytes of the packet + * have been processed (as valid SIP message). + */ size_eaten = pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, rdata); @@ -833,24 +1109,45 @@ static void on_read_complete(pj_ioqueue_key_t *key, } else if (bytes_read == 0) { /* Transport is closed */ - PJ_LOG(4,(tp->base.obj_name, "tcp connection closed")); - tcp_destroy(&tp->base); + PJ_LOG(4,(tcp->base.obj_name, "TCP connection closed")); + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tcp->close_reason==PJ_SUCCESS) + tcp->close_reason = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); + pjsip_transport_shutdown(&tcp->base); + return; - } else if (bytes_read < 0) { + //} else if (bytes_read < 0) { + } else if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) && + -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET)) + { /* Report error to endpoint. */ PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt, rdata->tp_info.transport->obj_name, - -bytes_read, "tcp recv() error")); + -bytes_read, "TCP recv() error")); + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = -bytes_read; + pjsip_transport_shutdown(&tcp->base); - /* Transport error, close transport */ - tcp_destroy(&tp->base); return; } if (i >= MAX_IMMEDIATE_PACKET) { - /* Force ioqueue_recv() to return PJ_EPENDING */ + /* Receive quota reached. Force ioqueue_recv() to + * return PJ_EPENDING + */ flags = PJ_IOQUEUE_ALWAYS_ASYNC; } else { flags = 0; @@ -867,6 +1164,7 @@ static void on_read_complete(pj_ioqueue_key_t *key, &bytes_read, flags); if (status == PJ_SUCCESS) { + /* Continue loop. */ pj_assert(i < MAX_IMMEDIATE_PACKET); @@ -879,30 +1177,56 @@ static void on_read_complete(pj_ioqueue_key_t *key, rdata->tp_info.transport->obj_name, status, "tcp recv() error")); - /* Transport error, close transport */ - tcp_destroy(&tp->base); + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; + pjsip_transport_shutdown(&tcp->base); + return; } } } -/* Callback from ioqueue when connect completes */ +/* + * Callback from ioqueue when asynchronous connect() operation completes. + */ static void on_connect_complete(pj_ioqueue_key_t *key, pj_status_t status) { - struct pending_connect *connect_op = (struct pending_connect *)key; - struct tcp_transport *tcp = connect_op->transport; + struct tcp_transport *tcp; pj_sockaddr_in addr; int addrlen; + tcp = pj_ioqueue_get_user_data(key); + + PJ_LOG(4,(tcp->base.obj_name, + "TCP transport %.*s:%d is connected to %.*s:%d", + (int)tcp->base.local_name.host.slen, + tcp->base.local_name.host.ptr, + tcp->base.local_name.port, + (int)tcp->base.remote_name.host.slen, + tcp->base.remote_name.host.ptr, + tcp->base.remote_name.port)); + /* Mark that pending connect() operation has completed. */ tcp->has_pending_connect = PJ_FALSE; /* Check connect() status */ if (status != PJ_SUCCESS) { + tcp_perror(tcp->base.obj_name, "TCP connect() error", status); - tcp_destroy(&tcp->base); + + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; + pjsip_transport_shutdown(&tcp->base); return; } @@ -925,7 +1249,13 @@ static void on_connect_complete(pj_ioqueue_key_t *key, /* Start pending read */ status = tcp_start_read(tcp); if (status != PJ_SUCCESS) { - tcp_destroy(&tcp->base); + /* We can not destroy the transport since high level objects may + * still keep reference to this transport. So we can only + * instruct transport manager to gracefully start the shutdown + * procedure for this transport. + */ + if (tcp->close_reason==PJ_SUCCESS) tcp->close_reason = status; + pjsip_transport_shutdown(&tcp->base); return; } diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c index 57c6d33f..8e11f2a2 100644 --- a/pjsip/src/pjsua-lib/pjsua_acc.c +++ b/pjsip/src/pjsua-lib/pjsua_acc.c @@ -77,18 +77,18 @@ static void update_acc_contact(unsigned acc_id, unsigned tp_id) { pjsua_acc *acc = &pjsua_var.acc[acc_id]; - pjsip_transport *tp = pjsua_var.tpdata[tp_id].tp; + struct transport_data *t = &pjsua_var.tpdata[tp_id]; char uri[80]; /* Transport must be valid */ - pj_assert(tp != NULL); + pj_assert(t->data.ptr != NULL); /* Build URI for the account */ pj_ansi_sprintf(uri, "", - (int)tp->local_name.host.slen, - tp->local_name.host.ptr, - tp->local_name.port, - pjsip_transport_get_type_name(tp->key.type)); + (int)t->local_name.host.slen, + t->local_name.host.ptr, + t->local_name.port, + pjsip_transport_get_type_name(t->type)); pj_strdup2(pjsua_var.pool, &acc->real_contact, uri); @@ -144,7 +144,7 @@ static pj_status_t initialize_acc(unsigned acc_id) } PJ_TODO(attach_account_to_transport); - if (pjsua_var.tpdata[0].tp) + if (pjsua_var.tpdata[0].data.ptr) update_acc_contact(acc_id, 0); /* Build account route-set from outbound proxies and route set from @@ -220,7 +220,8 @@ PJ_DEF(pj_status_t) pjsua_acc_add( const pjsua_acc_config *cfg, PJ_ETOOMANY); /* Must have a transport */ - PJ_ASSERT_RETURN(pjsua_var.tpdata[0].tp != NULL, PJ_EINVALIDOP); + PJ_TODO(associate_acc_with_transport); + PJ_ASSERT_RETURN(pjsua_var.tpdata[0].data.ptr != NULL, PJ_EINVALIDOP); PJSUA_LOCK(); @@ -281,20 +282,23 @@ PJ_DEF(pj_status_t) pjsua_acc_add_local( pjsua_transport_id tid, pjsua_acc_id *p_acc_id) { pjsua_acc_config cfg; - pjsip_transport *tp; + struct transport_data *t = &pjsua_var.tpdata[tid]; char uri[62]; + /* ID must be valid */ + PJ_ASSERT_RETURN(tid>=0 && tiddata.ptr != NULL, PJ_EINVAL); pjsua_acc_config_default(&cfg); /* Build URI for the account */ - pj_ansi_sprintf(uri, "", - (int)tp->local_name.host.slen, - tp->local_name.host.ptr, - tp->local_name.port); + pj_ansi_sprintf(uri, "", + (int)t->local_name.host.slen, + t->local_name.host.ptr, + t->local_name.port, + pjsip_transport_get_type_name(t->type)); cfg.id = pj_str(uri); diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c index 72c14c00..30bfdd9a 100644 --- a/pjsip/src/pjsua-lib/pjsua_core.c +++ b/pjsip/src/pjsua-lib/pjsua_core.c @@ -729,7 +729,7 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, /* Find empty transport slot */ for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) { - if (pjsua_var.tpdata[id].tp == NULL) + if (pjsua_var.tpdata[id].data.ptr == NULL) break; } @@ -741,7 +741,9 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, /* Create the transport */ if (type == PJSIP_TRANSPORT_UDP) { - + /* + * Create UDP transport. + */ pjsua_transport_config config; pj_sock_t sock = PJ_INVALID_SOCKET; pj_sockaddr_in pub_addr; @@ -773,14 +775,56 @@ PJ_DEF(pj_status_t) pjsua_transport_create( pjsip_transport_type_e type, goto on_return; } + + /* Save the transport */ + pjsua_var.tpdata[id].type = type; + pjsua_var.tpdata[id].local_name = tp->local_name; + pjsua_var.tpdata[id].data.tp = tp; + + } else if (type == PJSIP_TRANSPORT_TCP) { + /* + * Create TCP transport. + */ + pjsua_transport_config config; + pjsip_tpfactory *tcp; + pj_sockaddr_in local_addr; + + /* Supply default config if it's not specified */ + if (cfg == NULL) { + pjsua_transport_config_default(&config); + cfg = &config; + } + + /* Init local address */ + pj_sockaddr_in_init(&local_addr, 0, 0); + + if (cfg->port) + local_addr.sin_port = pj_htons((pj_uint16_t)cfg->port); + + if (cfg->ip_addr.s_addr) + local_addr.sin_addr.s_addr = cfg->ip_addr.s_addr; + + /* Create the TCP transport */ + status = pjsip_tcp_transport_start(pjsua_var.endpt, &local_addr, 1, + &tcp); + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Error creating SIP TCP listener", + status); + goto on_return; + } + + /* Save the transport */ + pjsua_var.tpdata[id].type = type; + pjsua_var.tpdata[id].local_name = tcp->addr_name; + pjsua_var.tpdata[id].data.factory = tcp; + } else { status = PJSIP_EUNSUPTRANSPORT; pjsua_perror(THIS_FILE, "Error creating transport", status); goto on_return; } - /* Save the transport */ - pjsua_var.tpdata[id].tp = tp; /* Return the ID */ if (p_id) *p_id = id; @@ -807,7 +851,7 @@ PJ_DEF(pj_status_t) pjsua_transport_register( pjsip_transport *tp, /* Find empty transport slot */ for (id=0; id < PJ_ARRAY_SIZE(pjsua_var.tpdata); ++id) { - if (pjsua_var.tpdata[id].tp == NULL) + if (pjsua_var.tpdata[id].data.ptr == NULL) break; } @@ -818,7 +862,9 @@ PJ_DEF(pj_status_t) pjsua_transport_register( pjsip_transport *tp, } /* Save the transport */ - pjsua_var.tpdata[id].tp = tp; + pjsua_var.tpdata[id].type = tp->key.type; + pjsua_var.tpdata[id].local_name = tp->local_name; + pjsua_var.tpdata[id].data.tp = tp; /* Return the ID */ if (p_id) *p_id = id; @@ -842,7 +888,7 @@ PJ_DEF(pj_status_t) pjsua_enum_transports( pjsua_transport_id id[], for (i=0, count=0; i=0 && iddata.tp; + + if (tp == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } + + info->id = id; + info->type = tp->key.type; + info->type_name = pj_str(tp->type_name); + info->info = pj_str(tp->info); + info->flag = tp->flag; + info->addr_len = tp->addr_len; + info->local_addr = tp->local_addr; + info->local_name = tp->local_name; + info->usage_count = pj_atomic_get(tp->ref_cnt); + + } else if (pjsua_var.tpdata[id].type == PJSIP_TRANSPORT_TCP) { + + pjsip_tpfactory *factory = t->data.factory; + + if (factory == NULL) { + PJSUA_UNLOCK(); + return PJ_EINVALIDOP; + } - info->id = id; - info->type = tp->key.type; - info->type_name = pj_str(tp->type_name); - info->info = pj_str(tp->info); - info->flag = tp->flag; - info->addr_len = tp->addr_len; - info->local_addr = tp->local_addr; - info->local_name = tp->local_name; - info->usage_count = pj_atomic_get(tp->ref_cnt); + info->id = id; + info->type = t->type; + info->type_name = pj_str("TCP"); + info->info = pj_str("TCP transport"); + info->flag = factory->flag; + info->addr_len = sizeof(factory->local_addr); + info->local_addr = factory->local_addr; + info->local_name = factory->addr_name; + info->usage_count = 0; + + } + PJSUA_UNLOCK(); @@ -906,7 +977,7 @@ PJ_DEF(pj_status_t) pjsua_transport_set_enable( pjsua_transport_id id, PJ_ASSERT_RETURN(id>=0 && id=0 && id #include +extern const char *system_name; + static void usage() { puts("Usage: test-pjsip"); @@ -57,6 +59,15 @@ int main(int argc, char *argv[]) return 1; } log_level = atoi(*opt_arg); + } else if (strcmp(*opt_arg, "-s") == 0 || + strcmp(*opt_arg, "--system") == 0) + { + ++opt_arg; + if (!opt_arg) { + usage(); + return 1; + } + system_name = *opt_arg; } else { usage(); return 1; @@ -67,9 +78,9 @@ int main(int argc, char *argv[]) retval = test_main(); - if (argc != 1) { + if (interractive) { char s[10]; - printf("\n"); + printf("\n"); fflush(stdout); fgets(s, sizeof(s), stdin); } diff --git a/pjsip/src/test-pjsip/msg_logger.c b/pjsip/src/test-pjsip/msg_logger.c index a4eac0da..e517ae05 100644 --- a/pjsip/src/test-pjsip/msg_logger.c +++ b/pjsip/src/test-pjsip/msg_logger.c @@ -27,13 +27,15 @@ static pj_bool_t msg_log_enabled; static pj_bool_t on_rx_msg(pjsip_rx_data *rdata) { if (msg_log_enabled) { - PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%d:\n" - "%s\n" + PJ_LOG(4,(THIS_FILE, "RX %d bytes %s from %s:%s:%d:\n" + "%.*s\n" "--end msg--", rdata->msg_info.len, pjsip_rx_data_get_info(rdata), + rdata->tp_info.transport->type_name, rdata->pkt_info.src_name, rdata->pkt_info.src_port, + rdata->msg_info.len, rdata->msg_info.msg_buf)); } @@ -43,13 +45,15 @@ static pj_bool_t on_rx_msg(pjsip_rx_data *rdata) static pj_status_t on_tx_msg(pjsip_tx_data *tdata) { if (msg_log_enabled) { - PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%d:\n" - "%s\n" + PJ_LOG(4,(THIS_FILE, "TX %d bytes %s to %s:%s:%d:\n" + "%.*s\n" "--end msg--", (tdata->buf.cur - tdata->buf.start), pjsip_tx_data_get_info(tdata), + tdata->tp_info.transport->type_name, tdata->tp_info.dst_name, tdata->tp_info.dst_port, + (tdata->buf.cur - tdata->buf.start), tdata->buf.start)); } return PJ_SUCCESS; diff --git a/pjsip/src/test-pjsip/msg_test.c b/pjsip/src/test-pjsip/msg_test.c index f5debe84..ebe43025 100644 --- a/pjsip/src/test-pjsip/msg_test.c +++ b/pjsip/src/test-pjsip/msg_test.c @@ -21,7 +21,11 @@ #include #define POOL_SIZE 8000 -#define LOOP 10000 +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 +# define LOOP 10000 +#else +# define LOOP 100000 +#endif #define AVERAGE_MSG_LEN 800 #define THIS_FILE "msg_test.c" @@ -35,8 +39,6 @@ static pjsip_msg *create_msg1(pj_pool_t *pool); #define FLAG_PARSE_ONLY 4 #define FLAG_PRINT_ONLY 8 -static int flag = 0; - struct test_msg { char msg[1024]; @@ -101,8 +103,12 @@ struct test_msg } }; -static pj_highprec_t detect_len, parse_len, print_len; -static pj_timestamp detect_time, parse_time, print_time; +static struct +{ + int flag; + pj_highprec_t detect_len, parse_len, print_len; + pj_timestamp detect_time, parse_time, print_time; +} var; static pj_status_t test_entry( pj_pool_t *pool, struct test_msg *entry ) { @@ -121,17 +127,17 @@ static pj_status_t test_entry( pj_pool_t *pool, struct test_msg *entry ) entry->len = pj_native_strlen(entry->msg); - if (flag & FLAG_PARSE_ONLY) + if (var.flag & FLAG_PARSE_ONLY) goto parse_msg; - if (flag & FLAG_PRINT_ONLY) { + if (var.flag & FLAG_PRINT_ONLY) { if (print_msg == NULL) print_msg = entry->creator(pool); goto print_msg; } /* Detect message. */ - detect_len = detect_len + entry->len; + var.detect_len = var.detect_len + entry->len; pj_get_timestamp(&t1); status = pjsip_find_msg(entry->msg, entry->len, PJ_FALSE, &msg_size); if (status != PJ_SUCCESS) { @@ -148,14 +154,14 @@ static pj_status_t test_entry( pj_pool_t *pool, struct test_msg *entry ) } pj_get_timestamp(&t2); pj_sub_timestamp(&t2, &t1); - pj_add_timestamp(&detect_time, &t2); + pj_add_timestamp(&var.detect_time, &t2); - if (flag & FLAG_DETECT_ONLY) + if (var.flag & FLAG_DETECT_ONLY) return PJ_SUCCESS; /* Parse message. */ parse_msg: - parse_len = parse_len + entry->len; + var.parse_len = var.parse_len + entry->len; pj_get_timestamp(&t1); pj_list_init(&err_list); parsed_msg = pjsip_parse_msg(pool, entry->msg, entry->len, &err_list); @@ -171,9 +177,9 @@ parse_msg: } pj_get_timestamp(&t2); pj_sub_timestamp(&t2, &t1); - pj_add_timestamp(&parse_time, &t2); + pj_add_timestamp(&var.parse_time, &t2); - if (flag & FLAG_PARSE_ONLY) + if (var.flag & FLAG_PARSE_ONLY) return PJ_SUCCESS; /* Create reference message. */ @@ -306,9 +312,9 @@ parse_msg: /* Print message. */ print_msg: - print_len = print_len + entry->len; + var.print_len = var.print_len + entry->len; pj_get_timestamp(&t1); - if (flag && FLAG_PRINT_ONLY) + if (var.flag && FLAG_PRINT_ONLY) ref_msg = print_msg; len = pjsip_msg_print(ref_msg, msgbuf1, PJSIP_MAX_PKT_LEN); if (len < 1) { @@ -317,7 +323,7 @@ print_msg: } pj_get_timestamp(&t2); pj_sub_timestamp(&t2, &t1); - pj_add_timestamp(&print_time, &t2); + pj_add_timestamp(&var.print_time, &t2); status = PJ_SUCCESS; @@ -674,17 +680,14 @@ static pjsip_msg *create_msg1(pj_pool_t *pool) /*****************************************************************************/ -int msg_test(void) +static pj_status_t simple_test(void) { + unsigned i; pj_status_t status; - pj_pool_t *pool; - int i, loop; - pj_timestamp zero; - pj_time_val elapsed; - pj_highprec_t avg_detect, avg_parse, avg_print, kbytes; PJ_LOG(3,(THIS_FILE, " simple test..")); for (i=0; i max) max = run[i].detect; + + PJ_LOG(3,("", " Maximum message detection/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP messages " + "can be pre-parse by pjsip_find_msg() " + "per second (tested with %d message sets with " + "average message length of " + "%d bytes)", PJ_ARRAY_SIZE(test_array), avg_len); + report_ival("msg-detect-per-sec", max, "msg/sec", desc); + + /* Print maximum parse/sec */ + for (i=0, max=0; i max) max = run[i].parse; + + PJ_LOG(3,("", " Maximum message parsing/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP messages " + "can be parsed by pjsip_parse_msg() " + "per second (tested with %d message sets with " + "average message length of " + "%d bytes)", PJ_ARRAY_SIZE(test_array), avg_len); + report_ival("msg-parse-per-sec", max, "msg/sec", desc); + + /* Msg parsing bandwidth */ + report_ival("msg-parse-bandwidth-mb", avg_len*max/1000000, "MB/sec", + "Message parsing bandwidth in megabytes (number of megabytes" + " worth of SIP messages that can be parsed per second). " + "The value is derived from msg-parse-per-sec above."); + + + /* Print maximum print/sec */ + for (i=0, max=0; i max) max = run[i].print; + + PJ_LOG(3,("", " Maximum message print/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP messages " + "can be printed by pjsip_msg_print()" + " per second (tested with %d message sets with " + "average message length of " + "%d bytes)", PJ_ARRAY_SIZE(test_array), avg_len); + + report_ival("msg-print-per-sec", max, "msg/sec", desc); + + /* Msg print bandwidth */ + report_ival("msg-printed-bandwidth-mb", avg_len*max/1000000, "MB/sec", + "Message print bandwidth in megabytes (total size of " + "SIP messages printed per second). " + "The value is derived from msg-print-per-sec above."); + + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/test-pjsip/test.c b/pjsip/src/test-pjsip/test.c index b8707332..3ae58daa 100644 --- a/pjsip/src/test-pjsip/test.c +++ b/pjsip/src/test-pjsip/test.c @@ -33,11 +33,24 @@ if (rc!=0) goto on_return; \ } while (0) +#define DO_TSX_TEST(test, param) \ + do { \ + PJ_LOG(3, (THIS_FILE, "Running %s(%s)...", #test, (param)->tp_type)); \ + rc = test(param); \ + PJ_LOG(3, (THIS_FILE, \ + "%s(%d)", \ + (rc ? "..ERROR" : "..success"), rc)); \ + if (rc!=0) goto on_return; \ + } while (0) pjsip_endpoint *endpt; int log_level = 3; +static pj_oshandle_t fd_report; +const char *system_name = "Unknown"; +static char buf[1024]; + void app_perror(const char *msg, pj_status_t rc) { char errbuf[256]; @@ -75,11 +88,142 @@ pj_status_t register_static_modules(pj_size_t *count, pjsip_module **modules) return PJ_SUCCESS; } +static pj_status_t init_report(void) +{ + char tmp[80]; + pj_time_val timestamp; + pj_parsed_time date_time; + pj_ssize_t len; + pj_status_t status; + + pj_ansi_sprintf(tmp, "pjsip-static-bench-%s-%s.htm", PJ_OS_NAME, PJ_CC_NAME); + + status = pj_file_open(NULL, tmp, PJ_O_WRONLY, &fd_report); + if (status != PJ_SUCCESS) + return status; + + /* Title */ + len = pj_ansi_sprintf(buf, "\n" + " \n" + " PJSIP %s (%s) - Static Benchmark\n" + " \n" + "\n" + "\n", + PJ_VERSION, + (PJ_DEBUG ? "Debug" : "Release")); + pj_file_write(fd_report, buf, &len); + + + /* Title */ + len = pj_ansi_sprintf(buf, "

PJSIP %s (%s) - Static Benchmark

\n", + PJ_VERSION, + (PJ_DEBUG ? "Debug" : "Release")); + pj_file_write(fd_report, buf, &len); + + len = pj_ansi_sprintf(buf, "

Below is the benchmark result generated " + "by test-pjsip program. The program " + "is single-threaded only.

\n"); + pj_file_write(fd_report, buf, &len); + + + /* Write table heading */ + len = pj_ansi_sprintf(buf, "\n" + " \n" + " \n" + " \n" + " \n"); + pj_file_write(fd_report, buf, &len); + + + /* Write version */ + report_sval("version", PJ_VERSION, "", "PJLIB/PJSIP version"); + + + /* Debug or release */ + report_sval("build-type", (PJ_DEBUG ? "Debug" : "Release"), "", "Build type"); + + + /* Write timestamp */ + pj_gettimeofday(×tamp); + report_ival("timestamp", timestamp.sec, "", "System timestamp of the test"); + + + /* Write time of day */ + pj_time_decode(×tamp, &date_time); + len = pj_ansi_sprintf(tmp, "%04d-%02d-%02d %02d:%02d:%02d", + date_time.year, date_time.mon+1, date_time.day, + date_time.hour, date_time.min, date_time.sec); + report_sval("date-time", tmp, "", "Date/time of the test"); + + + /* Write System */ + report_sval("system", system_name, "", "System description"); + + + /* Write OS type */ + report_sval("os-family", PJ_OS_NAME, "", "Operating system family"); + + + /* Write CC name */ + len = pj_ansi_sprintf(tmp, "%s-%d.%d.%d", PJ_CC_NAME, + PJ_CC_VER_1, PJ_CC_VER_2, PJ_CC_VER_2); + report_sval("cc-name", tmp, "", "Compiler name and version"); + + + return PJ_SUCCESS; +} + +void report_sval(const char *name, const char* value, const char *valname, + const char *desc) +{ + pj_ssize_t len; + + len = pj_ansi_sprintf(buf, " \n" + " \n" + " \n" + " \n", + name, value, valname, desc); + pj_file_write(fd_report, buf, &len); +} + + +void report_ival(const char *name, int value, const char *valname, + const char *desc) +{ + pj_ssize_t len; + + len = pj_ansi_sprintf(buf, " \n" + " \n" + " \n" + " \n", + name, value, valname, desc); + pj_file_write(fd_report, buf, &len); + +} + +static void close_report(void) +{ + pj_ssize_t len; + + if (fd_report) { + len = pj_ansi_sprintf(buf, "
VariableValueDescription
%s%s %s%s
%s%d %s%s
\n\n\n"); + pj_file_write(fd_report, buf, &len); + + pj_file_close(fd_report); + } +} + + int test_main(void) { pj_status_t rc; pj_caching_pool caching_pool; const char *filename; + unsigned i, tsx_test_cnt=0; + struct tsx_test_param tsx_test[10]; + pj_status_t status; + pjsip_transport *tp; + pjsip_tpfactory *tpfactory; int line; pj_log_set_level(log_level); @@ -93,6 +237,10 @@ int test_main(void) return rc; } + status = init_report(); + if (status != PJ_SUCCESS) + return status; + pj_dump_config(); pj_caching_pool_init( &caching_pool, &pj_pool_factory_default_policy, 0 ); @@ -124,6 +272,11 @@ int test_main(void) rc); goto on_return; } + tsx_test[tsx_test_cnt].port = 5060; + tsx_test[tsx_test_cnt].tp_type = "loop-dgram"; + tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_LOOP_DGRAM; + ++tsx_test_cnt; + #if INCLUDE_URI_TEST DO_TEST(uri_test()); @@ -138,6 +291,10 @@ int test_main(void) DO_TEST(txdata_test()); #endif +#if INCLUDE_TSX_BENCH + DO_TEST(tsx_bench()); +#endif + #if INCLUDE_UDP_TEST DO_TEST(transport_udp_test()); #endif @@ -146,15 +303,43 @@ int test_main(void) DO_TEST(transport_loop_test()); #endif +#if INCLUDE_TCP_TEST + DO_TEST(transport_tcp_test()); +#endif + + #if INCLUDE_TSX_TEST - DO_TEST(tsx_basic_test()); - DO_TEST(tsx_uac_test()); - DO_TEST(tsx_uas_test()); + status = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &tp); + if (status == PJ_SUCCESS) { + tsx_test[tsx_test_cnt].port = tp->local_name.port; + tsx_test[tsx_test_cnt].tp_type = "udp"; + tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_UDP; + ++tsx_test_cnt; + } + + status = pjsip_tcp_transport_start(endpt, NULL, 1, &tpfactory); + if (status == PJ_SUCCESS) { + tsx_test[tsx_test_cnt].port = tpfactory->addr_name.port; + tsx_test[tsx_test_cnt].tp_type = "tcp"; + tsx_test[tsx_test_cnt].type = PJSIP_TRANSPORT_TCP; + ++tsx_test_cnt; + } else { + app_perror("Unable to create TCP", status); + rc = -4; + goto on_return; + } + + + for (i=0; iref_cnt); + /* Test basic transport attributes */ status = generic_transport_test(loop); if (status != PJ_SUCCESS) return status; /* Basic transport's send/receive loopback test. */ - for (i=0; i<2; ++i) { + for (i=0; iref_cnt) != 1) { - return -50; + /* Check reference counter. */ + if (pj_atomic_get(loop->ref_cnt) != ref_cnt) { + PJ_LOG(3,(THIS_FILE, " error: ref counter is not %d (%d)", + ref_cnt, pj_atomic_get(loop->ref_cnt))); + return -51; } /* Decrement reference. */ diff --git a/pjsip/src/test-pjsip/transport_tcp_test.c b/pjsip/src/test-pjsip/transport_tcp_test.c new file mode 100644 index 00000000..70f1bc92 --- /dev/null +++ b/pjsip/src/test-pjsip/transport_tcp_test.c @@ -0,0 +1,143 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 +#include + +#define THIS_FILE "transport_tcp_test.c" + + +/* + * TCP transport test. + */ +int transport_tcp_test(void) +{ + enum { SEND_RECV_LOOP = 8 }; + pjsip_tpfactory *tpfactory; + pjsip_transport *tcp; + pj_sockaddr_in rem_addr; + pj_status_t status; + char url[64]; + unsigned rtt[SEND_RECV_LOOP], min_rtt; + int i, pkt_lost; + + /* Start TCP listener on arbitrary port. */ + status = pjsip_tcp_transport_start(endpt, NULL, 1, &tpfactory); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to start TCP transport", status); + return -10; + } + + + /* Get the listener address */ + status = pj_sockaddr_in_init(&rem_addr, &tpfactory->addr_name.host, + (pj_uint16_t)tpfactory->addr_name.port); + if (status != PJ_SUCCESS) { + app_perror(" Error: possibly invalid TCP address name", status); + return -14; + } + + pj_ansi_sprintf(url, "sip:alice@%s:%d;transport=tcp", + pj_inet_ntoa(rem_addr.sin_addr), + pj_ntohs(rem_addr.sin_port)); + + + /* Acquire one TCP transport. */ + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_TCP, + &rem_addr, sizeof(rem_addr), + &tcp); + if (status != PJ_SUCCESS || tcp == NULL) { + app_perror(" Error: unable to acquire TCP transport", status); + return -17; + } + + /* After pjsip_endpt_acquire_transport, TCP transport must have + * reference counter 1. + */ + if (pj_atomic_get(tcp->ref_cnt) != 1) + return -20; + + /* Test basic transport attributes */ + status = generic_transport_test(tcp); + if (status != PJ_SUCCESS) + return status; + + + /* Check again that reference counter is 1. */ + if (pj_atomic_get(tcp->ref_cnt) != 1) + return -70; + + /* Basic transport's send/receive loopback test. */ + for (i=0; iref_cnt) != 1) + return -80; + + /* Destroy this transport. */ + pjsip_transport_dec_ref(tcp); + + /* Force destroy this transport. */ + status = pjsip_transport_destroy(tcp); + if (status != PJ_SUCCESS) + return -90; + + /* Unregister factory */ + status = pjsip_tpmgr_unregister_tpfactory(pjsip_endpt_get_tpmgr(endpt), + tpfactory); + if (status != PJ_SUCCESS) + return -95; + + /* Flush events. */ + PJ_LOG(3,(THIS_FILE, " Flushing events, 1 second...")); + flush_events(1000); + + /* Done */ + return 0; +} diff --git a/pjsip/src/test-pjsip/transport_test.c b/pjsip/src/test-pjsip/transport_test.c index 0cd0d225..5aa491a2 100644 --- a/pjsip/src/test-pjsip/transport_test.c +++ b/pjsip/src/test-pjsip/transport_test.c @@ -173,7 +173,8 @@ static void send_msg_callback(pjsip_send_state *stateless_data, /* Test that we receive loopback message. */ int transport_send_recv_test( pjsip_transport_type_e tp_type, pjsip_transport *ref_tp, - char *target_url ) + char *target_url, + int *p_usec_rtt) { pj_bool_t msg_log_enabled; pj_status_t status; @@ -220,6 +221,8 @@ int transport_send_recv_test( pjsip_transport_type_e tp_type, pj_get_timestamp(&my_send_time); /* Send the message (statelessly). */ + PJ_LOG(5,(THIS_FILE, "Sending request to %.*s", + (int)target.slen, target.ptr)); status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL, &send_msg_callback); if (status != PJ_SUCCESS) { @@ -228,9 +231,9 @@ int transport_send_recv_test( pjsip_transport_type_e tp_type, send_status = status; } - /* Set the timeout (1 second from now) */ + /* Set the timeout (2 seconds from now) */ pj_gettimeofday(&timeout); - timeout.sec += 1; + timeout.sec += 2; /* Loop handling events until we get status */ do { @@ -268,7 +271,10 @@ int transport_send_recv_test( pjsip_transport_type_e tp_type, if (status == PJ_SUCCESS) { unsigned usec_rt; usec_rt = pj_elapsed_usec(&my_send_time, &my_recv_time); + PJ_LOG(3,(THIS_FILE, " round-trip = %d usec", usec_rt)); + + *p_usec_rtt = usec_rt; } /* Restore message logging. */ @@ -516,7 +522,6 @@ int transport_rt_test( pjsip_transport_type_e tp_type, unsigned total_sent; unsigned total_recv; - PJ_LOG(3,(THIS_FILE, " multithreaded round-trip test (%d threads)...", THREADS)); PJ_LOG(3,(THIS_FILE, " this will take approx %d seconds, please wait..", diff --git a/pjsip/src/test-pjsip/transport_udp_test.c b/pjsip/src/test-pjsip/transport_udp_test.c index 4db0a7ca..34bb02c4 100644 --- a/pjsip/src/test-pjsip/transport_udp_test.c +++ b/pjsip/src/test-pjsip/transport_udp_test.c @@ -29,11 +29,12 @@ */ int transport_udp_test(void) { - enum { SEND_RECV_LOOP = 2 }; + enum { SEND_RECV_LOOP = 8 }; pjsip_transport *udp_tp, *tp; pj_sockaddr_in addr, rem_addr; pj_str_t s; pj_status_t status; + unsigned rtt[SEND_RECV_LOOP], min_rtt; int i, pkt_lost; pj_sockaddr_in_init(&addr, NULL, TEST_UDP_PORT); @@ -79,11 +80,22 @@ int transport_udp_test(void) pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "127.0.0.1"), TEST_UDP_PORT); for (i=0; iport, param->tp_type); + pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + status = tsx_layer_test(); if (status != 0) return status; diff --git a/pjsip/src/test-pjsip/tsx_bench.c b/pjsip/src/test-pjsip/tsx_bench.c new file mode 100644 index 00000000..53c950cb --- /dev/null +++ b/pjsip/src/test-pjsip/tsx_bench.c @@ -0,0 +1,273 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 +#include + +#define THIS_FILE "tsx_uas_test.c" + + +static pjsip_module mod_tsx_user; + +static int uac_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed) +{ + unsigned i; + pjsip_tx_data *request; + pjsip_transaction **tsx; + pj_timestamp t1, t2, elapsed; + pj_status_t status; + + /* Create the request first. */ + pj_str_t str_target = pj_str("sip:someuser@someprovider.com"); + pj_str_t str_from = pj_str("\"Local User\" "); + pj_str_t str_to = pj_str("\"Remote User\" "); + pj_str_t str_contact = str_from; + + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &str_target, &str_from, &str_to, + &str_contact, NULL, -1, NULL, + &request); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return status; + } + + /* Create transaction array */ + tsx = pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*)); + + pj_memset(&mod_tsx_user, 0, sizeof(mod_tsx_user)); + mod_tsx_user.id = -1; + + /* Benchmark */ + elapsed.u64 = 0; + pj_get_timestamp(&t1); + for (i=0; iu64 = elapsed.u64; + status = PJ_SUCCESS; + +on_error: + for (i=0; i"); + pj_str_t str_to = pj_str("\"Remote User\" "); + pj_str_t str_contact = str_from; + + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &str_target, &str_from, &str_to, + &str_contact, NULL, -1, NULL, + &request); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return status; + } + + /* Create Via */ + via = pjsip_via_hdr_create(request->pool); + via->sent_by.host = pj_str("192.168.0.7"); + via->sent_by.port = 5061; + via->transport = pj_str("udp"); + via->rport_param = 1; + via->recvd_param = pj_str("192.168.0.7"); + pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via); + + + /* Create "dummy" rdata from the tdata */ + pj_memset(&rdata, 0, sizeof(pjsip_rx_data)); + rdata.tp_info.pool = request->pool; + rdata.msg_info.msg = request->msg; + rdata.msg_info.from = pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.to = pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL); + rdata.msg_info.cseq = pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL); + rdata.msg_info.cid = pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.via = via; + + pj_sockaddr_in_init(&remote, 0, 0); + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM, + &remote, sizeof(pj_sockaddr_in), + &rdata.tp_info.transport); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to get loop transport", status); + return status; + } + + + /* Create transaction array */ + tsx = pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*)); + + pj_memset(&mod_tsx_user, 0, sizeof(mod_tsx_user)); + mod_tsx_user.id = -1; + + + /* Benchmark */ + elapsed.u64 = 0; + pj_get_timestamp(&t1); + for (i=0; ibranch_param.ptr = branch_buf; + via->branch_param.slen = PJSIP_RFC3261_BRANCH_LEN + + pj_ansi_sprintf(branch_buf+PJSIP_RFC3261_BRANCH_LEN, + "-%d", i); + status = pjsip_tsx_create_uas(&mod_tsx_user, &rdata, &tsx[i]); + if (status != PJ_SUCCESS) + goto on_error; + + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&elapsed, &t2); + + p_elapsed->u64 = elapsed.u64; + status = PJ_SUCCESS; + +on_error: + for (i=0; ipjsip_tsx_create_uac(), based on the time " + "to create %d simultaneous transactions above.", + WORKING_SET); + + report_ival("create-uac-tsx-per-sec", + speed, "tsx/sec", desc); + + + + /* + * Benchmark UAS + */ + PJ_LOG(3,(THIS_FILE, " benchmarking UAS transaction creation:")); + for (i=0; ipjsip_tsx_create_uas(), based on the time " + "to create %d simultaneous transactions above.", + WORKING_SET); + + report_ival("create-uas-tsx-per-sec", + speed, "tsx/sec", desc); + + return PJ_SUCCESS; +} + diff --git a/pjsip/src/test-pjsip/tsx_uac_test.c b/pjsip/src/test-pjsip/tsx_uac_test.c index d2717fec..b97b01a9 100644 --- a/pjsip/src/test-pjsip/tsx_uac_test.c +++ b/pjsip/src/test-pjsip/tsx_uac_test.c @@ -91,6 +91,10 @@ static char *TEST9_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test9"; #define TEST4_RETRANSMIT_CNT 3 #define TEST5_RETRANSMIT_CNT 3 +static char TARGET_URI[128]; +static char FROM_URI[128]; +static unsigned tp_flag; +static struct tsx_test_param *test_param; static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e); static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata); @@ -140,7 +144,12 @@ static pj_bool_t test_complete; static pjsip_transport *loop; /* General timer entry to be used by tests. */ -static pj_timer_entry timer; +static struct my_timer +{ + pj_timer_entry entry; + char key_buf[1024]; + pj_str_t tsx_key; +} timer; /* * This is the handler to receive state changed notification from the @@ -166,6 +175,41 @@ static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) tsx->status_code, PJSIP_SC_TSX_TIMEOUT)); test_complete = -710; } + + + /* If transport is reliable, then there must not be any + * retransmissions. + */ + if (tp_flag & PJSIP_TRANSPORT_RELIABLE) { + if (recv_count != 1) { + PJ_LOG(3,(THIS_FILE, + " error: there were %d (re)transmissions", + recv_count)); + test_complete = -715; + } + } else { + /* Check the number of transmissions, which must be + * 6 for INVITE and 10 for non-INVITE + */ + if (tsx->method.id==PJSIP_INVITE_METHOD && recv_count != 7) { + PJ_LOG(3,(THIS_FILE, + " error: there were %d (re)transmissions", + recv_count)); + test_complete = -716; + } else + if (tsx->method.id==PJSIP_OPTIONS_METHOD && recv_count != 11) { + PJ_LOG(3,(THIS_FILE, + " error: there were %d (re)transmissions", + recv_count)); + test_complete = -717; + } else + if (tsx->method.id!=PJSIP_INVITE_METHOD && + tsx->method.id!=PJSIP_OPTIONS_METHOD) + { + PJ_LOG(3,(THIS_FILE, " error: unexpected method")); + test_complete = -718; + } + } } } else if (pj_strcmp2(&tsx->branch, TEST2_BRANCH_ID)==0) { @@ -515,7 +559,8 @@ static void send_response_callback( pj_timer_heap_t *timer_heap, static void terminate_tsx_callback( pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) { - pjsip_transaction *tsx = entry->user_data; + struct my_timer *m = (struct my_timer *)entry; + pjsip_transaction *tsx = pjsip_tsx_layer_find_tsx(&m->tsx_key, PJ_FALSE); int status_code = entry->id; if (tsx) { @@ -720,9 +765,9 @@ static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata) if (r->res_addr.transport) pjsip_transport_add_ref(r->res_addr.transport); - timer.cb = &send_response_callback; - timer.user_data = r; - pjsip_endpt_schedule_timer(endpt, &timer, &delay); + timer.entry.cb = &send_response_callback; + timer.entry.user_data = r; + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); return PJ_TRUE; @@ -768,11 +813,11 @@ static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata) PJSIP_ROLE_UAC, &pjsip_invite_method, rdata); - timer.user_data = pjsip_tsx_layer_find_tsx(&key, PJ_FALSE); - timer.id = 301; - timer.cb = &terminate_tsx_callback; + pj_strcpy(&timer.tsx_key, &key); + timer.entry.id = 301; + timer.entry.cb = &terminate_tsx_callback; - pjsip_endpt_schedule_timer(endpt, &timer, &delay); + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); } if (recv_count > 2) { @@ -841,9 +886,9 @@ static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata) if (r->res_addr.transport) pjsip_transport_add_ref(r->res_addr.transport); - timer.cb = &send_response_callback; - timer.user_data = r; - pjsip_endpt_schedule_timer(endpt, &timer, &delay); + timer.entry.cb = &send_response_callback; + timer.entry.user_data = r; + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); } else if (method->id == PJSIP_ACK_METHOD) { @@ -859,11 +904,11 @@ static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata) PJSIP_ROLE_UAC, &pjsip_invite_method, rdata); - timer.user_data = pjsip_tsx_layer_find_tsx(&key, PJ_FALSE); - timer.id = 302; - timer.cb = &terminate_tsx_callback; + pj_strcpy(&timer.tsx_key, &key); + timer.entry.id = 302; + timer.entry.cb = &terminate_tsx_callback; - pjsip_endpt_schedule_timer(endpt, &timer, &delay); + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); } if (recv_count > 2) { @@ -980,10 +1025,23 @@ static int perform_tsx_test(int dummy, char *target_uri, char *from_uri, } pjsip_tx_data_dec_ref(tdata); return test_complete; - } - /* Allow transaction to destroy itself */ - flush_events(500); + } else { + pj_time_val now; + + /* Allow transaction to destroy itself */ + flush_events(500); + + /* Wait until test completes */ + pj_gettimeofday(&now); + + if (PJ_TIME_VAL_LT(now, timeout)) { + pj_time_val interval; + interval = timeout; + PJ_TIME_VAL_SUB(interval, now); + flush_events(PJ_TIME_VAL_MSEC(interval)); + } + } /* Make sure transaction has been destroyed. */ if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) { @@ -1052,8 +1110,7 @@ static int tsx_uac_retransmit_test(void) pjsip_loop_set_recv_delay(loop, sub_test[i].delay, NULL); /* Do the test. */ - status = perform_tsx_test(-500, "sip:bob@127.0.0.1;transport=loop-dgram", - "sip:alice@127.0.0.1;transport=loop-dgram", + status = perform_tsx_test(-500, TARGET_URI, FROM_URI, TEST1_BRANCH_ID, 35, sub_test[i].method); if (status != 0) @@ -1093,9 +1150,8 @@ static int tsx_resolve_error_test(void) PJ_LOG(3,(THIS_FILE, " variant a: immediate resolving error")); status = perform_tsx_test(-800, - "sip:bob@unresolved-host;transport=loop-dgram", - "sip:alice@127.0.0.1;transport=loop-dgram", - TEST2_BRANCH_ID, 10, + "sip:bob@unresolved-host", + FROM_URI, TEST2_BRANCH_ID, 10, &pjsip_options_method); if (status != 0) return status; @@ -1105,20 +1161,22 @@ static int tsx_resolve_error_test(void) */ PJ_LOG(3,(THIS_FILE, " variant b: error via callback")); - /* Set loop transport to return delayed error. */ - pjsip_loop_set_failure(loop, 2, NULL); - pjsip_loop_set_send_callback_delay(loop, 10, NULL); + /* This only applies to "loop-dgram" transport */ + if (test_param->type == PJSIP_TRANSPORT_LOOP_DGRAM) { + /* Set loop transport to return delayed error. */ + pjsip_loop_set_failure(loop, 2, NULL); + pjsip_loop_set_send_callback_delay(loop, 10, NULL); - status = perform_tsx_test(-800, "sip:bob@127.0.0.1;transport=loop-dgram", - "sip:alice@127.0.0.1;transport=loop-dgram", - TEST2_BRANCH_ID, 2, - &pjsip_options_method); - if (status != 0) - return status; + status = perform_tsx_test(-800, TARGET_URI, FROM_URI, + TEST2_BRANCH_ID, 2, + &pjsip_options_method); + if (status != 0) + return status; - /* Restore loop transport settings. */ - pjsip_loop_set_failure(loop, 0, NULL); - pjsip_loop_set_send_callback_delay(loop, 0, NULL); + /* Restore loop transport settings. */ + pjsip_loop_set_failure(loop, 0, NULL); + pjsip_loop_set_send_callback_delay(loop, 0, NULL); + } return status; } @@ -1143,8 +1201,7 @@ static int tsx_terminate_resolving_test(void) pjsip_loop_set_send_callback_delay(loop, 100, &prev_delay); /* Start the test. */ - status = perform_tsx_test(-900, "sip:127.0.0.1;transport=loop-dgram", - "sip:127.0.0.1;transport=loop-dgram", + status = perform_tsx_test(-900, TARGET_URI, FROM_URI, TEST3_BRANCH_ID, 2, &pjsip_options_method); /* Restore delay. */ @@ -1186,8 +1243,7 @@ static int tsx_retransmit_fail_test(void) pjsip_loop_set_failure(loop, 0, 0); /* Start the test. */ - status = perform_tsx_test(-1000, "sip:127.0.0.1;transport=loop-dgram", - "sip:127.0.0.1;transport=loop-dgram", + status = perform_tsx_test(-1000, TARGET_URI, FROM_URI, TEST4_BRANCH_ID, 6, &pjsip_options_method); if (status != 0) @@ -1218,8 +1274,7 @@ static int tsx_terminate_after_retransmit_test(void) PJ_LOG(3,(THIS_FILE, " test5: terminate after retransmissions")); /* Do the test. */ - status = perform_tsx_test(-1100, "sip:bob@127.0.0.1;transport=loop-dgram", - "sip:alice@127.0.0.1;transport=loop-dgram", + status = perform_tsx_test(-1100, TARGET_URI, FROM_URI, TEST5_BRANCH_ID, 6, &pjsip_options_method); @@ -1249,18 +1304,20 @@ static int perform_generic_test( const char *title, /* Do the test. */ for (i=0; itype == PJSIP_TRANSPORT_LOOP_DGRAM) { + PJ_LOG(3,(THIS_FILE, " variant %c: with %d ms transport delay", + ('a'+i), delay[i])); - pjsip_loop_set_delay(loop, delay[i]); + pjsip_loop_set_delay(loop, delay[i]); + } - status = perform_tsx_test(-1200, - "sip:bob@127.0.0.1;transport=loop-dgram", - "sip:alice@127.0.0.1;transport=loop-dgram", - branch_id, - 10, method); + status = perform_tsx_test(-1200, TARGET_URI, FROM_URI, + branch_id, 10, method); if (status != 0) return status; + + if (test_param->type != PJSIP_TRANSPORT_LOOP_DGRAM) + break; } pjsip_loop_set_delay(loop, 0); @@ -1276,11 +1333,23 @@ static int perform_generic_test( const char *title, ** ***************************************************************************** */ -int tsx_uac_test(void) +int tsx_uac_test(struct tsx_test_param *param) { pj_sockaddr_in addr; pj_status_t status; + timer.tsx_key.ptr = timer.key_buf; + + test_param = param; + + /* Get transport flag */ + tp_flag = pjsip_transport_get_flag_from_type(test_param->type); + + pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + /* Check if loop transport is configured. */ status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM, &addr, sizeof(addr), &loop); @@ -1316,15 +1385,23 @@ int tsx_uac_test(void) if (status != 0) return status; - /* TEST4_BRANCH_ID: Transport failed after several retransmissions */ - status = tsx_retransmit_fail_test(); - if (status != 0) - return status; + /* TEST4_BRANCH_ID: Transport failed after several retransmissions. + * Only applies to loop transport. + */ + if (test_param->type == PJSIP_TRANSPORT_LOOP_DGRAM) { + status = tsx_retransmit_fail_test(); + if (status != 0) + return status; + } - /* TEST5_BRANCH_ID: Terminate transaction after several retransmissions */ - status = tsx_terminate_after_retransmit_test(); - if (status != 0) - return status; + /* TEST5_BRANCH_ID: Terminate transaction after several retransmissions + * Only applicable to non-reliable transports. + */ + if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) { + status = tsx_terminate_after_retransmit_test(); + if (status != 0) + return status; + } /* TEST6_BRANCH_ID: Successfull non-invite transaction */ status = perform_generic_test("test6: successfull non-invite transaction", @@ -1352,8 +1429,21 @@ int tsx_uac_test(void) if (status != 0) return status; - pjsip_transport_dec_ref(loop); + flush_events(500); + + /* Unregister modules. */ + status = pjsip_endpt_unregister_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to unregister module", status); + return -31; + } + status = pjsip_endpt_unregister_module(endpt, &msg_receiver); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to unregister module", status); + return -41; + } + return 0; } diff --git a/pjsip/src/test-pjsip/tsx_uas_test.c b/pjsip/src/test-pjsip/tsx_uas_test.c index 3ff2e18e..42cb075f 100644 --- a/pjsip/src/test-pjsip/tsx_uas_test.c +++ b/pjsip/src/test-pjsip/tsx_uas_test.c @@ -35,6 +35,8 @@ ** TEST1_BRANCH_ID ** Test that non-INVITE transaction returns 2xx response to the correct ** transport and correctly terminates the transaction. + ** This also checks that transaction is destroyed immediately after + ** it sends final response when reliable transport is used. ** ** TEST2_BRANCH_ID ** As above, for non-2xx final response. @@ -53,6 +55,7 @@ ** ** TEST6_BRANCH_ID ** As above, in COMPLETED state, with first sending provisional response. + ** (Only applicable for non-reliable transports). ** ** TEST7_BRANCH_ID ** INVITE transaction MUST retransmit non-2xx final response. @@ -65,6 +68,7 @@ ** ACK is received. (Note: PJSIP also retransmit 2xx final response ** until it's terminated by user). ** Transaction also MUST terminate in T4 seconds. + ** (Only applicable for non-reliable transports). ** ** TEST11_BRANCH_ID ** Test scenario where transport fails before response is sent (i.e. @@ -128,6 +132,12 @@ static char *TEST13_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test13"; #define TEST6_TITLE "test6: retransmit last response in COMPLETED state" +static char TARGET_URI[128]; +static char FROM_URI[128]; +static struct tsx_test_param *test_param; +static unsigned tp_flag; + + #define TEST_TIMEOUT_ERROR -30 #define MAX_ALLOWED_DIFF 150 @@ -521,7 +531,9 @@ static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) /* Check that status code is status_code. */ if (tsx->status_code != TEST6_STATUS_CODE) { - PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + PJ_LOG(3,(THIS_FILE, " error: incorrect status code %d " + "(expecting %d)", tsx->status_code, + TEST6_STATUS_CODE)); test_complete = -140; } @@ -577,6 +589,26 @@ static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) test_complete = -151; } + /* Check the number of retransmissions */ + if (tp_flag & PJSIP_TRANSPORT_RELIABLE) { + + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, " error: should not retransmit")); + test_complete = -1510; + } + + } else { + + if (tsx->retransmit_count != 10) { + PJ_LOG(3,(THIS_FILE, + " error: incorrect retransmit count %d " + "(expecting 10)", + tsx->retransmit_count)); + test_complete = -1510; + } + + } + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { /* Check that status code is status_code. */ @@ -814,6 +846,8 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) /* On received request, create UAS. */ pjsip_transaction *tsx; + PJ_LOG(4,(THIS_FILE, " received request (probably retransmission)")); + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" error: unable to create transaction", status); @@ -830,13 +864,17 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) send_response(rdata, tsx, TEST5_PROVISIONAL_CODE); } else if (pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0) { + PJ_LOG(4,(THIS_FILE, " sending provisional response")); send_response(rdata, tsx, TEST6_PROVISIONAL_CODE); + PJ_LOG(4,(THIS_FILE, " sending final response")); send_response(rdata, tsx, TEST6_STATUS_CODE); } } else { /* Verify the response received. */ + PJ_LOG(4,(THIS_FILE, " received response number %d", recv_count)); + ++recv_count; if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0) { @@ -869,7 +907,8 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) case 2: case 3: if (code != TEST6_STATUS_CODE) { - PJ_LOG(3,(THIS_FILE, " error: invalid code!")); + PJ_LOG(3,(THIS_FILE, " error: invalid code %d " + "(expecting %d)", code, TEST6_STATUS_CODE)); test_complete = -136; } break; @@ -1183,6 +1222,8 @@ static int perform_test( char *target_uri, char *from_uri, pjsip_tx_data_add_ref(tdata); /* (Re)Send the request. */ + PJ_LOG(4,(THIS_FILE, " (re)sending request %d", sent_cnt)); + status = pjsip_endpt_send_request_stateless(endpt, tdata, 0, 0); if (status != PJ_SUCCESS) { app_perror(" Error: unable to send request", status); @@ -1255,25 +1296,25 @@ static int perform_test( char *target_uri, char *from_uri, */ static int tsx_basic_final_response_test(void) { + unsigned duration; int status; PJ_LOG(3,(THIS_FILE," test1: basic sending 2xx final response")); - status = perform_test("sip:129.0.0.1;transport=loop-dgram", - "sip:129.0.0.1;transport=loop-dgram", - TEST1_BRANCH_ID, - 33, /* Test duration must be greater than 32 secs */ - &pjsip_options_method, 1, 0, 0); + /* Test duration must be greater than 32 secs if unreliable transport + * is used. + */ + duration = (tp_flag & PJSIP_TRANSPORT_RELIABLE) ? 1 : 33; + + status = perform_test(TARGET_URI, FROM_URI, TEST1_BRANCH_ID, + duration, &pjsip_options_method, 1, 0, 0); if (status != 0) return status; PJ_LOG(3,(THIS_FILE," test2: basic sending non-2xx final response")); - status = perform_test("sip:129.0.0.1;transport=loop-dgram", - "sip:129.0.0.1;transport=loop-dgram", - TEST2_BRANCH_ID, - 33, /* Test duration must be greater than 32 secs */ - &pjsip_options_method, 1, 0, 0); + status = perform_test(TARGET_URI, FROM_URI, TEST2_BRANCH_ID, + duration, &pjsip_options_method, 1, 0, 0); if (status != 0) return status; @@ -1289,14 +1330,15 @@ static int tsx_basic_final_response_test(void) */ static int tsx_basic_provisional_response_test(void) { + unsigned duration; int status; PJ_LOG(3,(THIS_FILE," test3: basic sending 2xx final response")); - status = perform_test("sip:129.0.0.1;transport=loop-dgram", - "sip:129.0.0.1;transport=loop-dgram", - TEST3_BRANCH_ID, - 35, + duration = (tp_flag & PJSIP_TRANSPORT_RELIABLE) ? 1 : 33; + duration += 2; + + status = perform_test(TARGET_URI, FROM_URI, TEST3_BRANCH_ID, duration, &pjsip_options_method, 1, 0, 0); return status; @@ -1320,10 +1362,7 @@ static int tsx_retransmit_last_response_test(const char *title, PJ_LOG(3,(THIS_FILE," %s", title)); - status = perform_test("sip:129.0.0.1;transport=loop-dgram", - "sip:129.0.0.1;transport=loop-dgram", - branch_id, - 5, + status = perform_test(TARGET_URI, FROM_URI, branch_id, 5, &pjsip_options_method, request_cnt, 1000, 1); if (status && status != TEST_TIMEOUT_ERROR) @@ -1357,9 +1396,7 @@ static int tsx_final_response_retransmission_test(void) PJ_LOG(3,(THIS_FILE, " test7: INVITE non-2xx final response retransmission")); - status = perform_test("sip:129.0.0.1;transport=loop-dgram", - "sip:129.0.0.1;transport=loop-dgram", - TEST7_BRANCH_ID, + status = perform_test(TARGET_URI, FROM_URI, TEST7_BRANCH_ID, 33, /* Test duration must be greater than 32 secs */ &pjsip_invite_method, 1, 0, 0); if (status != 0) @@ -1368,9 +1405,7 @@ static int tsx_final_response_retransmission_test(void) PJ_LOG(3,(THIS_FILE, " test8: INVITE 2xx final response retransmission")); - status = perform_test("sip:129.0.0.1;transport=loop-dgram", - "sip:129.0.0.1;transport=loop-dgram", - TEST8_BRANCH_ID, + status = perform_test(TARGET_URI, FROM_URI, TEST8_BRANCH_ID, 33, /* Test duration must be greater than 32 secs */ &pjsip_invite_method, 1, 0, 0); if (status != 0) @@ -1394,9 +1429,7 @@ static int tsx_ack_test(void) PJ_LOG(3,(THIS_FILE, " test9: receiving ACK for non-2xx final response")); - status = perform_test("sip:129.0.0.1;transport=loop-dgram", - "sip:129.0.0.1;transport=loop-dgram", - TEST9_BRANCH_ID, + status = perform_test(TARGET_URI, FROM_URI, TEST9_BRANCH_ID, 20, /* allow 5 retransmissions */ &pjsip_invite_method, 1, 0, 0); if (status != 0) @@ -1443,11 +1476,8 @@ static int tsx_transport_failure_test(void) pjsip_loop_set_failure(loop, 0, NULL); pjsip_loop_set_delay(loop, tests[i].transport_delay); - status = perform_test("sip:129.0.0.1;transport=loop-dgram", - "sip:129.0.0.1;transport=loop-dgram", - tests[i].branch_id, - 0, - &pjsip_invite_method, 1, 0, 1); + status = perform_test(TARGET_URI, FROM_URI, tests[i].branch_id, + 0, &pjsip_invite_method, 1, 0, 1); if (status && status != TEST_TIMEOUT_ERROR) return status; if (!status) { @@ -1494,19 +1524,26 @@ static int tsx_transport_failure_test(void) ** ***************************************************************************** */ -int tsx_uas_test(void) +int tsx_uas_test(struct tsx_test_param *param) { pj_sockaddr_in addr; pj_status_t status; + test_param = param; + tp_flag = pjsip_transport_get_flag_from_type(param->type); + + pj_ansi_sprintf(TARGET_URI, "sip:bob@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + pj_ansi_sprintf(FROM_URI, "sip:alice@127.0.0.1:%d;transport=%s", + param->port, param->tp_type); + /* Check if loop transport is configured. */ status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_LOOP_DGRAM, &addr, sizeof(addr), &loop); if (status != PJ_SUCCESS) { PJ_LOG(3,(THIS_FILE, " Error: loop transport is not configured!")); - return -1; + return -10; } - /* Register modules. */ status = pjsip_endpt_register_module(endpt, &tsx_user); if (status != PJ_SUCCESS) { @@ -1550,42 +1587,67 @@ int tsx_uas_test(void) if (status != 0) return status; - /* TEST6_BRANCH_ID: retransmit last response in PROCEEDING state + /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state + * This only applies to non-reliable transports, + * since UAS transaction is destroyed as soon + * as final response is sent for reliable transports. */ - status = tsx_retransmit_last_response_test(TEST6_TITLE, - TEST6_BRANCH_ID, - TEST6_REQUEST_COUNT, - TEST6_STATUS_CODE); - if (status != 0) - return status; + if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) { + status = tsx_retransmit_last_response_test(TEST6_TITLE, + TEST6_BRANCH_ID, + TEST6_REQUEST_COUNT, + TEST6_STATUS_CODE); + if (status != 0) + return status; + } /* TEST7_BRANCH_ID: INVITE non-2xx final response retransmission test * TEST8_BRANCH_ID: INVITE 2xx final response retransmission test */ - status = tsx_final_response_retransmission_test(); if (status != 0) return status; /* TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must * cease when ACK is received + * Only applicable for non-reliable transports. */ - status = tsx_ack_test(); - if (status != 0) - return status; + if ((tp_flag & PJSIP_TRANSPORT_RELIABLE) == 0) { + status = tsx_ack_test(); + if (status != 0) + return status; + } + /* TEST10_BRANCH_ID: test transport failure in TRYING state. * TEST11_BRANCH_ID: test transport failure in PROCEEDING state. * TEST12_BRANCH_ID: test transport failure in CONNECTED state. * TEST13_BRANCH_ID: test transport failure in CONFIRMED state. */ - status = tsx_transport_failure_test(); - if (status != 0) - return status; + /* Only valid for loop-dgram */ + if (param->type == PJSIP_TRANSPORT_LOOP_DGRAM) { + status = tsx_transport_failure_test(); + if (status != 0) + return status; + } - pjsip_transport_dec_ref(loop); - return 0; + /* Register modules. */ + status = pjsip_endpt_unregister_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to unregister module", status); + return -8; + } + status = pjsip_endpt_unregister_module(endpt, &msg_sender); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to unregister module", status); + return -9; + } + + if (loop) + pjsip_transport_dec_ref(loop); + + return 0; } diff --git a/pjsip/src/test-pjsip/txdata_test.c b/pjsip/src/test-pjsip/txdata_test.c index d39fad42..a7f0fb0b 100644 --- a/pjsip/src/test-pjsip/txdata_test.c +++ b/pjsip/src/test-pjsip/txdata_test.c @@ -21,11 +21,19 @@ #include #include -#define HFIND(msg,h,H) ((pjsip_##h##_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_##H, NULL)) #define THIS_FILE "txdata_test.c" +#define HFIND(msg,h,H) ((pjsip_##h##_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_##H, NULL)) + +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 +# define LOOP 10000 +#else +# define LOOP 100000 +#endif + + /* * This tests various core message creation functions. */ @@ -323,6 +331,77 @@ static int core_txdata_test(void) return 0; } + + +/* + * This test demonstrate the bug as reported in: + * http://bugzilla.pjproject.net/show_bug.cgi?id=49 + */ +static int gcc_test() +{ + char msgbuf[512]; + pj_str_t target = pj_str("sip:alice@wonderland:5061;x-param=param%201" + "?X-Hdr-1=Header%201" + "&X-Empty-Hdr="); + pjsip_tx_data *tdata; + pjsip_parser_err_report err_list; + pjsip_msg *msg; + int len; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " header param in URI to create request")); + + /* Create request with header param in target URI. */ + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target, + &target, &target, &target, NULL, -1, + NULL, &tdata); + if (status != 0) { + app_perror(" error: Unable to create request", status); + return -200; + } + + /* Print and parse the request. + * We'll check that header params are not present in + */ + len = pjsip_msg_print(tdata->msg, msgbuf, sizeof(msgbuf)); + if (len < 1) { + PJ_LOG(3,(THIS_FILE, " error: printing message")); + pjsip_tx_data_dec_ref(tdata); + return -250; + } + msgbuf[len] = '\0'; + + PJ_LOG(5,(THIS_FILE, "%d bytes request created:--begin-msg--\n" + "%s\n" + "--end-msg--", len, msgbuf)); + + /* Now parse the message. */ + pj_list_init(&err_list); + msg = pjsip_parse_msg( tdata->pool, msgbuf, len, &err_list); + if (msg == NULL) { + pjsip_parser_err_report *e; + + PJ_LOG(3,(THIS_FILE, " error: parsing message message")); + + e = err_list.next; + while (e != &err_list) { + PJ_LOG(3,(THIS_FILE, " %s in line %d col %d hname=%.*s", + pj_exception_id_name(e->except_code), + e->line, e->col+1, + (int)e->hname.slen, + e->hname.ptr)); + e = e->next; + } + + pjsip_tx_data_dec_ref(tdata); + return -255; + } + + pjsip_tx_data_dec_ref(tdata); + return 0; +} + + /* This tests the request creating functions against the following * requirements: * - header params in URI creates header in the request. @@ -345,6 +424,8 @@ static int txdata_test_uri_params(void) pjsip_tx_data *tdata; pjsip_sip_uri *uri; pjsip_param *param; + pjsip_via_hdr *via; + pjsip_parser_err_report err_list; pjsip_msg *msg; int len; pj_status_t status; @@ -360,6 +441,11 @@ static int txdata_test_uri_params(void) return -200; } + /* Fill up the Via header to prevent syntax error on parsing */ + via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->transport = pj_str("TCP"); + via->sent_by.host = pj_str("127.0.0.1"); + /* Print and parse the request. * We'll check that header params are not present in */ @@ -376,11 +462,25 @@ static int txdata_test_uri_params(void) "--end-msg--", len, msgbuf)); /* Now parse the message. */ - msg = pjsip_parse_msg( tdata->pool, msgbuf, len, NULL); + pj_list_init(&err_list); + msg = pjsip_parse_msg( tdata->pool, msgbuf, len, &err_list); if (msg == NULL) { - app_perror(" error: parsing message message", status); + pjsip_parser_err_report *e; + + PJ_LOG(3,(THIS_FILE, " error: parsing message message")); + + e = err_list.next; + while (e != &err_list) { + PJ_LOG(3,(THIS_FILE, " %s in line %d col %d hname=%.*s", + pj_exception_id_name(e->except_code), + e->line, e->col+1, + (int)e->hname.slen, + e->hname.ptr)); + e = e->next; + } + pjsip_tx_data_dec_ref(tdata); - return -250; + return -256; } /* Check the existence of port, other_param, and header param. @@ -512,18 +612,233 @@ static int txdata_test_uri_params(void) return 0; } + +/* + * create request benchmark + */ +static int create_request_bench(pj_timestamp *p_elapsed) +{ + enum { COUNT = 100 }; + unsigned i, j; + pjsip_tx_data *tdata[COUNT]; + pj_timestamp t1, t2, elapsed; + pj_status_t status; + + pj_str_t str_target = pj_str("sip:someuser@someprovider.com"); + pj_str_t str_from = pj_str("\"Local User\" "); + pj_str_t str_to = pj_str("\"Remote User\" "); + pj_str_t str_contact = str_from; + + elapsed.u64 = 0; + + for (i=0; iu64 = elapsed.u64; + return PJ_SUCCESS; + +on_error: + for (i=0; i"); + pj_str_t str_to = pj_str("\"Remote User\" "); + pj_str_t str_contact = str_from; + + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &str_target, &str_from, &str_to, + &str_contact, NULL, -1, NULL, + &request); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return status; + } + + /* Create several Via headers */ + via = pjsip_via_hdr_create(request->pool); + via->sent_by.host = pj_str("192.168.0.7"); + via->sent_by.port = 5061; + via->transport = pj_str("udp"); + via->rport_param = 0; + via->branch_param = pj_str("012345678901234567890123456789"); + via->recvd_param = pj_str("192.168.0.7"); + pjsip_msg_insert_first_hdr(request->msg, pjsip_hdr_clone(request->pool, via)); + pjsip_msg_insert_first_hdr(request->msg, pjsip_hdr_clone(request->pool, via)); + pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via); + + + /* Create "dummy" rdata from the tdata */ + pj_memset(&rdata, 0, sizeof(pjsip_rx_data)); + rdata.tp_info.pool = request->pool; + rdata.msg_info.msg = request->msg; + rdata.msg_info.from = pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.to = pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL); + rdata.msg_info.cseq = pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL); + rdata.msg_info.cid = pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.via = via; + + /* + * Now benchmark create_response + */ + elapsed.u64 = 0; + + for (i=0; iu64 = elapsed.u64; + pjsip_tx_data_dec_ref(request); + return PJ_SUCCESS; + +on_error: + for (i=0; ipjsip_endpt_create_request()"); + + + /* + * Benchmark create_response() + */ + PJ_LOG(3,(THIS_FILE, " benchmarking response creation:")); + for (i=0; ipjsip_endpt_create_response()"); + + return 0; } + diff --git a/pjsip/src/test-pjsip/uri_test.c b/pjsip/src/test-pjsip/uri_test.c index a221f4ca..916a30f4 100644 --- a/pjsip/src/test-pjsip/uri_test.c +++ b/pjsip/src/test-pjsip/uri_test.c @@ -32,12 +32,19 @@ #define PARAM_CHAR ALPHANUM MARK "[]/:&+$" #define POOL_SIZE 8000 -#define LOOP_COUNT 10000 +#if defined(PJ_DEBUG) && PJ_DEBUG!=0 +# define LOOP_COUNT 10000 +#else +# define LOOP_COUNT 40000 +#endif #define AVERAGE_URL_LEN 80 #define THREAD_COUNT 4 -static pj_highprec_t parse_len, print_len, cmp_len; -static pj_timestamp parse_time, print_time, cmp_time; +static struct +{ + pj_highprec_t parse_len, print_len, cmp_len; + pj_timestamp parse_time, print_time, cmp_time; +} var; /* URI creator functions. */ @@ -686,7 +693,7 @@ static pj_status_t do_uri_test(pj_pool_t *pool, struct uri_test *entry) /* Parse URI text. */ pj_get_timestamp(&t1); - parse_len = parse_len + entry->len; + var.parse_len = var.parse_len + entry->len; parsed_uri = pjsip_parse_uri(pool, entry->str, entry->len, 0); if (!parsed_uri) { /* Parsing failed. If the entry says that this is expected, then @@ -702,7 +709,7 @@ static pj_status_t do_uri_test(pj_pool_t *pool, struct uri_test *entry) } pj_get_timestamp(&t2); pj_sub_timestamp(&t2, &t1); - pj_add_timestamp(&parse_time, &t2); + pj_add_timestamp(&var.parse_time, &t2); /* Create the reference URI. */ ref_uri = entry->creator(pool); @@ -720,10 +727,10 @@ static pj_status_t do_uri_test(pj_pool_t *pool, struct uri_test *entry) s1.ptr[len] = '\0'; s1.slen = len; - print_len = print_len + len; + var.print_len = var.print_len + len; pj_get_timestamp(&t2); pj_sub_timestamp(&t2, &t1); - pj_add_timestamp(&print_time, &t2); + pj_add_timestamp(&var.print_time, &t2); len = pjsip_uri_print( PJSIP_URI_IN_OTHER, ref_uri, s2.ptr, PJSIP_MAX_URL_SIZE); if (len < 1) { @@ -755,10 +762,10 @@ static pj_status_t do_uri_test(pj_pool_t *pool, struct uri_test *entry) } } - cmp_len = cmp_len + len; + var.cmp_len = var.cmp_len + len; pj_get_timestamp(&t2); pj_sub_timestamp(&t2, &t1); - pj_add_timestamp(&cmp_time, &t2); + pj_add_timestamp(&var.cmp_time, &t2); /* Compare text. */ if (entry->printed) { @@ -785,16 +792,12 @@ on_return: return status; } -int uri_test() + +static int simple_uri_test(void) { - unsigned i, loop; + unsigned i; pj_pool_t *pool; pj_status_t status; - pj_timestamp zero; - pj_time_val elapsed; - pj_highprec_t avg_parse, avg_print, avg_cmp, kbytes; - - zero.u32.hi = zero.u32.lo = 0; PJ_LOG(3,(THIS_FILE, " simple test")); pool = pjsip_endpt_create_pool(endpt, "", POOL_SIZE, POOL_SIZE); @@ -803,16 +806,31 @@ int uri_test() if (status != PJ_SUCCESS) { PJ_LOG(3,(THIS_FILE, " error %d when testing entry %d", status, i)); - goto on_return; + return status; } } pjsip_endpt_release_pool(endpt, pool); - PJ_LOG(3,(THIS_FILE, " benchmarking...")); - parse_len = print_len = cmp_len = 0; - parse_time.u32.hi = parse_time.u32.lo = 0; - print_time.u32.hi = print_time.u32.lo = 0; - cmp_time.u32.hi = cmp_time.u32.lo = 0; + return 0; +} + +static int uri_benchmark(unsigned *p_parse, unsigned *p_print, unsigned *p_cmp) +{ + unsigned i, loop; + pj_pool_t *pool; + pj_status_t status; + pj_timestamp zero; + pj_time_val elapsed; + pj_highprec_t avg_parse, avg_print, avg_cmp, kbytes; + + pj_memset(&var, 0, sizeof(var)); + + zero.u32.hi = zero.u32.lo = 0; + + var.parse_len = var.print_len = var.cmp_len = 0; + var.parse_time.u32.hi = var.parse_time.u32.lo = 0; + var.print_time.u32.hi = var.print_time.u32.lo = 0; + var.cmp_time.u32.hi = var.cmp_time.u32.lo = 0; for (loop=0; loop max) max = run[i].parse; + + PJ_LOG(3,("", " Maximum URI parse/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be parsed with " + "pjsip_parse_uri() per second " + "(tested with %d URI set, with average length of " + "%d chars)", + PJ_ARRAY_SIZE(uri_test_array), avg_len); + + report_ival("uri-parse-per-sec", max, "URI/sec", desc); + + /* URI parsing bandwidth */ + report_ival("uri-parse-bandwidth-mb", avg_len*max/1000000, "MB/sec", + "URI parsing bandwidth in megabytes (number of megabytes " + "worth of URI that can be parsed per second)"); + + + /* Print maximum print/sec */ + for (i=0, max=0; i max) max = run[i].print; + + PJ_LOG(3,("", " Maximum URI print/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be printed with " + "pjsip_uri_print() per second " + "(tested with %d URI set, with average length of " + "%d chars)", + PJ_ARRAY_SIZE(uri_test_array), avg_len); + + report_ival("uri-print-per-sec", max, "URI/sec", desc); + + /* Print maximum detect/sec */ + for (i=0, max=0; i max) max = run[i].cmp; + + PJ_LOG(3,("", " Maximum URI comparison/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP/TEL URIs that can be compared with " + "pjsip_uri_cmp() per second " + "(tested with %d URI set, with average length of " + "%d chars)", + PJ_ARRAY_SIZE(uri_test_array), avg_len); + + report_ival("uri-cmp-per-sec", max, "URI/sec", desc); + + return PJ_SUCCESS; +} + -- cgit v1.2.3