diff options
Diffstat (limited to 'pjsip/src/test')
-rw-r--r-- | pjsip/src/test/dlg_core_test.c | 23 | ||||
-rw-r--r-- | pjsip/src/test/dns_test.c | 618 | ||||
-rw-r--r-- | pjsip/src/test/inv_offer_answer_test.c | 677 | ||||
-rw-r--r-- | pjsip/src/test/main.c | 90 | ||||
-rw-r--r-- | pjsip/src/test/main_rtems.c | 12 | ||||
-rw-r--r-- | pjsip/src/test/main_win32.c | 1 | ||||
-rw-r--r-- | pjsip/src/test/msg_err_test.c | 101 | ||||
-rw-r--r-- | pjsip/src/test/msg_logger.c | 104 | ||||
-rw-r--r-- | pjsip/src/test/msg_test.c | 2058 | ||||
-rw-r--r-- | pjsip/src/test/regc_test.c | 1162 | ||||
-rw-r--r-- | pjsip/src/test/test.c | 392 | ||||
-rw-r--r-- | pjsip/src/test/test.h | 125 | ||||
-rw-r--r-- | pjsip/src/test/transport_loop_test.c | 127 | ||||
-rw-r--r-- | pjsip/src/test/transport_tcp_test.c | 155 | ||||
-rw-r--r-- | pjsip/src/test/transport_test.c | 760 | ||||
-rw-r--r-- | pjsip/src/test/transport_udp_test.c | 128 | ||||
-rw-r--r-- | pjsip/src/test/tsx_basic_test.c | 157 | ||||
-rw-r--r-- | pjsip/src/test/tsx_bench.c | 280 | ||||
-rw-r--r-- | pjsip/src/test/tsx_uac_test.c | 1450 | ||||
-rw-r--r-- | pjsip/src/test/tsx_uas_test.c | 1656 | ||||
-rw-r--r-- | pjsip/src/test/txdata_test.c | 847 | ||||
-rw-r--r-- | pjsip/src/test/uri_test.c | 1091 |
22 files changed, 12014 insertions, 0 deletions
diff --git a/pjsip/src/test/dlg_core_test.c b/pjsip/src/test/dlg_core_test.c new file mode 100644 index 00000000..ae278cbd --- /dev/null +++ b/pjsip/src/test/dlg_core_test.c @@ -0,0 +1,23 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> + diff --git a/pjsip/src/test/dns_test.c b/pjsip/src/test/dns_test.c new file mode 100644 index 00000000..a9ffec01 --- /dev/null +++ b/pjsip/src/test/dns_test.c @@ -0,0 +1,618 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> +#include <pjlib-util.h> + +/* For logging purpose. */ +#define THIS_FILE "dns_test.c" + +struct result +{ + pj_status_t status; + pjsip_server_addresses servers; +}; + + +static void cb(pj_status_t status, + void *token, + const struct pjsip_server_addresses *addr) +{ + struct result *result = (struct result*) token; + + result->status = status; + if (status == PJ_SUCCESS) + pj_memcpy(&result->servers, addr, sizeof(*addr)); +} + + +static void add_dns_entries(pj_dns_resolver *resv) +{ + /* Inject DNS SRV entry */ + pj_dns_parsed_packet pkt; + pj_dns_parsed_query q; + pj_dns_parsed_rr ans[4]; + pj_dns_parsed_rr ar[5]; + pj_str_t tmp; + unsigned i; + + /* + * This is answer to SRV query to "example.com" domain, and + * the answer contains full reference to the A records of + * the server. The full DNS records is : + + _sip._udp.example.com 3600 IN SRV 0 0 5060 sip01.example.com. + _sip._udp.example.com 3600 IN SRV 0 20 5060 sip02.example.com. + _sip._udp.example.com 3600 IN SRV 0 10 5060 sip03.example.com. + _sip._udp.example.com 3600 IN SRV 1 0 5060 sip04.example.com. + + sip01.example.com. 3600 IN A 1.1.1.1 + sip02.example.com. 3600 IN A 2.2.2.2 + sip03.example.com. 3600 IN A 3.3.3.3 + sip04.example.com. 3600 IN A 4.4.4.4 + + ; Additionally, add A record for "example.com" + example.com. 3600 IN A 5.5.5.5 + + */ + pj_bzero(&pkt, sizeof(pkt)); + pj_bzero(ans, sizeof(ans)); + pj_bzero(ar, sizeof(ar)); + + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.hdr.anscount = PJ_ARRAY_SIZE(ans); + pkt.hdr.arcount = 0; + pkt.ans = ans; + pkt.arr = ar; + + ans[0].name = pj_str("_sip._udp.example.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 0; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 5060; + ans[0].rdata.srv.target = pj_str("sip01.example.com"); + + ans[1].name = pj_str("_sip._udp.example.com"); + ans[1].type = PJ_DNS_TYPE_SRV; + ans[1].dnsclass = PJ_DNS_CLASS_IN; + ans[1].ttl = 3600; + ans[1].rdata.srv.prio = 0; + ans[1].rdata.srv.weight = 20; + ans[1].rdata.srv.port = 5060; + ans[1].rdata.srv.target = pj_str("sip02.example.com"); + + ans[2].name = pj_str("_sip._udp.example.com"); + ans[2].type = PJ_DNS_TYPE_SRV; + ans[2].dnsclass = PJ_DNS_CLASS_IN; + ans[2].ttl = 3600; + ans[2].rdata.srv.prio = 0; + ans[2].rdata.srv.weight = 10; + ans[2].rdata.srv.port = 5060; + ans[2].rdata.srv.target = pj_str("sip03.example.com"); + + ans[3].name = pj_str("_sip._udp.example.com"); + ans[3].type = PJ_DNS_TYPE_SRV; + ans[3].dnsclass = PJ_DNS_CLASS_IN; + ans[3].ttl = 3600; + ans[3].rdata.srv.prio = 1; + ans[3].rdata.srv.weight = 0; + ans[3].rdata.srv.port = 5060; + ans[3].rdata.srv.target = pj_str("sip04.example.com"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + ar[0].name = pj_str("sip01.example.com"); + ar[0].type = PJ_DNS_TYPE_A; + ar[0].dnsclass = PJ_DNS_CLASS_IN; + ar[0].ttl = 3600; + ar[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "1.1.1.1")); + + ar[1].name = pj_str("sip02.example.com"); + ar[1].type = PJ_DNS_TYPE_A; + ar[1].dnsclass = PJ_DNS_CLASS_IN; + ar[1].ttl = 3600; + ar[1].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "2.2.2.2")); + + ar[2].name = pj_str("sip03.example.com"); + ar[2].type = PJ_DNS_TYPE_A; + ar[2].dnsclass = PJ_DNS_CLASS_IN; + ar[2].ttl = 3600; + ar[2].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "3.3.3.3")); + + ar[3].name = pj_str("sip04.example.com"); + ar[3].type = PJ_DNS_TYPE_A; + ar[3].dnsclass = PJ_DNS_CLASS_IN; + ar[3].ttl = 3600; + ar[3].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "4.4.4.4")); + + ar[4].name = pj_str("example.com"); + ar[4].type = PJ_DNS_TYPE_A; + ar[4].dnsclass = PJ_DNS_CLASS_IN; + ar[4].ttl = 3600; + ar[4].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "5.5.5.5")); + + /* + * Create individual A records for all hosts in "example.com" domain. + */ + for (i=0; i<PJ_ARRAY_SIZE(ar); ++i) { + pj_bzero(&pkt, sizeof(pkt)); + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.hdr.qdcount = 1; + pkt.q = &q; + q.name = ar[i].name; + q.type = ar[i].type; + q.dnsclass = PJ_DNS_CLASS_IN; + pkt.hdr.anscount = 1; + pkt.ans = &ar[i]; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + } + + /* + * Simulate DNS error response by creating these answers. + * Sample of invalid SRV records: _sip._udp.sip01.example.com. + */ + for (i=0; i<PJ_ARRAY_SIZE(ans); ++i) { + pj_dns_parsed_query q; + char buf[128]; + char *services[] = { "_sip._udp.", "_sip._tcp.", "_sips._tcp."}; + unsigned j; + + for (j=0; j<PJ_ARRAY_SIZE(services); ++j) { + q.dnsclass = PJ_DNS_CLASS_IN; + q.type = PJ_DNS_TYPE_SRV; + + q.name.ptr = buf; + pj_bzero(buf, sizeof(buf)); + pj_strcpy2(&q.name, services[j]); + pj_strcat(&q.name, &ans[i].rdata.srv.target); + + pj_bzero(&pkt, sizeof(pkt)); + pkt.hdr.qdcount = 1; + pkt.hdr.flags = PJ_DNS_SET_QR(1) | + PJ_DNS_SET_RCODE(PJ_DNS_RCODE_NXDOMAIN); + pkt.q = &q; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + } + } + + + /* + * ANOTHER DOMAIN. + * + * This time we let SRV and A get answered in different DNS + * query. + */ + + /* The "domain.com" DNS records (note the different the port): + + _sip._tcp.domain.com 3600 IN SRV 1 0 50060 sip06.domain.com. + _sip._tcp.domain.com 3600 IN SRV 2 0 50060 sip07.domain.com. + + sip06.domain.com. 3600 IN A 6.6.6.6 + sip07.domain.com. 3600 IN A 7.7.7.7 + */ + + pj_bzero(&pkt, sizeof(pkt)); + pj_bzero(&ans, sizeof(ans)); + pkt.hdr.flags = PJ_DNS_SET_QR(1); + pkt.hdr.anscount = 2; + pkt.ans = ans; + + /* Add the SRV records, with reverse priority (to test that sorting + * works. + */ + ans[0].name = pj_str("_sip._tcp.domain.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 2; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 50060; + ans[0].rdata.srv.target = pj_str("SIP07.DOMAIN.COM"); + + ans[1].name = pj_str("_sip._tcp.domain.com"); + ans[1].type = PJ_DNS_TYPE_SRV; + ans[1].dnsclass = PJ_DNS_CLASS_IN; + ans[1].ttl = 3600; + ans[1].rdata.srv.prio = 1; + ans[1].rdata.srv.weight = 0; + ans[1].rdata.srv.port = 50060; + ans[1].rdata.srv.target = pj_str("SIP06.DOMAIN.COM"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + /* From herein there is only one answer */ + pkt.hdr.anscount = 1; + + /* Add a single SRV for UDP */ + ans[0].name = pj_str("_sip._udp.domain.com"); + ans[0].type = PJ_DNS_TYPE_SRV; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.srv.prio = 0; + ans[0].rdata.srv.weight = 0; + ans[0].rdata.srv.port = 50060; + ans[0].rdata.srv.target = pj_str("SIP06.DOMAIN.COM"); + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + + /* Add the A record for sip06.domain.com */ + ans[0].name = pj_str("sip06.domain.com"); + ans[0].type = PJ_DNS_TYPE_A; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "6.6.6.6")); + + pkt.hdr.qdcount = 1; + pkt.q = &q; + q.name = ans[0].name; + q.type = ans[0].type; + q.dnsclass = ans[0].dnsclass; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + /* Add the A record for sip07.domain.com */ + ans[0].name = pj_str("sip07.domain.com"); + ans[0].type = PJ_DNS_TYPE_A; + ans[0].dnsclass = PJ_DNS_CLASS_IN; + ans[0].ttl = 3600; + ans[0].rdata.a.ip_addr = pj_inet_addr(pj_cstr(&tmp, "7.7.7.7")); + + pkt.hdr.qdcount = 1; + pkt.q = &q; + q.name = ans[0].name; + q.type = ans[0].type; + q.dnsclass = ans[0].dnsclass; + + pj_dns_resolver_add_entry( resv, &pkt, PJ_FALSE); + + pkt.hdr.qdcount = 0; +} + + +/* + * Perform server resolution where the results are expected to + * come in strict order. + */ +static int test_resolve(const char *title, + pj_pool_t *pool, + pjsip_transport_type_e type, + char *name, + int port, + pjsip_server_addresses *ref) +{ + pjsip_host_info dest; + struct result result; + + PJ_LOG(3,(THIS_FILE, " test_resolve(): %s", title)); + + dest.type = type; + dest.flag = pjsip_transport_get_flag_from_type(type); + dest.addr.host = pj_str(name); + dest.addr.port = port; + + result.status = 0x12345678; + + pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb); + + while (result.status == 0x12345678) { + int i = 0; + pj_time_val timeout = { 1, 0 }; + pjsip_endpt_handle_events(endpt, &timeout); + if (i == 1) + pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE); + } + + if (result.status != PJ_SUCCESS) { + app_perror(" pjsip_endpt_resolve() error", result.status); + return result.status; + } + + if (ref) { + unsigned i; + + if (ref->count != result.servers.count) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 10: result count mismatch")); + return 10; + } + + for (i=0; i<ref->count; ++i) { + pj_sockaddr_in *ra = (pj_sockaddr_in *)&ref->entry[i].addr; + pj_sockaddr_in *rb = (pj_sockaddr_in *)&result.servers.entry[i].addr; + + if (ra->sin_addr.s_addr != rb->sin_addr.s_addr) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 20: IP address mismatch")); + return 20; + } + if (ra->sin_port != rb->sin_port) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 30: port mismatch")); + return 30; + } + if (ref->entry[i].addr_len != result.servers.entry[i].addr_len) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 40: addr_len mismatch")); + return 40; + } + if (ref->entry[i].type != result.servers.entry[i].type) { + PJ_LOG(3,(THIS_FILE, " test_resolve() error 50: transport type mismatch")); + return 50; + } + } + } + + return PJ_SUCCESS; +} + +/* + * Perform round-robin/load balance test. + */ +static int round_robin_test(pj_pool_t *pool) +{ + enum { COUNT = 400, PCT_ALLOWANCE = 5 }; + unsigned i; + struct server_hit + { + char *ip_addr; + unsigned percent; + unsigned hits; + } server_hit[] = + { + { "1.1.1.1", 3, 0 }, + { "2.2.2.2", 65, 0 }, + { "3.3.3.3", 32, 0 }, + { "4.4.4.4", 0, 0 } + }; + + PJ_LOG(3,(THIS_FILE, " Performing round-robin/load-balance test..")); + + /* Do multiple resolve request to "example.com". + * The resolver should select the server based on the weight proportion + * the the servers in the SRV entry. + */ + for (i=0; i<COUNT; ++i) { + pjsip_host_info dest; + struct result result; + unsigned j; + + dest.type = PJSIP_TRANSPORT_UDP; + dest.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_UDP); + dest.addr.host = pj_str("example.com"); + dest.addr.port = 0; + + result.status = 0x12345678; + + pjsip_endpt_resolve(endpt, pool, &dest, &result, &cb); + + while (result.status == 0x12345678) { + int i = 0; + pj_time_val timeout = { 1, 0 }; + pjsip_endpt_handle_events(endpt, &timeout); + if (i == 1) + pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), PJ_TRUE); + } + + /* Find which server was "hit" */ + for (j=0; j<PJ_ARRAY_SIZE(server_hit); ++j) { + pj_str_t tmp; + pj_in_addr a1; + pj_sockaddr_in *a2; + + tmp = pj_str(server_hit[j].ip_addr); + a1 = pj_inet_addr(&tmp); + a2 = (pj_sockaddr_in*) &result.servers.entry[0].addr; + + if (a1.s_addr == a2->sin_addr.s_addr) { + server_hit[j].hits++; + break; + } + } + + if (j == PJ_ARRAY_SIZE(server_hit)) { + PJ_LOG(1,(THIS_FILE, "..round_robin_test() error 10: returned address mismatch")); + return 10; + } + } + + /* Print the actual hit rate */ + for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) { + PJ_LOG(3,(THIS_FILE, " ..Server %s: weight=%d%%, hit %d%% times", + server_hit[i].ip_addr, server_hit[i].percent, + (server_hit[i].hits * 100) / COUNT)); + } + + /* Compare the actual hit with the weight proportion */ + for (i=0; i<PJ_ARRAY_SIZE(server_hit); ++i) { + int actual_pct = (server_hit[i].hits * 100) / COUNT; + + if (actual_pct + PCT_ALLOWANCE < (int)server_hit[i].percent || + actual_pct - PCT_ALLOWANCE > (int)server_hit[i].percent) + { + PJ_LOG(1,(THIS_FILE, + "..round_robin_test() error 20: " + "hit rate difference for server %s (%d%%) is more than " + "tolerable allowance (%d%%)", + server_hit[i].ip_addr, + actual_pct - server_hit[i].percent, + PCT_ALLOWANCE)); + return 20; + } + } + + PJ_LOG(3,(THIS_FILE, + " Load balance test success, hit-rate is " + "within %d%% allowance", PCT_ALLOWANCE)); + return PJ_SUCCESS; +} + + +#define C(expr) status = expr; \ + if (status != PJ_SUCCESS) app_perror(THIS_FILE, "Error", status); + +static void add_ref(pjsip_server_addresses *r, + pjsip_transport_type_e type, + char *addr, + int port) +{ + pj_sockaddr_in *a; + pj_str_t tmp; + + r->entry[r->count].type = type; + r->entry[r->count].priority = 0; + r->entry[r->count].weight = 0; + r->entry[r->count].addr_len = sizeof(pj_sockaddr_in); + + a = (pj_sockaddr_in *)&r->entry[r->count].addr; + a->sin_family = pj_AF_INET(); + tmp = pj_str(addr); + a->sin_addr = pj_inet_addr(&tmp); + a->sin_port = pj_htons((pj_uint16_t)port); + + r->count++; +} + +static void create_ref(pjsip_server_addresses *r, + pjsip_transport_type_e type, + char *addr, + int port) +{ + r->count = 0; + add_ref(r, type, addr, port); +} + + +/* + * Main test entry. + */ +int resolve_test(void) +{ + pj_pool_t *pool; + pj_dns_resolver *resv; + pj_str_t nameserver; + pj_uint16_t port = 5353; + pj_status_t status; + + pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000); + + status = pjsip_endpt_create_resolver(endpt, &resv); + + nameserver = pj_str("192.168.0.106"); + pj_dns_resolver_set_ns(resv, 1, &nameserver, &port); + pjsip_endpt_set_resolver(endpt, resv); + + add_dns_entries(resv); + + /* These all should be resolved as IP addresses (DNS A query) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060); + status = test_resolve("IP address without transport and port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -100; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "1.1.1.1", 5060); + status = test_resolve("IP address with explicit port", pool, PJSIP_TRANSPORT_UNSPECIFIED, "1.1.1.1", 5060, &ref); + if (status != PJ_SUCCESS) + return -110; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TCP, "1.1.1.1", 5060); + status = test_resolve("IP address without port (TCP)", pool, PJSIP_TRANSPORT_TCP,"1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -120; + } + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TLS, "1.1.1.1", 5061); + status = test_resolve("IP address without port (TLS)", pool, PJSIP_TRANSPORT_TLS, "1.1.1.1", 0, &ref); + if (status != PJ_SUCCESS) + return -130; + } + + /* This should be resolved as DNS A record (because port is present) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "5.5.5.5", 5060); + status = test_resolve("domain name with port should resolve to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "example.com", 5060, &ref); + if (status != PJ_SUCCESS) + return -140; + } + + /* This will fail to be resolved as SRV, resolver should fallback to + * resolving to A record. + */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "2.2.2.2", 5060); + status = test_resolve("failure with SRV fallback to A record", pool, PJSIP_TRANSPORT_UNSPECIFIED, "sip02.example.com", 0, &ref); + if (status != PJ_SUCCESS) + return -150; + } + + /* Same as above, but explicitly for TLS. */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TLS, "2.2.2.2", 5061); + status = test_resolve("failure with SRV fallback to A record (for TLS)", pool, PJSIP_TRANSPORT_TLS, "sip02.example.com", 0, &ref); + if (status != PJ_SUCCESS) + return -150; + } + + /* Standard DNS SRV followed by A recolution */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_UDP, "6.6.6.6", 50060); + status = test_resolve("standard SRV resolution", pool, PJSIP_TRANSPORT_UNSPECIFIED, "domain.com", 0, &ref); + if (status != PJ_SUCCESS) + return -155; + } + + /* Standard DNS SRV followed by A recolution (explicit transport) */ + { + pjsip_server_addresses ref; + create_ref(&ref, PJSIP_TRANSPORT_TCP, "6.6.6.6", 50060); + add_ref(&ref, PJSIP_TRANSPORT_TCP, "7.7.7.7", 50060); + status = test_resolve("standard SRV resolution with explicit transport (TCP)", pool, PJSIP_TRANSPORT_TCP, "domain.com", 0, &ref); + if (status != PJ_SUCCESS) + return -160; + } + + + /* Round robin/load balance test */ + if (round_robin_test(pool) != 0) + return -170; + + /* Timeout test */ + { + status = test_resolve("timeout test", pool, PJSIP_TRANSPORT_UNSPECIFIED, "an.invalid.address", 0, NULL); + if (status == PJ_SUCCESS) + return -150; + } + + return 0; +} + diff --git a/pjsip/src/test/inv_offer_answer_test.c b/pjsip/src/test/inv_offer_answer_test.c new file mode 100644 index 00000000..22809ddf --- /dev/null +++ b/pjsip/src/test/inv_offer_answer_test.c @@ -0,0 +1,677 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip_ua.h> +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "inv_offer_answer_test.c" +#define PORT 5068 +#define CONTACT "sip:127.0.0.1:5068" +#define TRACE_(x) PJ_LOG(3,x) + +static struct oa_sdp_t +{ + const char *offer; + const char *answer; + unsigned pt_result; +} oa_sdp[] = +{ + { + /* Offer: */ + "v=0\r\n" + "o=alice 1 1 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 1 1 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n", + + 0 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 2 2 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 8\r\n" + "a=rtpmap:0 PCMA/8000\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 2 2 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 8\r\n" + "a=rtpmap:0 PCMA/8000\r\n", + + 8 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 3 3 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 3\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 3 3 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 3\r\n", + + 3 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 4 4 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 4\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 4 4 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 4\r\n", + + 4 + } +}; + + + +typedef enum oa_t +{ + OFFERER_NONE, + OFFERER_UAC, + OFFERER_UAS +} oa_t; + +typedef struct inv_test_param_t +{ + char *title; + unsigned inv_option; + pj_bool_t need_established; + unsigned count; + oa_t oa[4]; +} inv_test_param_t; + +typedef struct inv_test_t +{ + inv_test_param_t param; + pjsip_inv_session *uac; + pjsip_inv_session *uas; + + pj_bool_t complete; + pj_bool_t uas_complete, + uac_complete; + + unsigned oa_index; + unsigned uac_update_cnt, + uas_update_cnt; +} inv_test_t; + + +/**************** GLOBALS ******************/ +static inv_test_t inv_test; +static unsigned job_cnt; + +typedef enum job_type +{ + SEND_OFFER, + ESTABLISH_CALL +} job_type; + +typedef struct job_t +{ + job_type type; + pjsip_role_e who; +} job_t; + +static job_t jobs[128]; + + +/**************** UTILS ******************/ +static pjmedia_sdp_session *create_sdp(pj_pool_t *pool, const char *body) +{ + pjmedia_sdp_session *sdp; + pj_str_t dup; + pj_status_t status; + + pj_strdup2_with_null(pool, &dup, body); + status = pjmedia_sdp_parse(pool, dup.ptr, dup.slen, &sdp); + pj_assert(status == PJ_SUCCESS); + + return sdp; +} + +/**************** INVITE SESSION CALLBACKS ******************/ +static void on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) +{ + pjmedia_sdp_session *sdp; + + sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].answer); + pjsip_inv_set_sdp_answer(inv, sdp); + + if (inv_test.oa_index == inv_test.param.count-1 && + inv_test.param.need_established) + { + jobs[job_cnt].type = ESTABLISH_CALL; + jobs[job_cnt].who = PJSIP_ROLE_UAS; + job_cnt++; + } +} + + +static void on_create_offer(pjsip_inv_session *inv, + pjmedia_sdp_session **p_offer) +{ + pj_assert(!"Should not happen"); +} + +static void on_media_update(pjsip_inv_session *inv_ses, + pj_status_t status) +{ + if (inv_ses == inv_test.uas) { + inv_test.uas_update_cnt++; + pj_assert(inv_test.uas_update_cnt - inv_test.uac_update_cnt <= 1); + TRACE_((THIS_FILE, " Callee media is established")); + } else if (inv_ses == inv_test.uac) { + inv_test.uac_update_cnt++; + pj_assert(inv_test.uac_update_cnt - inv_test.uas_update_cnt <= 1); + TRACE_((THIS_FILE, " Caller media is established")); + + } else { + pj_assert(!"Unknown session!"); + } + + if (inv_test.uac_update_cnt == inv_test.uas_update_cnt) { + inv_test.oa_index++; + + if (inv_test.oa_index < inv_test.param.count) { + switch (inv_test.param.oa[inv_test.oa_index]) { + case OFFERER_UAC: + jobs[job_cnt].type = SEND_OFFER; + jobs[job_cnt].who = PJSIP_ROLE_UAC; + job_cnt++; + break; + case OFFERER_UAS: + jobs[job_cnt].type = SEND_OFFER; + jobs[job_cnt].who = PJSIP_ROLE_UAS; + job_cnt++; + break; + default: + pj_assert(!"Invalid oa"); + } + } + + pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs)); + } +} + +static void on_state_changed(pjsip_inv_session *inv, pjsip_event *e) +{ + const char *who = NULL; + + if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + TRACE_((THIS_FILE, " %s call disconnected", + (inv==inv_test.uas ? "Callee" : "Caller"))); + return; + } + + if (inv->state != PJSIP_INV_STATE_CONFIRMED) + return; + + if (inv == inv_test.uas) { + inv_test.uas_complete = PJ_TRUE; + who = "Callee"; + } else if (inv == inv_test.uac) { + inv_test.uac_complete = PJ_TRUE; + who = "Caller"; + } else + pj_assert(!"No session"); + + TRACE_((THIS_FILE, " %s call is confirmed", who)); + + if (inv_test.uac_complete && inv_test.uas_complete) + inv_test.complete = PJ_TRUE; +} + + +/**************** MODULE TO RECEIVE INITIAL INVITE ******************/ + +static pj_bool_t on_rx_request(pjsip_rx_data *rdata) +{ + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG && + rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) + { + pjsip_dialog *dlg; + pjmedia_sdp_session *sdp = NULL; + pj_str_t uri; + pjsip_tx_data *tdata; + pj_status_t status; + + /* + * Create UAS + */ + uri = pj_str(CONTACT); + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, + &uri, &dlg); + pj_assert(status == PJ_SUCCESS); + + if (inv_test.param.oa[0] == OFFERER_UAC) + sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].answer); + else if (inv_test.param.oa[0] == OFFERER_UAS) + sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].offer); + else + pj_assert(!"Invalid offerer type"); + + status = pjsip_inv_create_uas(dlg, rdata, sdp, inv_test.param.inv_option, &inv_test.uas); + pj_assert(status == PJ_SUCCESS); + + TRACE_((THIS_FILE, " Sending 183 with SDP")); + + /* + * Answer with 183 + */ + status = pjsip_inv_initial_answer(inv_test.uas, rdata, 183, NULL, + NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv_test.uas, tdata); + pj_assert(status == PJ_SUCCESS); + + return PJ_TRUE; + } + + return PJ_FALSE; +} + +static pjsip_module mod_inv_oa_test = +{ + NULL, NULL, /* prev, next. */ + { "mod-inv-oa-test", 15 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + +/**************** THE TEST ******************/ +static void run_job(job_t *j) +{ + pjsip_inv_session *inv; + pjsip_tx_data *tdata; + pjmedia_sdp_session *sdp; + pj_status_t status; + + if (j->who == PJSIP_ROLE_UAC) + inv = inv_test.uac; + else + inv = inv_test.uas; + + switch (j->type) { + case SEND_OFFER: + sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].offer); + + TRACE_((THIS_FILE, " Sending UPDATE with offer")); + status = pjsip_inv_update(inv, NULL, sdp, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv, tdata); + pj_assert(status == PJ_SUCCESS); + break; + case ESTABLISH_CALL: + TRACE_((THIS_FILE, " Sending 200/OK")); + status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv, tdata); + pj_assert(status == PJ_SUCCESS); + break; + } +} + + +static int perform_test(inv_test_param_t *param) +{ + pj_str_t uri; + pjsip_dialog *dlg; + pjmedia_sdp_session *sdp; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " %s", param->title)); + + pj_bzero(&inv_test, sizeof(inv_test)); + pj_memcpy(&inv_test.param, param, sizeof(*param)); + job_cnt = 0; + + uri = pj_str(CONTACT); + + /* + * Create UAC + */ + status = pjsip_dlg_create_uac(pjsip_ua_instance(), + &uri, &uri, &uri, &uri, &dlg); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -10); + + if (inv_test.param.oa[0] == OFFERER_UAC) + sdp = create_sdp(dlg->pool, oa_sdp[0].offer); + else + sdp = NULL; + + status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20); + + TRACE_((THIS_FILE, " Sending INVITE %s offer", (sdp ? "with" : "without"))); + + /* + * Make call! + */ + status = pjsip_inv_invite(inv_test.uac, &tdata); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30); + + status = pjsip_inv_send_msg(inv_test.uac, tdata); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30); + + /* + * Wait until test completes + */ + while (!inv_test.complete) { + pj_time_val delay = {0, 20}; + + pjsip_endpt_handle_events(endpt, &delay); + + while (job_cnt) { + job_t j; + + j = jobs[0]; + pj_array_erase(jobs, sizeof(jobs[0]), job_cnt, 0); + --job_cnt; + + run_job(&j); + } + } + + flush_events(100); + + /* + * Hangup + */ + TRACE_((THIS_FILE, " Disconnecting call")); + status = pjsip_inv_end_session(inv_test.uas, PJSIP_SC_DECLINE, 0, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv_test.uas, tdata); + pj_assert(status == PJ_SUCCESS); + + flush_events(500); + + return 0; +} + + +static pj_bool_t log_on_rx_msg(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + char info[80]; + + if (msg->type == PJSIP_REQUEST_MSG) + pj_ansi_snprintf(info, sizeof(info), "%.*s", + (int)msg->line.req.method.name.slen, + msg->line.req.method.name.ptr); + else + pj_ansi_snprintf(info, sizeof(info), "%d/%.*s", + msg->line.status.code, + (int)rdata->msg_info.cseq->method.name.slen, + rdata->msg_info.cseq->method.name.ptr); + + TRACE_((THIS_FILE, " Received %s %s sdp", info, + (msg->body ? "with" : "without"))); + + return PJ_FALSE; +} + + +/* Message logger module. */ +static pjsip_module mod_msg_logger = +{ + NULL, NULL, /* prev and next */ + { "mod-msg-loggee", 14}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &log_on_rx_msg, /* on_rx_request() */ + &log_on_rx_msg, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +static inv_test_param_t test_params[] = +{ +/* Normal scenario: + + UAC UAS + INVITE (offer) --> + 200/INVITE (answer) <-- + ACK --> + */ +#if 0 + { + "Standard INVITE with offer", + 0, + PJ_TRUE, + 1, + { OFFERER_UAC } + }, + + { + "Standard INVITE with offer, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 1, + { OFFERER_UAC } + }, +#endif + +/* Delayed offer: + UAC UAS + INVITE (no SDP) --> + 200/INVITE (offer) <-- + ACK (answer) --> + */ +#if 1 + { + "INVITE with no offer", + 0, + PJ_TRUE, + 1, + { OFFERER_UAS } + }, + + { + "INVITE with no offer, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 1, + { OFFERER_UAS } + }, +#endif + +/* Subsequent UAC offer with UPDATE: + + UAC UAS + INVITE (offer) --> + 180/rel (answer) <-- + UPDATE (offer) --> inv_update() on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) <-- + 200/INVITE <-- + ACK --> +*/ +#if 1 + { + "INVITE and UPDATE by UAC", + 0, + PJ_TRUE, + 2, + { OFFERER_UAC, OFFERER_UAC } + }, + { + "INVITE and UPDATE by UAC, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 2, + { OFFERER_UAC, OFFERER_UAC } + }, +#endif + +/* Subsequent UAS offer with UPDATE: + + INVITE (offer --> + 180/rel (answer) <-- + UPDATE (offer) <-- inv_update() + on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) --> + UPDATE (offer) --> on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) <-- + 200/INVITE <-- + ACK --> + + */ + { + "INVITE and many UPDATE by UAC and UAS", + 0, + PJ_TRUE, + 4, + { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS } + }, + +}; + + +static pjsip_dialog* on_dlg_forked(pjsip_dialog *first_set, pjsip_rx_data *res) +{ + return NULL; +} + + +static void on_new_session(pjsip_inv_session *inv, pjsip_event *e) +{ +} + + +int inv_offer_answer_test(void) +{ + unsigned i; + int rc = 0; + + /* Init UA layer */ + if (pjsip_ua_instance()->id == -1) { + pjsip_ua_init_param ua_param; + pj_bzero(&ua_param, sizeof(ua_param)); + ua_param.on_dlg_forked = &on_dlg_forked; + pjsip_ua_init_module(endpt, &ua_param); + } + + /* Init inv-usage */ + if (pjsip_inv_usage_instance()->id == -1) { + pjsip_inv_callback inv_cb; + pj_bzero(&inv_cb, sizeof(inv_cb)); + inv_cb.on_media_update = &on_media_update; + inv_cb.on_rx_offer = &on_rx_offer; + inv_cb.on_create_offer = &on_create_offer; + inv_cb.on_state_changed = &on_state_changed; + inv_cb.on_new_session = &on_new_session; + pjsip_inv_usage_init(endpt, &inv_cb); + } + + /* 100rel module */ + pjsip_100rel_init_module(endpt); + + /* Our module */ + pjsip_endpt_register_module(endpt, &mod_inv_oa_test); + pjsip_endpt_register_module(endpt, &mod_msg_logger); + + /* Create SIP UDP transport */ + { + pj_sockaddr_in addr; + pjsip_transport *tp; + pj_status_t status; + + pj_sockaddr_in_init(&addr, NULL, PORT); + status = pjsip_udp_transport_start(endpt, &addr, NULL, 1, &tp); + pj_assert(status == PJ_SUCCESS); + } + + /* Do tests */ + for (i=0; i<PJ_ARRAY_SIZE(test_params); ++i) { + rc = perform_test(&test_params[i]); + if (rc != 0) + goto on_return; + } + + +on_return: + return rc; +} + diff --git a/pjsip/src/test/main.c b/pjsip/src/test/main.c new file mode 100644 index 00000000..044cd51a --- /dev/null +++ b/pjsip/src/test/main.c @@ -0,0 +1,90 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +extern const char *system_name; + +static void usage() +{ + puts("Usage: test-pjsip"); + puts("Options:"); + puts(" -i,--interractive Key input at the end."); + puts(" -h,--help Show this screen"); + puts(" -l,--log-level N Set log level (0-6)"); +} + +int main(int argc, char *argv[]) +{ + int interractive = 0; + int retval; + char **opt_arg; + + /* Parse arguments. */ + opt_arg = argv+1; + while (*opt_arg) { + if (strcmp(*opt_arg, "-i") == 0 || + strcmp(*opt_arg, "--interractive") == 0) + { + interractive = 1; + } else if (strcmp(*opt_arg, "-h") == 0 || + strcmp(*opt_arg, "--help") == 0) + { + usage(); + return 1; + } else if (strcmp(*opt_arg, "-l") == 0 || + strcmp(*opt_arg, "--log-level") == 0) + { + ++opt_arg; + if (!opt_arg) { + usage(); + 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; + } + + ++opt_arg; + } + + retval = test_main(); + + if (interractive) { + char s[10]; + printf("<Press ENTER to quit>\n"); fflush(stdout); + if (fgets(s, sizeof(s), stdin) == NULL) + return retval; + } + + return retval; +} diff --git a/pjsip/src/test/main_rtems.c b/pjsip/src/test/main_rtems.c new file mode 100644 index 00000000..a4d14e5a --- /dev/null +++ b/pjsip/src/test/main_rtems.c @@ -0,0 +1,12 @@ + +/* + * !! OIY OIY !! + * + * The purpose of this file is only to get pjsip-test linked. I haven't + * actually tried to run pjsip-test on RTEMS!! + * + */ + +#include "../../pjlib/src/pjlib-test/main_rtems.c" + + diff --git a/pjsip/src/test/main_win32.c b/pjsip/src/test/main_win32.c new file mode 100644 index 00000000..3043a395 --- /dev/null +++ b/pjsip/src/test/main_win32.c @@ -0,0 +1 @@ +#include "../../pjlib/src/pjlib-test/main_win32.c" diff --git a/pjsip/src/test/msg_err_test.c b/pjsip/src/test/msg_err_test.c new file mode 100644 index 00000000..46d28f15 --- /dev/null +++ b/pjsip/src/test/msg_err_test.c @@ -0,0 +1,101 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "msg_err_test.c" + + +static pj_bool_t verify_success(pjsip_msg *msg, + pjsip_parser_err_report *err_list) +{ + return PJ_TRUE; +} + +static struct test_entry +{ + char msg[1024]; + pj_bool_t (*verify)(pjsip_msg *msg, + pjsip_parser_err_report *err_list); + +} test_entries[] = +{ + /* Syntax error in status line */ + { + "SIP/2.0 200\r\n" + "H-Name: H-Value\r\n" + "\r\n", + &verify_success + }, + + /* Syntax error in header */ + { + "SIP/2.0 200 OK\r\n" + "Via: SIP/2.0\r\n" + "H-Name: H-Value\r\n" + "\r\n", + &verify_success + }, + + /* Multiple syntax errors in headers */ + { + "SIP/2.0 200 OK\r\n" + "Via: SIP/2.0\r\n" + "H-Name: H-Value\r\n" + "Via: SIP/2.0\r\n" + "\r\n", + &verify_success + } +}; + + +int msg_err_test(void) +{ + pj_pool_t *pool; + unsigned i; + + PJ_LOG(3,(THIS_FILE, "Testing parsing error")); + + pool = pjsip_endpt_create_pool(endpt, "msgerrtest", 4000, 4000); + + for (i=0; i<PJ_ARRAY_SIZE(test_entries); ++i) { + pjsip_parser_err_report err_list, *e; + pjsip_msg *msg; + + PJ_LOG(3,(THIS_FILE, " Parsing msg %d", i)); + pj_list_init(&err_list); + msg = pjsip_parse_msg(pool, test_entries[i].msg, + strlen(test_entries[i].msg), &err_list); + + e = err_list.next; + while (e != &err_list) { + PJ_LOG(3,(THIS_FILE, + " reported syntax error at line %d col %d for %.*s", + e->line, e->col, + (int)e->hname.slen, + e->hname.ptr)); + e = e->next; + } + } + + pj_pool_release(pool); + return 0; +} diff --git a/pjsip/src/test/msg_logger.c b/pjsip/src/test/msg_logger.c new file mode 100644 index 00000000..8a71adcb --- /dev/null +++ b/pjsip/src/test/msg_logger.c @@ -0,0 +1,104 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "msg_logger.c" + +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:%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)); + } + + return PJ_FALSE; +} + +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:%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; +} + + +/* Message logger module. */ +static pjsip_module mod_msg_logger = +{ + NULL, NULL, /* prev and next */ + { "mod-msg-logger", 14}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_msg, /* on_rx_request() */ + &on_rx_msg, /* on_rx_response() */ + &on_tx_msg, /* on_tx_request() */ + &on_tx_msg, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +int init_msg_logger(void) +{ + pj_status_t status; + + if (mod_msg_logger.id != -1) + return 0; + + status = pjsip_endpt_register_module(endpt, &mod_msg_logger); + if (status != PJ_SUCCESS) { + app_perror(" error registering module", status); + return -10; + } + + return 0; +} + +int msg_logger_set_enabled(pj_bool_t enabled) +{ + int val = msg_log_enabled; + msg_log_enabled = enabled; + return val; +} + diff --git a/pjsip/src/test/msg_test.c b/pjsip/src/test/msg_test.c new file mode 100644 index 00000000..840c40ff --- /dev/null +++ b/pjsip/src/test/msg_test.c @@ -0,0 +1,2058 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define POOL_SIZE 8000 +#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" + +static pjsip_msg *create_msg0(pj_pool_t *pool); +static pjsip_msg *create_msg1(pj_pool_t *pool); + +#define STATUS_PARTIAL 1 +#define STATUS_SYNTAX_ERROR 2 + +#define FLAG_DETECT_ONLY 1 +#define FLAG_PARSE_ONLY 4 +#define FLAG_PRINT_ONLY 8 + +struct test_msg +{ + char msg[1024]; + pjsip_msg *(*creator)(pj_pool_t *pool); + pj_size_t len; + int expected_status; +} test_array[] = +{ +{ + /* 'Normal' message with all headers. */ + "INVITE sip:user@foo SIP/2.0\n" + "from: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=123457890123456\r" + "To: Fellow User <sip:user@foo.bar.domain.com>\r\n" + "Call-ID: 12345678901234567890@bar\r\n" + "Content-Length: 0\r\n" + "CSeq: 123456 INVITE\n" + "Contact: <sip:joe@bar> ; q=0.5;expires=3600,sip:user@host;q=0.500\r" + " ,sip:user2@host2\n" + "Content-Type: text/html ; charset=ISO-8859-4\r" + "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n" + " <sip:server10.biloxi.com;lr>\r" + "Record-Route: <sip:server10.biloxi.com>,\r\n" /* multiple routes+folding*/ + " <sip:bigbox3.site3.atlanta.com;lr>\n" + "v: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n" + "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" /* folding. */ + " ;received=192.0.2.1\r\n" + "Via: SIP/2.0/UDP 10.2.1.1, SIP/2.0/TCP 192.168.1.1\n" + "Organization: \r" + "Max-Forwards: 70\n" + "X-Header: \r\n" /* empty header */ + "P-Associated-URI:\r\n" /* empty header without space */ + "\r\n", + &create_msg0, + 0, + PJ_SUCCESS +}, +{ + /* Typical response message. */ + "SIP/2.0 200 OK\r\n" + "Via: SIP/2.0/SCTP server10.biloxi.com;branch=z9hG4bKnashds8;rport;received=192.0.2.1\r\n" + "Via: SIP/2.0/UDP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n" + "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.3\r\n" + "Route: <sip:proxy.sipprovider.com>\r\n" + "Route: <sip:proxy.supersip.com:5060>\r\n" + "Max-Forwards: 70\r\n" + "To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n" + "From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n" + "Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n" + "CSeq: 314159 INVITE\r\n" + "Contact: <sips:bob@192.0.2.4>\r\n" + "Content-Type: application/sdp\r\n" + "Content-Length: 150\r\n" + "\r\n" + "v=0\r\n" + "o=alice 53655765 2353687637 IN IP4 pc33.atlanta.com\r\n" + "s=-\r\n" + "t=0 0\r\n" + "c=IN IP4 pc33.atlanta.com\r\n" + "m=audio 3456 RTP/AVP 0 1 3 99\r\n" + "a=rtpmap:0 PCMU/8000\r\n", + &create_msg1, + 0, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.1 A short tortuous INVITE + */ + "INVITE sip:vivekg@chair-dnrc.example.com;unknownparam SIP/2.0\n" + "TO :\n" + " sip:vivekg@chair-dnrc.example.com ; tag = 1918181833n\n" + "from : \"J Rosenberg \\\\\\\"\" <sip:jdrosen@example.com>\n" + " ;\n" + " tag = 98asjd8\n" + "MaX-fOrWaRdS: 0068\n" + "Call-ID: wsinv.ndaksdj@192.0.2.1\n" + "Content-Length : 150\n" + "cseq: 0009\n" + " INVITE\n" + "Via : SIP / 2.0\n" + " /UDP\n" + " 192.0.2.2;rport;branch=390skdjuw\n" + "s :\n" + "NewFangledHeader: newfangled value\n" + " continued newfangled value\n" + "UnknownHeaderWithUnusualValue: ;;,,;;,;\n" + "Content-Type: application/sdp\n" + "Route:\n" + " <sip:services.example.com;lr;unknownwith=value;unknown-no-value>\n" + "v: SIP / 2.0 / TCP spindle.example.com ;\n" + " branch = z9hG4bK9ikj8 ,\n" + " SIP / 2.0 / UDP 192.168.255.111 ; branch=\n" + " z9hG4bK30239\n" + "m:\"Quoted string \\\"\\\"\" <sip:jdrosen@example.com> ; newparam =\n" + " newvalue ;\n" + " secondparam ; q = 0.33\r\n" + "\r\n" + "v=0\r\n" + "o=mhandley 29739 7272939 IN IP4 192.0.2.3\r\n" + "s=-\r\n" + "c=IN IP4 192.0.2.4\r\n" + "t=0 0\r\n" + "m=audio 49217 RTP/AVP 0 12\r\n" + "m=video 3227 RTP/AVP 31\r\n" + "a=rtpmap:31 LPC\r\n", + NULL, + 0, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.2 Wide Range of Valid Characters + */ + "!interesting-Method0123456789_*+`.%indeed'~ sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*:&it+has=1,weird!*pas$wo~d_too.(doesn't-it)@example.com SIP/2.0\n" + "Via: SIP/2.0/UDP host1.example.com;rport;branch=z9hG4bK-.!%66*_+`'~\n" + "To: \"BEL:\\\x07 NUL:\\\x00 DEL:\\\x7F\" <sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*@example.com>\n" + "From: token1~` token2'+_ token3*%!.- <sip:mundane@example.com> ;fromParam''~+*_!.-%=\"\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xB0\xD1\x8E\xD1\x89\xD0\xB8\xD0\xB9\";tag=_token~1'+`*%!-.\n" + "Call-ID: intmeth.word%ZK-!.*_+'@word`~)(><:\\/\"][?}{\n" + "CSeq: 139122385 !interesting-Method0123456789_*+`.%indeed'~\n" + "Max-Forwards: 255\n" + "extensionHeader-!.%*+_`'~: \xEF\xBB\xBF\xE5\xA4\xA7\xE5\x81\x9C\xE9\x9B\xBB\n" + "Content-Length: 0\r\n\r\n", + NULL, + 641, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.3 Valid Use of the % Escaping Mechanism + */ + "INVITE sip:sips%3Auser%40example.com@example.net SIP/2.0\n" + "To: sip:%75se%72@example.com\n" + "From: <sip:I%20have%20spaces@example.net>;tag=1234\n" + "Max-Forwards: 87\n" + "i: esc01.239409asdfakjkn23onasd0-3234\n" + "CSeq: 234234 INVITE\n" + "Via: SIP/2.0/UDP host5.example.net;rport;branch=z9hG4bKkdjuw\n" + "C: application/sdp\n" + "Contact:\n" + " <sip:cal%6Cer@192.168.0.2:5060;%6C%72;n%61me=v%61lue%25%34%31>\n" + "Content-Length: 150\r\n" + "\r\n" + "v=0\r\n" + "o=mhandley 29739 7272939 IN IP4 192.0.2.1\r\n" + "s=-\r\n" + "c=IN IP4 192.0.2.1\r\n" + "t=0 0\r\n" + "m=audio 49217 RTP/AVP 0 12\r\n" + "m=video 3227 RTP/AVP 31\r\n" + "a=rtpmap:31 LPC\r\n", + NULL, + 0, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.4 Escaped Nulls in URIs + */ + "REGISTER sip:example.com SIP/2.0\r\n" + "To: sip:null-%00-null@example.com\r\n" + "From: sip:null-%00-null@example.com;tag=839923423\r\n" + "Max-Forwards: 70\r\n" + "Call-ID: escnull.39203ndfvkjdasfkq3w4otrq0adsfdfnavd\r\n" + "CSeq: 14398234 REGISTER\r\n" + "Via: SIP/2.0/UDP host5.example.com;rport;branch=z9hG4bKkdjuw\r\n" + "Contact: <sip:%00@host5.example.com>\r\n" + "Contact: <sip:%00%00@host5.example.com>\r\n" + "L:0\r\n" + "\r\n", + NULL, + 0, + PJ_SUCCESS +}, +{ + /* Torture message from RFC 4475 + * 3.1.1.5 Use of % When It Is Not an Escape + */ + "RE%47IST%45R sip:registrar.example.com SIP/2.0\r\n" + "To: \"%Z%45\" <sip:resource@example.com>\r\n" + "From: \"%Z%45\" <sip:resource@example.com>;tag=f232jadfj23\r\n" + "Call-ID: esc02.asdfnqwo34rq23i34jrjasdcnl23nrlknsdf\r\n" + "Via: SIP/2.0/TCP host.example.com;rport;branch=z9hG4bK209%fzsnel234\r\n" + "CSeq: 29344 RE%47IST%45R\r\n" + "Max-Forwards: 70\r\n" + "Contact: <sip:alias1@host1.example.com>\r\n" + "C%6Fntact: <sip:alias2@host2.example.com>\r\n" + "Contact: <sip:alias3@host3.example.com>\r\n" + "l: 0\r\n" + "\r\n", + NULL, + 0, + PJ_SUCCESS +} +}; + +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 ) +{ + pjsip_msg *parsed_msg, *ref_msg = NULL; + static pjsip_msg *print_msg; + pj_status_t status = PJ_SUCCESS; + int len; + pj_str_t str1, str2; + pjsip_hdr *hdr1, *hdr2; + pj_timestamp t1, t2; + pjsip_parser_err_report err_list; + pj_size_t msg_size; + char msgbuf1[PJSIP_MAX_PKT_LEN]; + char msgbuf2[PJSIP_MAX_PKT_LEN]; + enum { BUFLEN = 512 }; + + if (entry->len==0) + entry->len = pj_ansi_strlen(entry->msg); + + if (var.flag & FLAG_PARSE_ONLY) + goto parse_msg; + + if (var.flag & FLAG_PRINT_ONLY) { + if (print_msg == NULL) + print_msg = entry->creator(pool); + goto print_msg; + } + + /* Detect message. */ + 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) { + if (status!=PJSIP_EPARTIALMSG || + entry->expected_status!=STATUS_PARTIAL) + { + app_perror(" error: unable to detect message", status); + return -5; + } + } + if (msg_size != entry->len) { + PJ_LOG(3,(THIS_FILE, " error: size mismatch")); + return -6; + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.detect_time, &t2); + + if (var.flag & FLAG_DETECT_ONLY) + return PJ_SUCCESS; + + /* Parse message. */ +parse_msg: + 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); + if (parsed_msg == NULL) { + if (entry->expected_status != STATUS_SYNTAX_ERROR) { + status = -10; + if (err_list.next != &err_list) { + PJ_LOG(3,(THIS_FILE, " Syntax error in line %d col %d", + err_list.next->line, err_list.next->col)); + } + goto on_return; + } + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.parse_time, &t2); + + if ((var.flag & FLAG_PARSE_ONLY) || entry->creator==NULL) + return PJ_SUCCESS; + + /* Create reference message. */ + ref_msg = entry->creator(pool); + + /* Create buffer for comparison. */ + str1.ptr = (char*)pj_pool_alloc(pool, BUFLEN); + str2.ptr = (char*)pj_pool_alloc(pool, BUFLEN); + + /* Compare message type. */ + if (parsed_msg->type != ref_msg->type) { + status = -20; + goto on_return; + } + + /* Compare request or status line. */ + if (parsed_msg->type == PJSIP_REQUEST_MSG) { + pjsip_method *m1 = &parsed_msg->line.req.method; + pjsip_method *m2 = &ref_msg->line.req.method; + + if (pjsip_method_cmp(m1, m2) != 0) { + status = -30; + goto on_return; + } + status = pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, + parsed_msg->line.req.uri, + ref_msg->line.req.uri); + if (status != PJ_SUCCESS) { + app_perror(" error: request URI mismatch", status); + status = -31; + goto on_return; + } + } else { + if (parsed_msg->line.status.code != ref_msg->line.status.code) { + PJ_LOG(3,(THIS_FILE, " error: status code mismatch")); + status = -32; + goto on_return; + } + if (pj_strcmp(&parsed_msg->line.status.reason, + &ref_msg->line.status.reason) != 0) + { + PJ_LOG(3,(THIS_FILE, " error: status text mismatch")); + status = -33; + goto on_return; + } + } + + /* Compare headers. */ + hdr1 = parsed_msg->hdr.next; + hdr2 = ref_msg->hdr.next; + + while (hdr1 != &parsed_msg->hdr && hdr2 != &ref_msg->hdr) { + len = hdr1->vptr->print_on(hdr1, str1.ptr, BUFLEN); + if (len < 1) { + status = -40; + goto on_return; + } + str1.ptr[len] = '\0'; + str1.slen = len; + + len = hdr2->vptr->print_on(hdr2, str2.ptr, BUFLEN); + if (len < 1) { + status = -50; + goto on_return; + } + str2.ptr[len] = '\0'; + str2.slen = len; + + if (pj_strcmp(&str1, &str2) != 0) { + status = -60; + PJ_LOG(3,(THIS_FILE, " error: header string mismatch:\n" + " h1='%s'\n" + " h2='%s'\n", + str1.ptr, str2.ptr)); + goto on_return; + } + + hdr1 = hdr1->next; + hdr2 = hdr2->next; + } + + if (hdr1 != &parsed_msg->hdr || hdr2 != &ref_msg->hdr) { + status = -70; + goto on_return; + } + + /* Compare body? */ + if (parsed_msg->body==NULL && ref_msg->body==NULL) + goto print_msg; + + /* Compare msg body length. */ + if (parsed_msg->body->len != ref_msg->body->len) { + status = -80; + goto on_return; + } + + /* Compare msg body content type. */ + if (pj_strcmp(&parsed_msg->body->content_type.type, + &ref_msg->body->content_type.type) != 0) { + status = -90; + goto on_return; + } + if (pj_strcmp(&parsed_msg->body->content_type.subtype, + &ref_msg->body->content_type.subtype) != 0) { + status = -100; + goto on_return; + } + + /* Compare body content. */ + str1.slen = parsed_msg->body->print_body(parsed_msg->body, + msgbuf1, sizeof(msgbuf1)); + if (str1.slen < 1) { + status = -110; + goto on_return; + } + str1.ptr = msgbuf1; + + str2.slen = ref_msg->body->print_body(ref_msg->body, + msgbuf2, sizeof(msgbuf2)); + if (str2.slen < 1) { + status = -120; + goto on_return; + } + str2.ptr = msgbuf2; + + if (pj_strcmp(&str1, &str2) != 0) { + status = -140; + goto on_return; + } + + /* Print message. */ +print_msg: + var.print_len = var.print_len + entry->len; + pj_get_timestamp(&t1); + if (var.flag && FLAG_PRINT_ONLY) + ref_msg = print_msg; + len = pjsip_msg_print(ref_msg, msgbuf1, PJSIP_MAX_PKT_LEN); + if (len < 1) { + status = -150; + goto on_return; + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.print_time, &t2); + + + status = PJ_SUCCESS; + +on_return: + return status; +} + + +static pjsip_msg *create_msg0(pj_pool_t *pool) +{ + + pjsip_msg *msg; + pjsip_name_addr *name_addr; + pjsip_sip_uri *url; + pjsip_fromto_hdr *fromto; + pjsip_cid_hdr *cid; + pjsip_clen_hdr *clen; + pjsip_cseq_hdr *cseq; + pjsip_contact_hdr *contact; + pjsip_ctype_hdr *ctype; + pjsip_routing_hdr *routing; + pjsip_via_hdr *via; + pjsip_generic_string_hdr *generic; + pj_str_t str; + + msg = pjsip_msg_create(pool, PJSIP_REQUEST_MSG); + + /* "INVITE sip:user@foo SIP/2.0\n" */ + pjsip_method_set(&msg->line.req.method, PJSIP_INVITE_METHOD); + url = pjsip_sip_uri_create(pool, 0); + msg->line.req.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "user"); + pj_strdup2(pool, &url->host, "foo"); + + /* "From: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=123457890123456\r" */ + fromto = pjsip_from_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto); + pj_strdup2(pool, &fromto->tag, "123457890123456"); + name_addr = pjsip_name_addr_create(pool); + fromto->uri = (pjsip_uri*)name_addr; + pj_strdup2(pool, &name_addr->display, "Hi I'm Joe"); + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "joe.user"); + pj_strdup2(pool, &url->host, "bar.otherdomain.com"); + + /* "To: Fellow User <sip:user@foo.bar.domain.com>\r\n" */ + fromto = pjsip_to_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto); + name_addr = pjsip_name_addr_create(pool); + fromto->uri = (pjsip_uri*)name_addr; + pj_strdup2(pool, &name_addr->display, "Fellow User"); + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "user"); + pj_strdup2(pool, &url->host, "foo.bar.domain.com"); + + /* "Call-ID: 12345678901234567890@bar\r\n" */ + cid = pjsip_cid_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid); + pj_strdup2(pool, &cid->id, "12345678901234567890@bar"); + + /* "Content-Length: 0\r\n" */ + clen = pjsip_clen_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen); + clen->len = 0; + + /* "CSeq: 123456 INVITE\n" */ + cseq = pjsip_cseq_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq); + cseq->cseq = 123456; + pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD); + + /* "Contact: <sip:joe@bar>;q=0.5;expires=3600*/ + contact = pjsip_contact_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact); + contact->q1000 = 500; + contact->expires = 3600; + name_addr = pjsip_name_addr_create(pool); + contact->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "joe"); + pj_strdup2(pool, &url->host, "bar"); + + /*, sip:user@host;q=0.500\r" */ + contact = pjsip_contact_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact); + contact->q1000 = 500; + name_addr = pjsip_name_addr_create(pool); + contact->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "user"); + pj_strdup2(pool, &url->host, "host"); + + /* " ,sip:user2@host2\n" */ + contact = pjsip_contact_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact); + name_addr = pjsip_name_addr_create(pool); + contact->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->user, "user2"); + pj_strdup2(pool, &url->host, "host2"); + + /* "Content-Type: text/html; charset=ISO-8859-4\r" */ + ctype = pjsip_ctype_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype); + pj_strdup2(pool, &ctype->media.type, "text"); + pj_strdup2(pool, &ctype->media.subtype, "html"); + pj_strdup2(pool, &ctype->media.param, ";charset=ISO-8859-4"); + + /* "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n" */ + routing = pjsip_route_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing); + url = pjsip_sip_uri_create(pool, 0); + routing->name_addr.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com"); + url->lr_param = 1; + + /* " <sip:server10.biloxi.com;lr>\r" */ + routing = pjsip_route_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing); + url = pjsip_sip_uri_create(pool, 0); + routing->name_addr.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->host, "server10.biloxi.com"); + url->lr_param = 1; + + /* "Record-Route: <sip:server10.biloxi.com>,\r\n" */ + routing = pjsip_rr_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing); + url = pjsip_sip_uri_create(pool, 0); + routing->name_addr.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->host, "server10.biloxi.com"); + url->lr_param = 0; + + /* " <sip:bigbox3.site3.atlanta.com;lr>\n" */ + routing = pjsip_rr_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing); + url = pjsip_sip_uri_create(pool, 0); + routing->name_addr.uri = (pjsip_uri*)url; + pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com"); + url->lr_param = 1; + + /* "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n" */ + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + pj_strdup2(pool, &via->transport, "SCTP"); + pj_strdup2(pool, &via->sent_by.host, "bigbox3.site3.atlanta.com"); + pj_strdup2(pool, &via->branch_param, "z9hG4bK77ef4c230"); + + /* "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" + " ;received=192.0.2.1\r\n" */ + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + pj_strdup2(pool, &via->transport, "UDP"); + pj_strdup2(pool, &via->sent_by.host, "pc33.atlanta.com"); + pj_strdup2(pool, &via->branch_param, "z9hG4bKnashds8"); + pj_strdup2(pool, &via->recvd_param, "192.0.2.1"); + + + /* "Via: SIP/2.0/UDP 10.2.1.1, */ + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + pj_strdup2(pool, &via->transport, "UDP"); + pj_strdup2(pool, &via->sent_by.host, "10.2.1.1"); + + + /*SIP/2.0/TCP 192.168.1.1\n" */ + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + pj_strdup2(pool, &via->transport, "TCP"); + pj_strdup2(pool, &via->sent_by.host, "192.168.1.1"); + + /* "Organization: \r" */ + str.ptr = "Organization"; + str.slen = 12; + generic = pjsip_generic_string_hdr_create(pool, &str, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + generic->hvalue.ptr = NULL; + generic->hvalue.slen = 0; + + /* "Max-Forwards: 70\n" */ + str.ptr = "Max-Forwards"; + str.slen = 12; + generic = pjsip_generic_string_hdr_create(pool, &str, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + str.ptr = "70"; + str.slen = 2; + generic->hvalue = str; + + /* "X-Header: \r\n" */ + str.ptr = "X-Header"; + str.slen = 8; + generic = pjsip_generic_string_hdr_create(pool, &str, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + str.ptr = NULL; + str.slen = 0; + generic->hvalue = str; + + /* P-Associated-URI:\r\n */ + str.ptr = "P-Associated-URI"; + str.slen = 16; + generic = pjsip_generic_string_hdr_create(pool, &str, NULL); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic); + str.ptr = NULL; + str.slen = 0; + generic->hvalue = str; + + return msg; +} + +static pjsip_msg *create_msg1(pj_pool_t *pool) +{ + pjsip_via_hdr *via; + pjsip_route_hdr *route; + pjsip_name_addr *name_addr; + pjsip_sip_uri *url; + pjsip_max_fwd_hdr *max_fwd; + pjsip_to_hdr *to; + pjsip_from_hdr *from; + pjsip_contact_hdr *contact; + pjsip_ctype_hdr *ctype; + pjsip_cid_hdr *cid; + pjsip_clen_hdr *clen; + pjsip_cseq_hdr *cseq; + pjsip_msg *msg = pjsip_msg_create(pool, PJSIP_RESPONSE_MSG); + pjsip_msg_body *body; + + //"SIP/2.0 200 OK\r\n" + msg->line.status.code = 200; + msg->line.status.reason = pj_str("OK"); + + //"Via: SIP/2.0/SCTP server10.biloxi.com;branch=z9hG4bKnashds8;rport;received=192.0.2.1\r\n" + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + via->transport = pj_str("SCTP"); + via->sent_by.host = pj_str("server10.biloxi.com"); + via->branch_param = pj_str("z9hG4bKnashds8"); + via->rport_param = 0; + via->recvd_param = pj_str("192.0.2.1"); + + //"Via: SIP/2.0/UDP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n" + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + via->transport = pj_str("UDP"); + via->sent_by.host = pj_str("bigbox3.site3.atlanta.com"); + via->branch_param = pj_str("z9hG4bK77ef4c2312983.1"); + via->recvd_param = pj_str("192.0.2.2"); + + //"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.3\r\n" + via = pjsip_via_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)via); + via->transport = pj_str("UDP"); + via->sent_by.host = pj_str("pc33.atlanta.com"); + via->branch_param = pj_str("z9hG4bK776asdhds"); + via->recvd_param = pj_str("192.0.2.3"); + + //"Route: <sip:proxy.sipprovider.com>\r\n" + route = pjsip_route_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)route); + url = pjsip_sip_uri_create(pool, PJ_FALSE); + route->name_addr.uri = (pjsip_uri*)url; + url->host = pj_str("proxy.sipprovider.com"); + + //"Route: <sip:proxy.supersip.com:5060>\r\n" + route = pjsip_route_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)route); + url = pjsip_sip_uri_create(pool, PJ_FALSE); + route->name_addr.uri = (pjsip_uri*)url; + url->host = pj_str("proxy.supersip.com"); + url->port = 5060; + + //"Max-Forwards: 70\r\n" + max_fwd = pjsip_max_fwd_hdr_create(pool, 70); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)max_fwd); + + //"To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n" + to = pjsip_to_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)to); + name_addr = pjsip_name_addr_create(pool); + name_addr->display = pj_str("Bob"); + to->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, PJ_FALSE); + name_addr->uri = (pjsip_uri*)url; + url->user = pj_str("bob"); + url->host = pj_str("biloxi.com"); + to->tag = pj_str("a6c85cf"); + + //"From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n" + from = pjsip_from_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)from); + name_addr = pjsip_name_addr_create(pool); + name_addr->display = pj_str("Alice"); + from->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, PJ_FALSE); + name_addr->uri = (pjsip_uri*)url; + url->user = pj_str("alice"); + url->host = pj_str("atlanta.com"); + from->tag = pj_str("1928301774"); + + //"Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n" + cid = pjsip_cid_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid); + cid->id = pj_str("a84b4c76e66710@pc33.atlanta.com"); + + //"CSeq: 314159 INVITE\r\n" + cseq = pjsip_cseq_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq); + cseq->cseq = 314159; + pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD); + + //"Contact: <sips:bob@192.0.2.4>\r\n" + contact = pjsip_contact_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact); + name_addr = pjsip_name_addr_create(pool); + contact->uri = (pjsip_uri*)name_addr; + url = pjsip_sip_uri_create(pool, PJ_TRUE); + name_addr->uri = (pjsip_uri*)url; + url->user = pj_str("bob"); + url->host = pj_str("192.0.2.4"); + + //"Content-Type: application/sdp\r\n" + ctype = pjsip_ctype_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype); + ctype->media.type = pj_str("application"); + ctype->media.subtype = pj_str("sdp"); + + //"Content-Length: 150\r\n" + clen = pjsip_clen_hdr_create(pool); + pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen); + clen->len = 150; + + // Body + body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body); + msg->body = body; + body->content_type.type = pj_str("application"); + body->content_type.subtype = pj_str("sdp"); + body->data = (void*) + "v=0\r\n" + "o=alice 53655765 2353687637 IN IP4 pc33.atlanta.com\r\n" + "s=-\r\n" + "t=0 0\r\n" + "c=IN IP4 pc33.atlanta.com\r\n" + "m=audio 3456 RTP/AVP 0 1 3 99\r\n" + "a=rtpmap:0 PCMU/8000\r\n"; + body->len = pj_ansi_strlen((const char*) body->data); + body->print_body = &pjsip_print_text_body; + + return msg; +} + +/*****************************************************************************/ + +static pj_status_t simple_test(void) +{ + unsigned i; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " simple test..")); + for (i=0; i<PJ_ARRAY_SIZE(test_array); ++i) { + pj_pool_t *pool; + pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE); + status = test_entry( pool, &test_array[i] ); + pjsip_endpt_release_pool(endpt, pool); + + if (status != PJ_SUCCESS) + return status; + } + + return PJ_SUCCESS; +} + + +#if INCLUDE_BENCHMARKS +static int msg_benchmark(unsigned *p_detect, unsigned *p_parse, + unsigned *p_print) +{ + 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_bzero(&var, sizeof(var)); + zero.u64 = 0; + + for (loop=0; loop<LOOP; ++loop) { + for (i=0; i<(int)PJ_ARRAY_SIZE(test_array); ++i) { + pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE); + status = test_entry( pool, &test_array[i] ); + pjsip_endpt_release_pool(endpt, pool); + + if (status != PJ_SUCCESS) + return status; + } + } + + kbytes = var.detect_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.detect_time); + avg_detect = pj_elapsed_usec(&zero, &var.detect_time); + pj_highprec_mul(avg_detect, AVERAGE_MSG_LEN); + pj_highprec_div(avg_detect, var.detect_len); + avg_detect = 1000000 / avg_detect; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB detected in %d.%03ds (avg=%d msg detection/sec)", + (unsigned)(var.detect_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_detect)); + *p_detect = (unsigned)avg_detect; + + kbytes = var.parse_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.parse_time); + avg_parse = pj_elapsed_usec(&zero, &var.parse_time); + pj_highprec_mul(avg_parse, AVERAGE_MSG_LEN); + pj_highprec_div(avg_parse, var.parse_len); + avg_parse = 1000000 / avg_parse; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB parsed in %d.%03ds (avg=%d msg parsing/sec)", + (unsigned)(var.parse_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_parse)); + *p_parse = (unsigned)avg_parse; + + kbytes = var.print_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.print_time); + avg_print = pj_elapsed_usec(&zero, &var.print_time); + pj_highprec_mul(avg_print, AVERAGE_MSG_LEN); + pj_highprec_div(avg_print, var.print_len); + avg_print = 1000000 / avg_print; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB printed in %d.%03ds (avg=%d msg print/sec)", + (unsigned)(var.print_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_print)); + + *p_print = (unsigned)avg_print; + return status; +} +#endif /* INCLUDE_BENCHMARKS */ + +/*****************************************************************************/ +/* Test various header parsing and production */ +static int hdr_test_success(pjsip_hdr *h); +static int hdr_test_accept0(pjsip_hdr *h); +static int hdr_test_accept1(pjsip_hdr *h); +static int hdr_test_accept2(pjsip_hdr *h); +static int hdr_test_allow0(pjsip_hdr *h); +static int hdr_test_authorization(pjsip_hdr *h); +static int hdr_test_cid(pjsip_hdr *h); +static int hdr_test_contact0(pjsip_hdr *h); +static int hdr_test_contact1(pjsip_hdr *h); +static int hdr_test_contact_q0(pjsip_hdr *h); +static int hdr_test_contact_q1(pjsip_hdr *h); +static int hdr_test_contact_q2(pjsip_hdr *h); +static int hdr_test_contact_q3(pjsip_hdr *h); +static int hdr_test_contact_q4(pjsip_hdr *h); +static int hdr_test_content_length(pjsip_hdr *h); +static int hdr_test_content_type(pjsip_hdr *h); +static int hdr_test_from(pjsip_hdr *h); +static int hdr_test_proxy_authenticate(pjsip_hdr *h); +static int hdr_test_record_route(pjsip_hdr *h); +static int hdr_test_supported(pjsip_hdr *h); +static int hdr_test_to(pjsip_hdr *h); +static int hdr_test_via(pjsip_hdr *h); +static int hdr_test_via_ipv6_1(pjsip_hdr *h); +static int hdr_test_via_ipv6_2(pjsip_hdr *h); +static int hdr_test_via_ipv6_3(pjsip_hdr *h); +static int hdr_test_retry_after1(pjsip_hdr *h); +static int hdr_test_subject_utf(pjsip_hdr *h); + + +#define GENERIC_PARAM "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3" +#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab:cd;p3" +#define PARAM_CHAR "][/:&+$" +#define SIMPLE_ADDR_SPEC "sip:host" +#define ADDR_SPEC SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\"" +#define NAME_ADDR "<" ADDR_SPEC ">" + +#define HDR_FLAG_PARSE_FAIL 1 +#define HDR_FLAG_DONT_PRINT 2 + +struct hdr_test_t +{ + char *hname; + char *hshort_name; + char *hcontent; + int (*test)(pjsip_hdr*); + unsigned flags; +} hdr_test_data[] = +{ + { + /* Empty Accept */ + "Accept", NULL, + "", + &hdr_test_accept0 + }, + + { + /* Overflowing generic string header */ + "Accept", NULL, + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \ + "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a", + &hdr_test_success, + HDR_FLAG_PARSE_FAIL + }, + + { + /* Normal Accept */ + "Accept", NULL, + "application/*, text/plain", + &hdr_test_accept1 + }, + + { + /* Accept with params */ + "Accept", NULL, + "application/*;p1=v1, text/plain", + &hdr_test_accept2 + }, + + { + /* Empty Allow */ + "Allow", NULL, + "", + &hdr_test_allow0, + }, + + { + /* Authorization, testing which params should be quoted */ + "Authorization", NULL, + "Digest username=\"username\", realm=\"realm\", nonce=\"nonce\", " \ + "uri=\"sip:domain\", response=\"RESPONSE\", algorithm=MD5, " \ + "cnonce=\"CNONCE\", opaque=\"OPAQUE\", qop=auth, nc=00000001", + &hdr_test_authorization + }, + + { + /* Call ID */ + "Call-ID", "i", + "-.!%*_+`'~()<>:\\\"/[]?{}", + &hdr_test_cid, + }, + + { + /* Parameter belong to hparam */ + "Contact", "m", + SIMPLE_ADDR_SPEC ";p1=v1", + &hdr_test_contact0, + HDR_FLAG_DONT_PRINT + }, + + { + /* generic-param in Contact header */ + "Contact", "m", + NAME_ADDR ";" GENERIC_PARAM, + &hdr_test_contact1 + }, + + { + /* q=0 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=0", + &hdr_test_contact_q0, + HDR_FLAG_DONT_PRINT + }, + + { + /* q=0.5 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=0.5", + &hdr_test_contact_q1 + }, + + { + /* q=1 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=1", + &hdr_test_contact_q2 + }, + + { + /* q=1.0 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=1.0", + &hdr_test_contact_q3, + HDR_FLAG_DONT_PRINT + }, + + { + /* q=1.1 parameter in Contact header */ + "Contact", "m", + NAME_ADDR ";q=1.15", + &hdr_test_contact_q4 + }, + + { + /* Content-Length */ + "Content-Length", "l", + "10", + &hdr_test_content_length + }, + + { + /* Content-Type, with generic-param */ + "Content-Type", "c", + "application/sdp" ";" GENERIC_PARAM, + &hdr_test_content_type, + HDR_FLAG_DONT_PRINT + }, + + { + /* From, testing parameters and generic-param */ + "From", "f", + NAME_ADDR ";" GENERIC_PARAM, + &hdr_test_from + }, + + { + /* Proxy-Authenticate, testing which params should be quoted */ + "Proxy-Authenticate", NULL, + "Digest realm=\"realm\",domain=\"sip:domain\",nonce=\"nonce\"," \ + "opaque=\"opaque\",stale=true,algorithm=MD5,qop=\"auth\"", + &hdr_test_proxy_authenticate + }, + + { + /* Record-Route, param belong to header */ + "Record-Route", NULL, + NAME_ADDR ";" GENERIC_PARAM, + &hdr_test_record_route + }, + + { + /* Empty Supported */ + "Supported", "k", + "", + &hdr_test_supported, + }, + + { + /* To */ + "To", "t", + NAME_ADDR ";" GENERIC_PARAM, + &hdr_test_to + }, + + { + /* Via */ + "Via", "v", + "SIP/2.0/XYZ host" ";" GENERIC_PARAM, + &hdr_test_via + }, + + { + /* Via with IPv6 */ + "Via", "v", + "SIP/2.0/UDP [::1]", + &hdr_test_via_ipv6_1 + }, + + { + /* Via with IPv6 */ + "Via", "v", + "SIP/2.0/UDP [::1]:5061", + &hdr_test_via_ipv6_2 + }, + + { + /* Via with IPv6 */ + "Via", "v", + "SIP/2.0/UDP [::1];rport=5061;received=::2", + &hdr_test_via_ipv6_3 + }, + + { + /* Retry-After header with comment */ + "Retry-After", NULL, + "10(Already Pending Register)", + &hdr_test_retry_after1 + }, + + { + /* Non-ASCII UTF-8 characters in Subject */ + "Subject", NULL, + "\xC0\x81", + &hdr_test_subject_utf + } +}; + +static int hdr_test_success(pjsip_hdr *h) +{ + PJ_UNUSED_ARG(h); + return 0; +} + +/* "" */ +static int hdr_test_accept0(pjsip_hdr *h) +{ + pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h; + + if (h->type != PJSIP_H_ACCEPT) + return -1010; + + if (hdr->count != 0) + return -1020; + + return 0; +} + +/* "application/ *, text/plain\r\n" */ +static int hdr_test_accept1(pjsip_hdr *h) +{ + pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h; + + if (h->type != PJSIP_H_ACCEPT) + return -1110; + + if (hdr->count != 2) + return -1120; + + if (pj_strcmp2(&hdr->values[0], "application/*")) + return -1130; + + if (pj_strcmp2(&hdr->values[1], "text/plain")) + return -1140; + + return 0; +} + +/* "application/ *;p1=v1, text/plain\r\n" */ +static int hdr_test_accept2(pjsip_hdr *h) +{ + pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h; + + if (h->type != PJSIP_H_ACCEPT) + return -1210; + + if (hdr->count != 2) + return -1220; + + if (pj_strcmp2(&hdr->values[0], "application/*;p1=v1")) + return -1230; + + if (pj_strcmp2(&hdr->values[1], "text/plain")) + return -1240; + + return 0; +} + +/* "" */ +static int hdr_test_allow0(pjsip_hdr *h) +{ + pjsip_allow_hdr *hdr = (pjsip_allow_hdr*)h; + + if (h->type != PJSIP_H_ALLOW) + return -1310; + + if (hdr->count != 0) + return -1320; + + return 0; + +} + + +/* + "Digest username=\"username\", realm=\"realm\", nonce=\"nonce\", " \ + "uri=\"sip:domain\", response=\"RESPONSE\", algorithm=MD5, " \ + "cnonce=\"CNONCE\", opaque=\"OPAQUE\", qop=auth, nc=00000001", + */ +static int hdr_test_authorization(pjsip_hdr *h) +{ + pjsip_authorization_hdr *hdr = (pjsip_authorization_hdr*)h; + + if (h->type != PJSIP_H_AUTHORIZATION) + return -1410; + + if (pj_strcmp2(&hdr->scheme, "Digest")) + return -1420; + + if (pj_strcmp2(&hdr->credential.digest.username, "username")) + return -1421; + + if (pj_strcmp2(&hdr->credential.digest.realm, "realm")) + return -1422; + + if (pj_strcmp2(&hdr->credential.digest.nonce, "nonce")) + return -1423; + + if (pj_strcmp2(&hdr->credential.digest.uri, "sip:domain")) + return -1424; + + if (pj_strcmp2(&hdr->credential.digest.response, "RESPONSE")) + return -1425; + + if (pj_strcmp2(&hdr->credential.digest.algorithm, "MD5")) + return -1426; + + if (pj_strcmp2(&hdr->credential.digest.cnonce, "CNONCE")) + return -1427; + + if (pj_strcmp2(&hdr->credential.digest.opaque, "OPAQUE")) + return -1428; + + if (pj_strcmp2(&hdr->credential.digest.qop, "auth")) + return -1429; + + if (pj_strcmp2(&hdr->credential.digest.nc, "00000001")) + return -1430; + + return 0; +} + + +/* + "-.!%*_+`'~()<>:\\\"/[]?{}\r\n" + */ +static int hdr_test_cid(pjsip_hdr *h) +{ + pjsip_cid_hdr *hdr = (pjsip_cid_hdr*)h; + + if (h->type != PJSIP_H_CALL_ID) + return -1510; + + if (pj_strcmp2(&hdr->id, "-.!%*_+`'~()<>:\\\"/[]?{}")) + return -1520; + + return 0; +} + +/* + #define SIMPLE_ADDR_SPEC "sip:host" + */ +static int test_simple_addr_spec(pjsip_uri *uri) +{ + pjsip_sip_uri *sip_uri = (pjsip_sip_uri *)pjsip_uri_get_uri(uri); + + if (!PJSIP_URI_SCHEME_IS_SIP(uri)) + return -900; + + if (pj_strcmp2(&sip_uri->host, "host")) + return -910; + + if (sip_uri->port != 0) + return -920; + + return 0; +} + +/* +#define PARAM_CHAR "][/:&+$" +#define SIMPLE_ADDR_SPEC "sip:host" +#define ADDR_SPEC SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\"" +#define NAME_ADDR "<" ADDR_SPEC ">" + */ +static int nameaddr_test(void *uri) +{ + pjsip_sip_uri *sip_uri=(pjsip_sip_uri *)pjsip_uri_get_uri((pjsip_uri*)uri); + pjsip_param *param; + int rc; + + if (!PJSIP_URI_SCHEME_IS_SIP(uri)) + return -930; + + rc = test_simple_addr_spec((pjsip_uri*)sip_uri); + if (rc != 0) + return rc; + + if (pj_list_size(&sip_uri->other_param) != 2) + return -940; + + param = sip_uri->other_param.next; + + if (pj_strcmp2(¶m->name, PARAM_CHAR)) + return -942; + + if (pj_strcmp2(¶m->value, PARAM_CHAR)) + return -943; + + param = param->next; + if (pj_strcmp2(¶m->name, "p1")) + return -942; + if (pj_strcmp2(¶m->value, "\";\"")) + return -943; + + return 0; +} + +/* +#define GENERIC_PARAM "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3" + */ +static int generic_param_test(pjsip_param *param_head) +{ + pjsip_param *param; + + if (pj_list_size(param_head) != 4) + return -950; + + param = param_head->next; + + if (pj_strcmp2(¶m->name, "p0")) + return -952; + if (pj_strcmp2(¶m->value, "a")) + return -953; + + param = param->next; + if (pj_strcmp2(¶m->name, "p1")) + return -954; + if (pj_strcmp2(¶m->value, "\"ab:;cd\"")) + return -955; + + param = param->next; + if (pj_strcmp2(¶m->name, "p2")) + return -956; + if (pj_strcmp2(¶m->value, "ab:cd")) + return -957; + + param = param->next; + if (pj_strcmp2(¶m->name, "p3")) + return -958; + if (pj_strcmp2(¶m->value, "")) + return -959; + + return 0; +} + + + +/* + SIMPLE_ADDR_SPEC ";p1=v1\r\n" + */ +static int hdr_test_contact0(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + pjsip_param *param; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1610; + + rc = test_simple_addr_spec(hdr->uri); + if (rc != 0) + return rc; + + if (pj_list_size(&hdr->other_param) != 1) + return -1620; + + param = hdr->other_param.next; + + if (pj_strcmp2(¶m->name, "p1")) + return -1630; + + if (pj_strcmp2(¶m->value, "v1")) + return -1640; + + return 0; +} + +/* + NAME_ADDR GENERIC_PARAM "\r\n", + */ +static int hdr_test_contact1(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; +} + +/* + NAME_ADDR ";q=0" + */ +static int hdr_test_contact_q0(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 0) + return -1711; + + return 0; +} + +/* + NAME_ADDR ";q=0.5" + */ +static int hdr_test_contact_q1(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 500) + return -1712; + + return 0; +} + +/* + NAME_ADDR ";q=1" + */ +static int hdr_test_contact_q2(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 1000) + return -1713; + + return 0; +} + +/* + NAME_ADDR ";q=1.0" + */ +static int hdr_test_contact_q3(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 1000) + return -1714; + + return 0; +} + +/* + NAME_ADDR ";q=1.15" + */ +static int hdr_test_contact_q4(pjsip_hdr *h) +{ + pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h; + int rc; + + if (h->type != PJSIP_H_CONTACT) + return -1710; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + if (hdr->q1000 != 1150) + return -1715; + + return 0; +} + +/* + "10" + */ +static int hdr_test_content_length(pjsip_hdr *h) +{ + pjsip_clen_hdr *hdr = (pjsip_clen_hdr*)h; + + if (h->type != PJSIP_H_CONTENT_LENGTH) + return -1810; + + if (hdr->len != 10) + return -1820; + + return 0; +} + +/* + "application/sdp" GENERIC_PARAM, + */ +static int hdr_test_content_type(pjsip_hdr *h) +{ + pjsip_ctype_hdr *hdr = (pjsip_ctype_hdr*)h; + + if (h->type != PJSIP_H_CONTENT_TYPE) + return -1910; + + if (pj_strcmp2(&hdr->media.type, "application")) + return -1920; + + if (pj_strcmp2(&hdr->media.subtype, "sdp")) + return -1930; + + /* Currently, if the media parameter contains escaped characters, + * pjsip will print the parameter unescaped. + */ + PJ_TODO(FIX_PARAMETER_IN_MEDIA_TYPE); + + if (pj_strcmp2(&hdr->media.param, ";" GENERIC_PARAM_PARSED)) + return -1940; + + return 0; +} + +/* + NAME_ADDR GENERIC_PARAM, + */ +static int hdr_test_from(pjsip_hdr *h) +{ + pjsip_from_hdr *hdr = (pjsip_from_hdr*)h; + int rc; + + if (h->type != PJSIP_H_FROM) + return -2010; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; +} + +/* + "Digest realm=\"realm\", domain=\"sip:domain\", nonce=\"nonce\", " \ + "opaque=\"opaque\", stale=true, algorithm=MD5, qop=\"auth\"", + */ +static int hdr_test_proxy_authenticate(pjsip_hdr *h) +{ + pjsip_proxy_authenticate_hdr *hdr = (pjsip_proxy_authenticate_hdr*)h; + + if (h->type != PJSIP_H_PROXY_AUTHENTICATE) + return -2110; + + if (pj_strcmp2(&hdr->scheme, "Digest")) + return -2120; + + if (pj_strcmp2(&hdr->challenge.digest.realm, "realm")) + return -2130; + + if (pj_strcmp2(&hdr->challenge.digest.domain, "sip:domain")) + return -2140; + + if (pj_strcmp2(&hdr->challenge.digest.nonce, "nonce")) + return -2150; + + if (pj_strcmp2(&hdr->challenge.digest.opaque, "opaque")) + return -2160; + + if (hdr->challenge.digest.stale != 1) + return -2170; + + if (pj_strcmp2(&hdr->challenge.digest.algorithm, "MD5")) + return -2180; + + if (pj_strcmp2(&hdr->challenge.digest.qop, "auth")) + return -2190; + + return 0; +} + +/* + NAME_ADDR GENERIC_PARAM, + */ +static int hdr_test_record_route(pjsip_hdr *h) +{ + pjsip_rr_hdr *hdr = (pjsip_rr_hdr*)h; + int rc; + + if (h->type != PJSIP_H_RECORD_ROUTE) + return -2210; + + rc = nameaddr_test(&hdr->name_addr); + if (rc != 0) + return rc; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; + +} + +/* + " \r\n" + */ +static int hdr_test_supported(pjsip_hdr *h) +{ + pjsip_supported_hdr *hdr = (pjsip_supported_hdr*)h; + + if (h->type != PJSIP_H_SUPPORTED) + return -2310; + + if (hdr->count != 0) + return -2320; + + return 0; +} + +/* + NAME_ADDR GENERIC_PARAM, + */ +static int hdr_test_to(pjsip_hdr *h) +{ + pjsip_to_hdr *hdr = (pjsip_to_hdr*)h; + int rc; + + if (h->type != PJSIP_H_TO) + return -2410; + + rc = nameaddr_test(hdr->uri); + if (rc != 0) + return rc; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; +} + +/* + "SIP/2.0 host" GENERIC_PARAM + */ +static int hdr_test_via(pjsip_hdr *h) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*)h; + int rc; + + if (h->type != PJSIP_H_VIA) + return -2510; + + if (pj_strcmp2(&hdr->transport, "XYZ")) + return -2515; + + if (pj_strcmp2(&hdr->sent_by.host, "host")) + return -2520; + + if (hdr->sent_by.port != 0) + return -2530; + + rc = generic_param_test(&hdr->other_param); + if (rc != 0) + return rc; + + return 0; +} + + +/* + "SIP/2.0/UDP [::1]" + */ +static int hdr_test_via_ipv6_1(pjsip_hdr *h) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*)h; + + if (h->type != PJSIP_H_VIA) + return -2610; + + if (pj_strcmp2(&hdr->transport, "UDP")) + return -2615; + + if (pj_strcmp2(&hdr->sent_by.host, "::1")) + return -2620; + + if (hdr->sent_by.port != 0) + return -2630; + + return 0; +} + +/* "SIP/2.0/UDP [::1]:5061" */ +static int hdr_test_via_ipv6_2(pjsip_hdr *h) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*)h; + + if (h->type != PJSIP_H_VIA) + return -2710; + + if (pj_strcmp2(&hdr->transport, "UDP")) + return -2715; + + if (pj_strcmp2(&hdr->sent_by.host, "::1")) + return -2720; + + if (hdr->sent_by.port != 5061) + return -2730; + + return 0; +} + +/* "SIP/2.0/UDP [::1];rport=5061;received=::2" */ +static int hdr_test_via_ipv6_3(pjsip_hdr *h) +{ + pjsip_via_hdr *hdr = (pjsip_via_hdr*)h; + + if (h->type != PJSIP_H_VIA) + return -2810; + + if (pj_strcmp2(&hdr->transport, "UDP")) + return -2815; + + if (pj_strcmp2(&hdr->sent_by.host, "::1")) + return -2820; + + if (hdr->sent_by.port != 0) + return -2830; + + if (pj_strcmp2(&hdr->recvd_param, "::2")) + return -2840; + + if (hdr->rport_param != 5061) + return -2850; + + return 0; +} + +/* "10(Already Pending Register)" */ +static int hdr_test_retry_after1(pjsip_hdr *h) +{ + pjsip_retry_after_hdr *hdr = (pjsip_retry_after_hdr*)h; + + if (h->type != PJSIP_H_RETRY_AFTER) + return -2910; + + if (hdr->ivalue != 10) + return -2920; + + if (pj_strcmp2(&hdr->comment, "Already Pending Register")) + return -2930; + + return 0; +} + +/* Subject: \xC0\x81 */ +static int hdr_test_subject_utf(pjsip_hdr *h) +{ + pjsip_subject_hdr *hdr = (pjsip_subject_hdr*)h; + + if (pj_strcmp2(&h->name, "Subject")) + return -2950; + + if (pj_strcmp2(&hdr->hvalue, "\xC0\x81")) + return -2960; + + return 0; +} + +static int hdr_test(void) +{ + unsigned i; + + PJ_LOG(3,(THIS_FILE, " testing header parsing..")); + + for (i=0; i<PJ_ARRAY_SIZE(hdr_test_data); ++i) { + struct hdr_test_t *test = &hdr_test_data[i]; + pj_str_t hname; + int len, parsed_len; + pj_pool_t *pool; + pjsip_hdr *parsed_hdr1=NULL, *parsed_hdr2=NULL; + char *input, *output; +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + static char hcontent[1024]; +#else + char *hcontent; +#endif + int rc; + + pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE); + + /* Parse the header */ + hname = pj_str(test->hname); + len = strlen(test->hcontent); +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + PJ_ASSERT_RETURN(len < sizeof(hcontent), PJSIP_EMSGTOOLONG); + strcpy(hcontent, test->hcontent); +#else + hcontent = test->hcontent; +#endif + + parsed_hdr1 = (pjsip_hdr*) pjsip_parse_hdr(pool, &hname, + hcontent, len, + &parsed_len); + if (parsed_hdr1 == NULL) { + if (test->flags & HDR_FLAG_PARSE_FAIL) { + pj_pool_release(pool); + continue; + } + PJ_LOG(3,(THIS_FILE, " error parsing header %s: %s", test->hname, test->hcontent)); + return -500; + } + + /* Test the parsing result */ + if (test->test && (rc=test->test(parsed_hdr1)) != 0) { + PJ_LOG(3,(THIS_FILE, " validation failed for header %s: %s", test->hname, test->hcontent)); + PJ_LOG(3,(THIS_FILE, " error code is %d", rc)); + return -502; + } + +#if 1 + /* Parse with hshortname, if present */ + if (test->hshort_name) { + hname = pj_str(test->hshort_name); + len = strlen(test->hcontent); +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + PJ_ASSERT_RETURN(len < sizeof(hcontent), PJSIP_EMSGTOOLONG); + strcpy(hcontent, test->hcontent); +#else + hcontent = test->hcontent; +#endif + + parsed_hdr2 = (pjsip_hdr*) pjsip_parse_hdr(pool, &hname, hcontent, len, &parsed_len); + if (parsed_hdr2 == NULL) { + PJ_LOG(3,(THIS_FILE, " error parsing header %s: %s", test->hshort_name, test->hcontent)); + return -510; + } + } +#endif + + if (test->flags & HDR_FLAG_DONT_PRINT) { + pj_pool_release(pool); + continue; + } + + /* Print the original header */ + input = (char*) pj_pool_alloc(pool, 1024); + len = pj_ansi_snprintf(input, 1024, "%s: %s", test->hname, test->hcontent); + if (len < 1 || len >= 1024) + return -520; + + /* Print the parsed header*/ + output = (char*) pj_pool_alloc(pool, 1024); + len = pjsip_hdr_print_on(parsed_hdr1, output, 1024); + if (len < 1 || len >= 1024) { + PJ_LOG(3,(THIS_FILE, " header too long: %s: %s", test->hname, test->hcontent)); + return -530; + } + output[len] = 0; + + if (strcmp(input, output) != 0) { + PJ_LOG(3,(THIS_FILE, " header character by character comparison failed.")); + PJ_LOG(3,(THIS_FILE, " original header=|%s|", input)); + PJ_LOG(3,(THIS_FILE, " parsed header =|%s|", output)); + return -540; + } + + pj_pool_release(pool); + } + + return 0; +} + + +/*****************************************************************************/ + +int msg_test(void) +{ + enum { COUNT = 1, DETECT=0, PARSE=1, PRINT=2 }; + struct { + unsigned detect; + unsigned parse; + unsigned print; + } run[COUNT]; + unsigned i, max, avg_len; + char desc[250]; + pj_status_t status; + + status = hdr_test(); + if (status != 0) + return status; + + status = simple_test(); + if (status != PJ_SUCCESS) + return status; + +#if INCLUDE_BENCHMARKS + for (i=0; i<COUNT; ++i) { + PJ_LOG(3,(THIS_FILE, " benchmarking (%d of %d)..", i+1, COUNT)); + status = msg_benchmark(&run[i].detect, &run[i].parse, &run[i].print); + if (status != PJ_SUCCESS) + return status; + } + + /* Calculate average message length */ + for (i=0, avg_len=0; i<PJ_ARRAY_SIZE(test_array); ++i) { + avg_len += test_array[i].len; + } + avg_len /= PJ_ARRAY_SIZE(test_array); + + + /* Print maximum detect/sec */ + for (i=0, max=0; i<COUNT; ++i) + if (run[i].detect > 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 <tt>pjsip_find_msg()</tt> " + "per second (tested with %d message sets with " + "average message length of " + "%d bytes)", (int)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<COUNT; ++i) + if (run[i].parse > max) max = run[i].parse; + + PJ_LOG(3,("", " Maximum message parsing/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP messages " + "can be <b>parsed</b> by <tt>pjsip_parse_msg()</tt> " + "per second (tested with %d message sets with " + "average message length of " + "%d bytes)", (int)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<COUNT; ++i) + if (run[i].print > max) max = run[i].print; + + PJ_LOG(3,("", " Maximum message print/sec=%u", max)); + + pj_ansi_sprintf(desc, "Number of SIP messages " + "can be <b>printed</b> by <tt>pjsip_msg_print()</tt>" + " per second (tested with %d message sets with " + "average message length of " + "%d bytes)", (int)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."); + +#endif /* INCLUDE_BENCHMARKS */ + + return PJ_SUCCESS; +} + + + + diff --git a/pjsip/src/test/regc_test.c b/pjsip/src/test/regc_test.c new file mode 100644 index 00000000..0d668782 --- /dev/null +++ b/pjsip/src/test/regc_test.c @@ -0,0 +1,1162 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip_ua.h> +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "regc_test.c" + + +/************************************************************************/ +/* A module to inject error into outgoing sending operation */ +static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata); + +static struct +{ + pjsip_module mod; + unsigned count; + unsigned count_before_reject; +} send_mod = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-send", 8 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + &mod_send_on_tx_request, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + }, + 0, + 0xFFFF +}; + + +static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata) +{ + PJ_UNUSED_ARG(tdata); + + if (++send_mod.count > send_mod.count_before_reject) + return PJ_ECANCELLED; + else + return PJ_SUCCESS; +}; + + +/************************************************************************/ +/* Registrar for testing */ +static pj_bool_t regs_rx_request(pjsip_rx_data *rdata); + +enum contact_op +{ + NONE, /* don't put Contact header */ + EXACT, /* return exact contact */ + MODIFIED, /* return modified Contact header */ +}; + +struct registrar_cfg +{ + pj_bool_t respond; /* should it respond at all */ + unsigned status_code; /* final response status code */ + pj_bool_t authenticate; /* should we authenticate? */ + enum contact_op contact_op; /* What should we do with Contact */ + unsigned expires_param; /* non-zero to put in expires param */ + unsigned expires; /* non-zero to put in Expires header*/ + + pj_str_t more_contacts; /* Additional Contact headers to put*/ +}; + +static struct registrar +{ + pjsip_module mod; + struct registrar_cfg cfg; + unsigned response_cnt; +} registrar = +{ + { + NULL, NULL, /* prev, next. */ + { "registrar", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + ®s_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + } +}; + +static pj_bool_t regs_rx_request(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + pjsip_hdr hdr_list; + int code; + pj_status_t status; + + if (msg->line.req.method.id != PJSIP_REGISTER_METHOD) + return PJ_FALSE; + + if (!registrar.cfg.respond) + return PJ_TRUE; + + pj_list_init(&hdr_list); + + if (registrar.cfg.authenticate && + pjsip_msg_find_hdr(msg, PJSIP_H_AUTHORIZATION, NULL)==NULL) + { + pjsip_generic_string_hdr *hwww; + const pj_str_t hname = pj_str("WWW-Authenticate"); + const pj_str_t hvalue = pj_str("Digest realm=\"test\""); + + hwww = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname, + &hvalue); + pj_list_push_back(&hdr_list, hwww); + + code = 401; + + } else { + if (registrar.cfg.contact_op == EXACT || + registrar.cfg.contact_op == MODIFIED) + { + pjsip_hdr *hsrc; + + for (hsrc=msg->hdr.next; hsrc!=&msg->hdr; hsrc=hsrc->next) { + pjsip_contact_hdr *hdst; + + if (hsrc->type != PJSIP_H_CONTACT) + continue; + + hdst = (pjsip_contact_hdr*) + pjsip_hdr_clone(rdata->tp_info.pool, hsrc); + + if (hdst->expires==0) + continue; + + if (registrar.cfg.contact_op == MODIFIED) { + if (PJSIP_URI_SCHEME_IS_SIP(hdst->uri) || + PJSIP_URI_SCHEME_IS_SIPS(hdst->uri)) + { + pjsip_sip_uri *sip_uri = (pjsip_sip_uri*) + pjsip_uri_get_uri(hdst->uri); + sip_uri->host = pj_str("x-modified-host"); + sip_uri->port = 1; + } + } + + if (registrar.cfg.expires_param) + hdst->expires = registrar.cfg.expires_param; + + pj_list_push_back(&hdr_list, hdst); + } + } + + if (registrar.cfg.more_contacts.slen) { + pjsip_generic_string_hdr *hcontact; + const pj_str_t hname = pj_str("Contact"); + + hcontact = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname, + ®istrar.cfg.more_contacts); + pj_list_push_back(&hdr_list, hcontact); + } + + if (registrar.cfg.expires) { + pjsip_expires_hdr *hexp; + + hexp = pjsip_expires_hdr_create(rdata->tp_info.pool, + registrar.cfg.expires); + pj_list_push_back(&hdr_list, hexp); + } + + registrar.response_cnt++; + + code = registrar.cfg.status_code; + } + + status = pjsip_endpt_respond(endpt, NULL, rdata, code, NULL, + &hdr_list, NULL, NULL); + pj_assert(status == PJ_SUCCESS); + + return PJ_TRUE; +} + + +/************************************************************************/ +/* Client registration test session */ +struct client +{ + /* Result/expected result */ + int error; + int code; + pj_bool_t have_reg; + int expiration; + unsigned contact_cnt; + pj_bool_t auth; + + /* Commands */ + pj_bool_t destroy_on_cb; + + /* Status */ + pj_bool_t done; + + /* Additional results */ + int interval; + int next_reg; +}; + +/* regc callback */ +static void client_cb(struct pjsip_regc_cbparam *param) +{ + struct client *client = (struct client*) param->token; + pjsip_regc_info info; + pj_status_t status; + + client->done = PJ_TRUE; + + status = pjsip_regc_get_info(param->regc, &info); + pj_assert(status == PJ_SUCCESS); + + client->error = (param->status != PJ_SUCCESS); + client->code = param->code; + + if (client->error) + return; + + client->have_reg = info.auto_reg && info.interval>0 && + param->expiration>0; + client->expiration = param->expiration; + client->contact_cnt = param->contact_cnt; + client->interval = info.interval; + client->next_reg = info.next_reg; + + if (client->destroy_on_cb) + pjsip_regc_destroy(param->regc); +} + + +/* Generic client test session */ +static struct client client_result; +static int do_test(const char *title, + const struct registrar_cfg *srv_cfg, + const struct client *client_cfg, + const pj_str_t *registrar_uri, + unsigned contact_cnt, + const pj_str_t contacts[], + unsigned expires, + pj_bool_t leave_session, + pjsip_regc **p_regc) +{ + pjsip_regc *regc; + unsigned i; + const pj_str_t aor = pj_str("<sip:regc-test@pjsip.org>"); + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " %s", title)); + + /* Modify registrar settings */ + pj_memcpy(®istrar.cfg, srv_cfg, sizeof(*srv_cfg)); + + pj_bzero(&client_result, sizeof(client_result)); + client_result.destroy_on_cb = client_cfg->destroy_on_cb; + + status = pjsip_regc_create(endpt, &client_result, &client_cb, ®c); + if (status != PJ_SUCCESS) + return -100; + + status = pjsip_regc_init(regc, registrar_uri, &aor, &aor, contact_cnt, + contacts, expires ? expires : 60); + if (status != PJ_SUCCESS) { + pjsip_regc_destroy(regc); + return -110; + } + + if (client_cfg->auth) { + pjsip_cred_info cred; + + pj_bzero(&cred, sizeof(cred)); + cred.realm = pj_str("*"); + cred.scheme = pj_str("digest"); + cred.username = pj_str("user"); + cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; + cred.data = pj_str("password"); + + status = pjsip_regc_set_credentials(regc, 1, &cred); + if (status != PJ_SUCCESS) { + pjsip_regc_destroy(regc); + return -115; + } + } + + /* Register */ + status = pjsip_regc_register(regc, PJ_TRUE, &tdata); + if (status != PJ_SUCCESS) { + pjsip_regc_destroy(regc); + return -120; + } + status = pjsip_regc_send(regc, tdata); + + /* That's it, wait until the callback is sent */ + for (i=0; i<600 && !client_result.done; ++i) { + flush_events(100); + } + + if (!client_result.done) { + PJ_LOG(3,(THIS_FILE, " error: test has timed out")); + pjsip_regc_destroy(regc); + return -200; + } + + /* Destroy the regc, we're done with the test, unless we're + * instructed to leave the session open. + */ + if (!leave_session && !client_cfg->destroy_on_cb) + pjsip_regc_destroy(regc); + + /* Compare results with expected results */ + + if (client_result.error != client_cfg->error) { + PJ_LOG(3,(THIS_FILE, " error: expecting err=%d, got err=%d", + client_cfg->error, client_result.error)); + return -210; + } + if (client_result.code != client_cfg->code) { + PJ_LOG(3,(THIS_FILE, " error: expecting code=%d, got code=%d", + client_cfg->code, client_result.code)); + return -220; + } + if (client_result.expiration != client_cfg->expiration) { + PJ_LOG(3,(THIS_FILE, " error: expecting expiration=%d, got expiration=%d", + client_cfg->expiration, client_result.expiration)); + return -240; + } + if (client_result.contact_cnt != client_cfg->contact_cnt) { + PJ_LOG(3,(THIS_FILE, " error: expecting contact_cnt=%d, got contact_cnt=%d", + client_cfg->contact_cnt, client_result.contact_cnt)); + return -250; + } + if (client_result.have_reg != client_cfg->have_reg) { + PJ_LOG(3,(THIS_FILE, " error: expecting have_reg=%d, got have_reg=%d", + client_cfg->have_reg, client_result.have_reg)); + return -260; + } + if (client_result.interval != client_result.expiration) { + PJ_LOG(3,(THIS_FILE, " error: interval (%d) is different than expiration (%d)", + client_result.interval, client_result.expiration)); + return -270; + } + if (client_result.expiration > 0 && client_result.next_reg < 1) { + PJ_LOG(3,(THIS_FILE, " error: next_reg=%d, expecting positive number because expiration is %d", + client_result.next_reg, client_result.expiration)); + return -280; + } + + /* Looks like everything is okay. */ + if (leave_session) { + *p_regc = regc; + } + + return 0; +} + + +/************************************************************************/ +/* Customized tests */ + +/* Check that client is sending register refresh */ +static int keep_alive_test(const pj_str_t *registrar_uri) +{ + enum { TIMEOUT = 40 }; + struct registrar_cfg server_cfg = + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}}; + struct client client_cfg = + /* error code have_reg expiration contact_cnt auth? destroy*/ + { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE}; + pj_str_t contact = pj_str("<sip:c@C>"); + + + pjsip_regc *regc; + unsigned i; + int ret; + + ret = do_test("register refresh (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri, + 1, &contact, TIMEOUT, PJ_TRUE, ®c); + if (ret != 0) + return ret; + + /* Reset server response_cnt */ + registrar.response_cnt = 0; + + /* Wait until keep-alive/refresh is done */ + for (i=0; i<(TIMEOUT-1)*10 && registrar.response_cnt==0; ++i) { + flush_events(100); + } + + if (registrar.response_cnt==0) { + PJ_LOG(3,(THIS_FILE, " error: no refresh is received")); + return -400; + } + + if (client_result.error) { + PJ_LOG(3,(THIS_FILE, " error: got error")); + return -410; + } + if (client_result.code != 200) { + PJ_LOG(3,(THIS_FILE, " error: expecting code=%d, got code=%d", + 200, client_result.code)); + return -420; + } + if (client_result.expiration != TIMEOUT) { + PJ_LOG(3,(THIS_FILE, " error: expecting expiration=%d, got expiration=%d", + TIMEOUT, client_result.expiration)); + return -440; + } + if (client_result.contact_cnt != 1) { + PJ_LOG(3,(THIS_FILE, " error: expecting contact_cnt=%d, got contact_cnt=%d", + TIMEOUT, client_result.contact_cnt)); + return -450; + } + if (client_result.have_reg == 0) { + PJ_LOG(3,(THIS_FILE, " error: expecting have_reg=%d, got have_reg=%d", + 1, client_result.have_reg)); + return -460; + } + if (client_result.interval != TIMEOUT) { + PJ_LOG(3,(THIS_FILE, " error: interval (%d) is different than expiration (%d)", + client_result.interval, TIMEOUT)); + return -470; + } + if (client_result.expiration > 0 && client_result.next_reg < 1) { + PJ_LOG(3,(THIS_FILE, " error: next_reg=%d, expecting positive number because expiration is %d", + client_result.next_reg, client_result.expiration)); + return -480; + } + + /* Success */ + pjsip_regc_destroy(regc); + return 0; +} + + +/* Send error on refresh */ +static int refresh_error(const pj_str_t *registrar_uri, + pj_bool_t destroy_on_cb) +{ + enum { TIMEOUT = 40 }; + struct registrar_cfg server_cfg = + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}}; + struct client client_cfg = + /* error code have_reg expiration contact_cnt auth? destroy*/ + { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE}; + pj_str_t contact = pj_str("<sip:c@C>"); + + pjsip_regc *regc; + unsigned i; + int ret; + + ret = do_test("refresh error (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri, + 1, &contact, TIMEOUT, PJ_TRUE, ®c); + if (ret != 0) + return ret; + + /* Reset server response_cnt */ + registrar.response_cnt = 0; + + /* inject error for transmission */ + send_mod.count = 0; + send_mod.count_before_reject = 0; + + /* reconfigure client */ + client_result.done = PJ_FALSE; + client_result.destroy_on_cb = destroy_on_cb; + + /* Wait until keep-alive/refresh is done */ + for (i=0; i<TIMEOUT*10 && !client_result.done; ++i) { + flush_events(100); + } + + send_mod.count_before_reject = 0xFFFF; + + if (!destroy_on_cb) + pjsip_regc_destroy(regc); + + if (!client_result.done) { + PJ_LOG(3,(THIS_FILE, " error: test has timed out")); + return -500; + } + + /* Expecting error */ + if (client_result.error==PJ_FALSE && client_result.code/100==2) { + PJ_LOG(3,(THIS_FILE, " error: expecting error got successfull result")); + return -510; + } + + return PJ_SUCCESS; +}; + + +/* Send error on refresh */ +static int update_test(const pj_str_t *registrar_uri) +{ + enum { TIMEOUT = 40 }; + struct registrar_cfg server_cfg = + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, TIMEOUT, 0, {NULL, 0}}; + struct client client_cfg = + /* error code have_reg expiration contact_cnt auth? destroy*/ + { PJ_FALSE, 200, PJ_TRUE, TIMEOUT, 1, PJ_FALSE,PJ_FALSE}; + pj_str_t contacts[] = { + { "<sip:a>", 7 }, + { "<sip:b>", 7 }, + { "<sip:c>", 7 } + }; + + pjsip_regc *regc; + pjsip_contact_hdr *h1, *h2; + pjsip_sip_uri *u1, *u2; + unsigned i; + pj_status_t status; + pjsip_tx_data *tdata = NULL; + int ret = 0; + + /* initially only has 1 contact */ + ret = do_test("update test", &server_cfg, &client_cfg, registrar_uri, + 1, &contacts[0], TIMEOUT, PJ_TRUE, ®c); + if (ret != 0) { + return -600; + } + + /***** + * replace the contact with new one + */ + PJ_LOG(3,(THIS_FILE, " replacing contact")); + status = pjsip_regc_update_contact(regc, 1, &contacts[1]); + if (status != PJ_SUCCESS) { + ret = -610; + goto on_return; + } + + status = pjsip_regc_register(regc, PJ_TRUE, &tdata); + if (status != PJ_SUCCESS) { + ret = -620; + goto on_return; + } + + /* Check that the REGISTER contains two Contacts: + * - <sip:a>;expires=0, + * - <sip:b> + */ + h1 = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL); + if (!h1) { + ret = -630; + goto on_return; + } + if ((void*)h1->next == (void*)&tdata->msg->hdr) + h2 = NULL; + else + h2 = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next); + if (!h2) { + ret = -640; + goto on_return; + } + /* must not have other Contact header */ + if ((void*)h2->next != (void*)&tdata->msg->hdr && + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL) + { + ret = -645; + goto on_return; + } + + u1 = (pjsip_sip_uri*) pjsip_uri_get_uri(h1->uri); + u2 = (pjsip_sip_uri*) pjsip_uri_get_uri(h2->uri); + + if (*u1->host.ptr == 'a') { + if (h1->expires != 0) { + ret = -650; + goto on_return; + } + if (h2->expires == 0) { + ret = -660; + goto on_return; + } + + } else { + pj_assert(*u1->host.ptr == 'b'); + if (h1->expires == 0) { + ret = -670; + goto on_return; + } + if (h2->expires != 0) { + ret = -680; + goto on_return; + } + } + + /* Destroy tdata */ + pjsip_tx_data_dec_ref(tdata); + tdata = NULL; + + + + /** + * First loop, it will update with more contacts. Second loop + * should do nothing. + */ + for (i=0; i<2; ++i) { + if (i==0) + PJ_LOG(3,(THIS_FILE, " replacing with more contacts")); + else + PJ_LOG(3,(THIS_FILE, " updating contacts with same contacts")); + + status = pjsip_regc_update_contact(regc, 2, &contacts[1]); + if (status != PJ_SUCCESS) { + ret = -710; + goto on_return; + } + + status = pjsip_regc_register(regc, PJ_TRUE, &tdata); + if (status != PJ_SUCCESS) { + ret = -720; + goto on_return; + } + + /* Check that the REGISTER contains two Contacts: + * - <sip:b> + * - <sip:c> + */ + h1 = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL); + if (!h1) { + ret = -730; + goto on_return; + } + if ((void*)h1->next == (void*)&tdata->msg->hdr) + h2 = NULL; + else + h2 = (pjsip_contact_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next); + if (!h2) { + ret = -740; + goto on_return; + } + /* must not have other Contact header */ + if ((void*)h2->next != (void*)&tdata->msg->hdr && + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL) + { + ret = -745; + goto on_return; + } + + /* both contacts must not have expires=0 parameter */ + if (h1->expires == 0) { + ret = -750; + goto on_return; + } + if (h2->expires == 0) { + ret = -760; + goto on_return; + } + + /* Destroy tdata */ + pjsip_tx_data_dec_ref(tdata); + tdata = NULL; + } + +on_return: + if (tdata) pjsip_tx_data_dec_ref(tdata); + pjsip_regc_destroy(regc); + return ret; +}; + + +/* send error on authentication */ +static int auth_send_error(const pj_str_t *registrar_uri, + pj_bool_t destroy_on_cb) +{ + enum { TIMEOUT = 40 }; + struct registrar_cfg server_cfg = + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_TRUE, EXACT, 75, 0, {NULL, 0}}; + struct client client_cfg = + /* error code have_reg expiration contact_cnt auth? destroy*/ + { PJ_TRUE, 401, PJ_FALSE, -1, 0, PJ_TRUE, PJ_TRUE}; + pj_str_t contact = pj_str("<sip:c@C>"); + + pjsip_regc *regc; + int ret; + + client_cfg.destroy_on_cb = destroy_on_cb; + + /* inject error for second request retry */ + send_mod.count = 0; + send_mod.count_before_reject = 1; + + ret = do_test("auth send error", &server_cfg, &client_cfg, registrar_uri, + 1, &contact, TIMEOUT, PJ_TRUE, ®c); + + send_mod.count_before_reject = 0xFFFF; + + return ret; +}; + + + + +/************************************************************************/ +enum +{ + OFF = 1, + ON = 2, + ON_OFF = 3, +}; + +int regc_test(void) +{ + struct test_rec { + unsigned check_contact; + unsigned add_xuid_param; + + const char *title; + char *alt_registrar; + unsigned contact_cnt; + char *contacts[4]; + unsigned expires; + struct registrar_cfg server_cfg; + struct client client_cfg; + } test_rec[] = + { + /* immediate error */ + { + OFF, /* check_contact */ + OFF, /* add_xuid_param */ + "immediate error", /* title */ + "sip:unresolved-host-xyy", /* alt_registrar */ + 1, /* contact cnt */ + { "sip:user@127.0.0.1:5060" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_FALSE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 503, PJ_FALSE, -1, 0, PJ_FALSE} + }, + + /* timeout test */ + { + OFF, /* check_contact */ + OFF, /* add_xuid_param */ + "timeout test (takes ~32 secs)",/* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "sip:user@127.0.0.1:5060" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_FALSE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth? */ + { PJ_FALSE, 408, PJ_FALSE, -1, 0, PJ_FALSE} + }, + + /* Basic successful registration scenario: + * a good registrar returns the Contact header as is and + * add expires parameter. In this test no additional bindings + * are returned. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "basic", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, 75, 65, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_FALSE} + }, + + /* Basic successful registration scenario with authentication + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "authentication", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_TRUE, EXACT, 75, 65, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_TRUE} + }, + + /* a good registrar returns the Contact header as is and + * add expires parameter. Also it adds bindings from other + * clients in this test. + */ + { + ON_OFF, /* check_contact */ + ON, /* add_xuid_param */ + "more bindings in response", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060;transport=udp>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, EXACT, 75, 65, {"<sip:a@a>;expires=70", 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 2, PJ_FALSE} + }, + + + /* a bad registrar returns modified Contact header, but it + * still returns all parameters intact. In this case + * the expiration is taken from the expires param because + * of matching xuid param or because the number of + * Contact header matches. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "registrar modifies Contact header", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, MODIFIED, 75, 65, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 1, PJ_FALSE} + }, + + + /* a bad registrar returns modified Contact header, but it + * still returns all parameters intact. In addition it returns + * bindings from other clients. + * + * In this case the expiration is taken from the expires param + * because add_xuid_param is enabled. + */ + { + ON_OFF, /* check_contact */ + ON, /* add_xuid_param */ + "registrar modifies Contact header and add bindings", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, MODIFIED, 75, 65, {"<sip:a@a>;expires=70", 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 75, 2, PJ_FALSE} + }, + + + /* a bad registrar returns completely different Contact and + * all parameters are gone. In this case the expiration is + * also taken from the expires param since the number of + * header matches. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "registrar replaces Contact header", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 202, PJ_FALSE, NONE, 0, 65, {"<sip:a@A>;expires=75", 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 202, PJ_TRUE, 75, 1, PJ_FALSE} + }, + + + /* a bad registrar returns completely different Contact (and + * all parameters are gone) and it also includes bindings from + * other clients. + * In this case the expiration is taken from the Expires header. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + " as above with additional bindings", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 65, {"<sip:a@A>;expires=75, <sip:b@B;expires=70>", 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 65, 2, PJ_FALSE} + }, + + /* the registrar doesn't return any bindings, but for some + * reason it includes an Expires header. + * In this case the expiration is taken from the Expires header. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "no Contact but with Expires", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" }, /* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 65, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 65, 0, PJ_FALSE} + }, + + /* Neither Contact header nor Expires header are present. + * In this case the expiration is taken from the request. + */ + { + ON_OFF, /* check_contact */ + ON_OFF, /* add_xuid_param */ + "no Contact and no Expires", /* title */ + NULL, /* alt_registrar */ + 1, /* contact cnt */ + { "<sip:user@127.0.0.1:5060>" },/* contacts[] */ + 600, /* expires */ + + /* registrar config: */ + /* respond code auth contact exp_prm expires more_contacts */ + { PJ_TRUE, 200, PJ_FALSE, NONE, 0, 0, {NULL, 0}}, + + /* client expected results: */ + /* error code have_reg expiration contact_cnt auth?*/ + { PJ_FALSE, 200, PJ_TRUE, 600, 0, PJ_FALSE} + }, + }; + + unsigned i; + pj_sockaddr_in addr; + pjsip_transport *udp = NULL; + pj_uint16_t port; + char registrar_uri_buf[80]; + pj_str_t registrar_uri; + int rc = 0; + + pj_sockaddr_in_init(&addr, 0, 0); + + /* Acquire existing transport, if any */ + rc = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, &addr, sizeof(addr), NULL, &udp); + if (rc == PJ_SUCCESS) { + port = pj_sockaddr_get_port(&udp->local_addr); + pjsip_transport_dec_ref(udp); + udp = NULL; + } else { + rc = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &udp); + if (rc != PJ_SUCCESS) { + app_perror(" error creating UDP transport", rc); + rc = -2; + goto on_return; + } + + port = pj_sockaddr_get_port(&udp->local_addr); + } + + /* Register registrar module */ + rc = pjsip_endpt_register_module(endpt, ®istrar.mod); + if (rc != PJ_SUCCESS) { + app_perror(" error registering module", rc); + rc = -3; + goto on_return; + } + + /* Register send module */ + rc = pjsip_endpt_register_module(endpt, &send_mod.mod); + if (rc != PJ_SUCCESS) { + app_perror(" error registering module", rc); + rc = -3; + goto on_return; + } + + pj_ansi_snprintf(registrar_uri_buf, sizeof(registrar_uri_buf), + "sip:127.0.0.1:%d", (int)port); + registrar_uri = pj_str(registrar_uri_buf); + + for (i=0; i<PJ_ARRAY_SIZE(test_rec); ++i) { + struct test_rec *t = &test_rec[i]; + unsigned j, x; + pj_str_t reg_uri; + pj_str_t contacts[8]; + + /* Fill in the registrar address if it's not specified */ + if (t->alt_registrar == NULL) { + reg_uri = registrar_uri; + } else { + reg_uri = pj_str(t->alt_registrar); + } + + /* Build contact pj_str_t's */ + for (j=0; j<t->contact_cnt; ++j) { + contacts[j] = pj_str(t->contacts[j]); + } + + /* Normalize more_contacts field */ + if (t->server_cfg.more_contacts.ptr) + t->server_cfg.more_contacts.slen = strlen(t->server_cfg.more_contacts.ptr); + + /* Do tests with three combinations: + * - check_contact on/off + * - add_xuid_param on/off + * - destroy_on_callback on/off + */ + for (x=1; x<=2; ++x) { + unsigned y; + + if ((t->check_contact & x) == 0) + continue; + + pjsip_cfg()->regc.check_contact = (x-1); + + for (y=1; y<=2; ++y) { + unsigned z; + + if ((t->add_xuid_param & y) == 0) + continue; + + pjsip_cfg()->regc.add_xuid_param = (y-1); + + for (z=0; z<=1; ++z) { + char new_title[200]; + + t->client_cfg.destroy_on_cb = z; + + sprintf(new_title, "%s [check=%d, xuid=%d, destroy=%d]", + t->title, pjsip_cfg()->regc.check_contact, + pjsip_cfg()->regc.add_xuid_param, z); + rc = do_test(new_title, &t->server_cfg, &t->client_cfg, + ®_uri, t->contact_cnt, contacts, + t->expires, PJ_FALSE, NULL); + if (rc != 0) + goto on_return; + } + + } + } + + /* Sleep between test groups to avoid using up too many + * active transactions. + */ + pj_thread_sleep(1000); + } + + /* keep-alive test */ + rc = keep_alive_test(®istrar_uri); + if (rc != 0) + goto on_return; + + /* Send error on refresh without destroy on callback */ + rc = refresh_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + + /* Send error on refresh, destroy on callback */ + rc = refresh_error(®istrar_uri, PJ_TRUE); + if (rc != 0) + goto on_return; + + /* Updating contact */ + rc = update_test(®istrar_uri); + if (rc != 0) + goto on_return; + + /* Send error during auth, don't destroy on callback */ + rc = auth_send_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + + /* Send error during auth, destroy on callback */ + rc = auth_send_error(®istrar_uri, PJ_FALSE); + if (rc != 0) + goto on_return; + +on_return: + if (registrar.mod.id != -1) { + pjsip_endpt_unregister_module(endpt, ®istrar.mod); + } + if (send_mod.mod.id != -1) { + pjsip_endpt_unregister_module(endpt, &send_mod.mod); + } + if (udp) { + pjsip_transport_dec_ref(udp); + } + return rc; +} + + diff --git a/pjsip/src/test/test.c b/pjsip/src/test/test.c new file mode 100644 index 00000000..5ce2b71a --- /dev/null +++ b/pjsip/src/test/test.c @@ -0,0 +1,392 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +#include "test.h" +#include <pjlib.h> +#include <pjlib-util.h> +#include <pjsip.h> + +#define THIS_FILE "test.c" + +#define DO_TEST(test) do { \ + PJ_LOG(3, (THIS_FILE, "Running %s...", #test)); \ + rc = test; \ + PJ_LOG(3, (THIS_FILE, \ + "%s(%d)", \ + (rc ? "..ERROR" : "..success"), rc)); \ + 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; +int param_log_decor = PJ_LOG_HAS_NEWLINE | PJ_LOG_HAS_TIME | + PJ_LOG_HAS_MICRO_SEC; + +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]; + + PJ_CHECK_STACK(); + + pj_strerror(rc, errbuf, sizeof(errbuf)); + PJ_LOG(3,(THIS_FILE, "%s: [pj_status_t=%d] %s", msg, rc, errbuf)); + +} + +void flush_events(unsigned duration) +{ + pj_time_val stop_time; + + pj_gettimeofday(&stop_time); + stop_time.msec += duration; + pj_time_val_normalize(&stop_time); + + /* Process all events for the specified duration. */ + for (;;) { + pj_time_val timeout = {0, 1}, now; + + pjsip_endpt_handle_events(endpt, &timeout); + + pj_gettimeofday(&now); + if (PJ_TIME_VAL_GTE(now, stop_time)) + break; + } +} + +pj_status_t register_static_modules(pj_size_t *count, pjsip_module **modules) +{ + *count = 0; + 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, "<HTML>\n" + " <HEAD>\n" + " <TITLE>PJSIP %s (%s) - Static Benchmark</TITLE>\n" + " </HEAD>\n" + "<BODY>\n" + "\n", + PJ_VERSION, + (PJ_DEBUG ? "Debug" : "Release")); + pj_file_write(fd_report, buf, &len); + + + /* Title */ + len = pj_ansi_sprintf(buf, "<H1>PJSIP %s (%s) - Static Benchmark</H1>\n", + PJ_VERSION, + (PJ_DEBUG ? "Debug" : "Release")); + pj_file_write(fd_report, buf, &len); + + len = pj_ansi_sprintf(buf, "<P>Below is the benchmark result generated " + "by <b>test-pjsip</b> program. The program " + "is single-threaded only.</P>\n"); + pj_file_write(fd_report, buf, &len); + + + /* Write table heading */ + len = pj_ansi_sprintf(buf, "<TABLE border=\"1\" cellpadding=\"4\">\n" + " <TR><TD bgColor=\"aqua\" align=\"center\">Variable</TD>\n" + " <TD bgColor=\"aqua\" align=\"center\">Value</TD>\n" + " <TD bgColor=\"aqua\" align=\"center\">Description</TD>\n" + " </TR>\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, " <TR><TD><TT>%s</TT></TD>\n" + " <TD align=\"right\"><B>%s %s</B></TD>\n" + " <TD>%s</TD>\n" + " </TR>\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, " <TR><TD><TT>%s</TT></TD>\n" + " <TD align=\"right\"><B>%d %s</B></TD>\n" + " <TD>%s</TD>\n" + " </TR>\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, "</TABLE>\n</BODY>\n</HTML>\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 tsx_test_cnt=0; + struct tsx_test_param tsx_test[10]; + pj_status_t status; +#if INCLUDE_TSX_TEST + unsigned i; + pjsip_transport *tp; +#if PJ_HAS_TCP + pjsip_tpfactory *tpfactory; +#endif /* PJ_HAS_TCP */ +#endif /* INCLUDE_TSX_TEST */ + int line; + + pj_log_set_level(log_level); + pj_log_set_decor(param_log_decor); + + if ((rc=pj_init()) != PJ_SUCCESS) { + app_perror("pj_init", rc); + return rc; + } + + if ((rc=pjlib_util_init()) != PJ_SUCCESS) { + app_perror("pj_init", rc); + 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, + PJSIP_TEST_MEM_SIZE ); + + rc = pjsip_endpt_create(&caching_pool.factory, "endpt", &endpt); + if (rc != PJ_SUCCESS) { + app_perror("pjsip_endpt_create", rc); + pj_caching_pool_destroy(&caching_pool); + return rc; + } + + PJ_LOG(3,(THIS_FILE,"")); + + /* Init logger module. */ + init_msg_logger(); + msg_logger_set_enabled(1); + + /* Start transaction layer module. */ + rc = pjsip_tsx_layer_init_module(endpt); + if (rc != PJ_SUCCESS) { + app_perror(" Error initializing transaction module", rc); + goto on_return; + } + + /* Create loop transport. */ + rc = pjsip_loop_start(endpt, NULL); + if (rc != PJ_SUCCESS) { + app_perror(" error: unable to create datagram loop transport", + 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()); +#endif + +#if INCLUDE_MSG_TEST + DO_TEST(msg_test()); + DO_TEST(msg_err_test()); +#endif + +#if INCLUDE_TXDATA_TEST + DO_TEST(txdata_test()); +#endif + +#if INCLUDE_TSX_BENCH + DO_TEST(tsx_bench()); +#endif + +#if INCLUDE_UDP_TEST + DO_TEST(transport_udp_test()); +#endif + +#if INCLUDE_LOOP_TEST + DO_TEST(transport_loop_test()); +#endif + +#if INCLUDE_TCP_TEST + DO_TEST(transport_tcp_test()); +#endif + +#if INCLUDE_RESOLVE_TEST + DO_TEST(resolve_test()); +#endif + + +#if INCLUDE_TSX_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; + } + +#if PJ_HAS_TCP + 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; + } +#endif + + + for (i=0; i<tsx_test_cnt; ++i) { + DO_TSX_TEST(tsx_basic_test, &tsx_test[i]); + DO_TSX_TEST(tsx_uac_test, &tsx_test[i]); + DO_TSX_TEST(tsx_uas_test, &tsx_test[i]); + } +#endif + +#if INCLUDE_INV_OA_TEST + DO_TEST(inv_offer_answer_test()); +#endif + +#if INCLUDE_REGC_TEST + DO_TEST(regc_test()); +#endif + + +on_return: + flush_events(500); + + /* Dumping memory pool usage */ + PJ_LOG(3,(THIS_FILE, "Peak memory size=%u MB", + caching_pool.peak_used_size / 1000000)); + + pjsip_endpt_destroy(endpt); + pj_caching_pool_destroy(&caching_pool); + + PJ_LOG(3,(THIS_FILE, "")); + + pj_thread_get_stack_info(pj_thread_this(), &filename, &line); + PJ_LOG(3,(THIS_FILE, "Stack max usage: %u, deepest: %s:%u", + pj_thread_get_stack_max_usage(pj_thread_this()), + filename, line)); + if (rc == 0) + PJ_LOG(3,(THIS_FILE, "Looks like everything is okay!..")); + else + PJ_LOG(3,(THIS_FILE, "Test completed with error(s)")); + + report_ival("test-status", rc, "", "Overall test status/result (0==success)"); + close_report(); + return rc; +} + diff --git a/pjsip/src/test/test.h b/pjsip/src/test/test.h new file mode 100644 index 00000000..6c695725 --- /dev/null +++ b/pjsip/src/test/test.h @@ -0,0 +1,125 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __TEST_H__ +#define __TEST_H__ + +#include <pjsip/sip_types.h> + +extern pjsip_endpoint *endpt; + +#define TEST_UDP_PORT 15060 +#define TEST_UDP_PORT_STR "15060" + +/** + * Memory size to use in caching pool. + * Default: 2MB + */ +#ifndef PJSIP_TEST_MEM_SIZE +# define PJSIP_TEST_MEM_SIZE (2*1024*1024) +#endif + + + +#define INCLUDE_MESSAGING_GROUP 1 +#define INCLUDE_TRANSPORT_GROUP 1 +#define INCLUDE_TSX_GROUP 1 +#define INCLUDE_INV_GROUP 1 +#define INCLUDE_REGC_GROUP 1 + +#define INCLUDE_BENCHMARKS 1 + +/* + * Include tests that normally would fail under certain gcc + * optimization levels. + */ +#ifndef INCLUDE_GCC_TEST +# define INCLUDE_GCC_TEST 0 +#endif + + +#define INCLUDE_URI_TEST INCLUDE_MESSAGING_GROUP +#define INCLUDE_MSG_TEST INCLUDE_MESSAGING_GROUP +#define INCLUDE_TXDATA_TEST INCLUDE_MESSAGING_GROUP +#define INCLUDE_TSX_BENCH INCLUDE_MESSAGING_GROUP +#define INCLUDE_UDP_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_LOOP_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_TCP_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_RESOLVE_TEST INCLUDE_TRANSPORT_GROUP +#define INCLUDE_TSX_TEST INCLUDE_TSX_GROUP +#define INCLUDE_INV_OA_TEST INCLUDE_INV_GROUP +#define INCLUDE_REGC_TEST INCLUDE_REGC_GROUP + + +/* The tests */ +int uri_test(void); +int msg_test(void); +int msg_err_test(void); +int txdata_test(void); +int tsx_bench(void); +int transport_udp_test(void); +int transport_loop_test(void); +int transport_tcp_test(void); +int resolve_test(void); +int regc_test(void); + +struct tsx_test_param +{ + int type; + int port; + char *tp_type; +}; + +int tsx_basic_test(struct tsx_test_param *param); +int tsx_uac_test(struct tsx_test_param *param); +int tsx_uas_test(struct tsx_test_param *param); + +/* Transport test helpers (transport_test.c). */ +int generic_transport_test(pjsip_transport *tp); +int transport_send_recv_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + char *target_url, + int *p_usec_rtt); +int transport_rt_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + char *target_url, + int *pkt_lost); +int transport_load_test(char *target_url); + +/* Invite session */ +int inv_offer_answer_test(void); + +/* Test main entry */ +int test_main(void); + +/* Test utilities. */ +void app_perror(const char *msg, pj_status_t status); +int init_msg_logger(void); +int msg_logger_set_enabled(pj_bool_t enabled); +void flush_events(unsigned duration); + + +void report_ival(const char *name, int value, const char *valname, const char *desc); +void report_sval(const char *name, const char* value, const char *valname, const char *desc); + + +/* Settings. */ +extern int log_level; + +#endif /* __TEST_H__ */ diff --git a/pjsip/src/test/transport_loop_test.c b/pjsip/src/test/transport_loop_test.c new file mode 100644 index 00000000..a28b7b07 --- /dev/null +++ b/pjsip/src/test/transport_loop_test.c @@ -0,0 +1,127 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "transport_loop_test.c" + +static int datagram_loop_test() +{ + enum { LOOP = 8 }; + pjsip_transport *loop; + int i, pkt_lost; + pj_sockaddr_in addr; + pj_status_t status; + long ref_cnt; + int rtt[LOOP], min_rtt; + + PJ_LOG(3,(THIS_FILE, "testing datagram loop transport")); + + /* Test acquire transport. */ + status = pjsip_endpt_acquire_transport( endpt, PJSIP_TRANSPORT_LOOP_DGRAM, + &addr, sizeof(addr), NULL, &loop); + if (status != PJ_SUCCESS) { + app_perror(" error: loop transport is not configured", status); + return -20; + } + + /* Get initial reference counter */ + ref_cnt = pj_atomic_get(loop->ref_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<LOOP; ++i) { + status = transport_send_recv_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop, + "sip:bob@130.0.0.1;transport=loop-dgram", + &rtt[i]); + if (status != 0) + return status; + } + + min_rtt = 0xFFFFFFF; + for (i=0; i<LOOP; ++i) + if (rtt[i] < min_rtt) min_rtt = rtt[i]; + + report_ival("loop-rtt-usec", min_rtt, "usec", + "Best Loopback transport round trip time, in microseconds " + "(time from sending request until response is received. " + "Tests were performed on local machine only)"); + + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop, + "sip:bob@130.0.0.1;transport=loop-dgram", + &pkt_lost); + if (status != 0) + return status; + + if (pkt_lost != 0) { + PJ_LOG(3,(THIS_FILE, " error: %d packet(s) was lost", pkt_lost)); + return -40; + } + + /* Put delay. */ + PJ_LOG(3,(THIS_FILE," setting network delay to 10 ms")); + pjsip_loop_set_delay(loop, 10); + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_LOOP_DGRAM, loop, + "sip:bob@130.0.0.1;transport=loop-dgram", + &pkt_lost); + if (status != 0) + return status; + + if (pkt_lost != 0) { + PJ_LOG(3,(THIS_FILE, " error: %d packet(s) was lost", pkt_lost)); + return -50; + } + + /* Restore delay. */ + pjsip_loop_set_delay(loop, 0); + + /* 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. */ + pjsip_transport_dec_ref(loop); + + return 0; +} + +int transport_loop_test(void) +{ + int status; + + status = datagram_loop_test(); + if (status != 0) + return status; + + return 0; +} diff --git a/pjsip/src/test/transport_tcp_test.c b/pjsip/src/test/transport_tcp_test.c new file mode 100644 index 00000000..d012fb0e --- /dev/null +++ b/pjsip/src/test/transport_tcp_test.c @@ -0,0 +1,155 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "transport_tcp_test.c" + + +/* + * TCP transport test. + */ +#if PJ_HAS_TCP +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[PJSIP_MAX_URL_SIZE]; + int 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), + NULL, &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 -40; + + /* Load test */ + if (transport_load_test(url) != 0) + return -60; + + /* Basic transport's send/receive loopback test. */ + for (i=0; i<SEND_RECV_LOOP; ++i) { + status = transport_send_recv_test(PJSIP_TRANSPORT_TCP, tcp, url, &rtt[i]); + + if (status != 0) { + pjsip_transport_dec_ref(tcp); + flush_events(500); + return -72; + } + } + + min_rtt = 0xFFFFFFF; + for (i=0; i<SEND_RECV_LOOP; ++i) + if (rtt[i] < min_rtt) min_rtt = rtt[i]; + + report_ival("tcp-rtt-usec", min_rtt, "usec", + "Best TCP transport round trip time, in microseconds " + "(time from sending request until response is received. " + "Tests were performed on local machine only, and after " + "TCP socket has been established by previous test)"); + + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_TCP, tcp, url, &pkt_lost); + if (status != 0) { + pjsip_transport_dec_ref(tcp); + return status; + } + + if (pkt_lost != 0) + PJ_LOG(3,(THIS_FILE, " note: %d packet(s) was lost", pkt_lost)); + + /* Check again that reference counter is still 1. */ + if (pj_atomic_get(tcp->ref_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; +} +#else /* PJ_HAS_TCP */ +int transport_tcp_test(void) +{ + return 0; +} +#endif /* PJ_HAS_TCP */ diff --git a/pjsip/src/test/transport_test.c b/pjsip/src/test/transport_test.c new file mode 100644 index 00000000..9f19b68f --- /dev/null +++ b/pjsip/src/test/transport_test.c @@ -0,0 +1,760 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "transport_test.c" + +/////////////////////////////////////////////////////////////////////////////// +/* + * Generic testing for transport, to make sure that basic + * attributes have been initialized properly. + */ +int generic_transport_test(pjsip_transport *tp) +{ + PJ_LOG(3,(THIS_FILE, " structure test...")); + + /* Check that local address name is valid. */ + { + struct pj_in_addr addr; + + /* Note: inet_aton() returns non-zero if addr is valid! */ + if (pj_inet_aton(&tp->local_name.host, &addr) != 0) { + if (addr.s_addr==PJ_INADDR_ANY || addr.s_addr==PJ_INADDR_NONE) { + PJ_LOG(3,(THIS_FILE, " Error: invalid address name")); + return -420; + } + } else { + /* It's okay. local_name.host may be a hostname instead of + * IP address. + */ + } + } + + /* Check that port is valid. */ + if (tp->local_name.port <= 0) { + return -430; + } + + /* Check length of address (for now we only check against sockaddr_in). */ + if (tp->addr_len != sizeof(pj_sockaddr_in)) + return -440; + + /* Check type. */ + if (tp->key.type == PJSIP_TRANSPORT_UNSPECIFIED) + return -450; + + /* That's it. */ + return PJ_SUCCESS; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Send/receive test. + * + * This test sends a request to loopback address; as soon as request is + * received, response will be sent, and time is recorded. + * + * The main purpose is to test that the basic transport functionalities works, + * before we continue with more complicated tests. + */ +#define FROM_HDR "Bob <sip:bob@example.com>" +#define CONTACT_HDR "Bob <sip:bob@127.0.0.1>" +#define CALL_ID_HDR "SendRecv-Test" +#define CSEQ_VALUE 100 +#define BODY "Hello World!" + +static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata); + +/* Flag to indicate message has been received + * (or failed to send) + */ +#define NO_STATUS -2 +static int send_status = NO_STATUS; +static int recv_status = NO_STATUS; +static pj_timestamp my_send_time, my_recv_time; + +/* Module to receive messages for this test. */ +static pjsip_module my_module = +{ + NULL, NULL, /* prev and next */ + { "Transport-Test", 14}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &my_on_rx_request, /* on_rx_request() */ + &my_on_rx_response, /* on_rx_response() */ + NULL, /* on_tsx_state() */ +}; + + +static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata) +{ + /* Check that this is our request. */ + if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) { + /* It is! */ + /* Send response. */ + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + pj_status_t status; + + status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) { + recv_status = status; + return PJ_TRUE; + } + status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + recv_status = status; + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + recv_status = status; + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + return PJ_TRUE; + } + + /* Not ours. */ + return PJ_FALSE; +} + +static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata) +{ + if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) { + pj_get_timestamp(&my_recv_time); + recv_status = PJ_SUCCESS; + return PJ_TRUE; + } + return PJ_FALSE; +} + +/* Transport callback. */ +static void send_msg_callback(pjsip_send_state *stateless_data, + pj_ssize_t sent, pj_bool_t *cont) +{ + if (sent < 1) { + /* Obtain the error code. */ + send_status = -sent; + } else { + send_status = PJ_SUCCESS; + } + + /* Don't want to continue. */ + *cont = PJ_FALSE; +} + + +/* Test that we receive loopback message. */ +int transport_send_recv_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + char *target_url, + int *p_usec_rtt) +{ + pj_bool_t msg_log_enabled; + pj_status_t status; + pj_str_t target, from, to, contact, call_id, body; + pjsip_method method; + pjsip_tx_data *tdata; + pj_time_val timeout; + + PJ_LOG(3,(THIS_FILE, " single message round-trip test...")); + + /* Register out test module to receive the message (if necessary). */ + if (my_module.id == -1) { + status = pjsip_endpt_register_module( endpt, &my_module ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to register module", status); + return -500; + } + } + + /* Disable message logging. */ + msg_log_enabled = msg_logger_set_enabled(0); + + /* Create a request message. */ + target = pj_str(target_url); + from = pj_str(FROM_HDR); + to = pj_str(target_url); + contact = pj_str(CONTACT_HDR); + call_id = pj_str(CALL_ID_HDR); + body = pj_str(BODY); + + pjsip_method_set(&method, PJSIP_OPTIONS_METHOD); + status = pjsip_endpt_create_request( endpt, &method, &target, &from, &to, + &contact, &call_id, CSEQ_VALUE, + &body, &tdata ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -510; + } + + /* Reset statuses */ + send_status = recv_status = NO_STATUS; + + /* Start time. */ + 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) { + /* Immediate error! */ + pjsip_tx_data_dec_ref(tdata); + send_status = status; + } + + /* Set the timeout (2 seconds from now) */ + pj_gettimeofday(&timeout); + timeout.sec += 2; + + /* Loop handling events until we get status */ + do { + pj_time_val now; + pj_time_val poll_interval = { 0, 10 }; + + pj_gettimeofday(&now); + if (PJ_TIME_VAL_GTE(now, timeout)) { + PJ_LOG(3,(THIS_FILE, " error: timeout in send/recv test")); + status = -540; + goto on_return; + } + + if (send_status!=NO_STATUS && send_status!=PJ_SUCCESS) { + app_perror(" error sending message", send_status); + status = -550; + goto on_return; + } + + if (recv_status!=NO_STATUS && recv_status!=PJ_SUCCESS) { + app_perror(" error receiving message", recv_status); + status = -560; + goto on_return; + } + + if (send_status!=NO_STATUS && recv_status!=NO_STATUS) { + /* Success! */ + break; + } + + pjsip_endpt_handle_events(endpt, &poll_interval); + + } while (1); + + 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. */ + msg_logger_set_enabled(msg_log_enabled); + + status = PJ_SUCCESS; + +on_return: + return status; +} + + +/////////////////////////////////////////////////////////////////////////////// +/* + * Multithreaded round-trip test + * + * This test will spawn multiple threads, each of them send a request. As soon + * as request is received, response will be sent, and time is recorded. + * + * The main purpose of this test is to ensure there's no crash when multiple + * threads are sending/receiving messages. + * + */ +static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata); +static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata); + +static pjsip_module rt_module = +{ + NULL, NULL, /* prev and next */ + { "Transport-RT-Test", 17}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &rt_on_rx_request, /* on_rx_request() */ + &rt_on_rx_response, /* on_rx_response() */ + NULL, /* tsx_handler() */ +}; + +static struct +{ + pj_thread_t *thread; + pj_timestamp send_time; + pj_timestamp total_rt_time; + int sent_request_count, recv_response_count; + pj_str_t call_id; + pj_timer_entry timeout_timer; + pj_timer_entry tx_timer; + pj_mutex_t *mutex; +} rt_test_data[16]; + +static char rt_target_uri[64]; +static pj_bool_t rt_stop; +static pj_str_t rt_call_id; + +static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata) +{ + if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) { + pjsip_tx_data *tdata; + pjsip_response_addr res_addr; + pj_status_t status; + + status = pjsip_endpt_create_response( endpt, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error creating response", status); + return PJ_TRUE; + } + status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr); + if (status != PJ_SUCCESS) { + app_perror(" error in get response address", status); + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error sending response", status); + pjsip_tx_data_dec_ref(tdata); + return PJ_TRUE; + } + return PJ_TRUE; + + } + return PJ_FALSE; +} + +static pj_status_t rt_send_request(int thread_id) +{ + pj_status_t status; + pj_str_t target, from, to, contact, call_id; + pjsip_tx_data *tdata; + pj_time_val timeout_delay; + + pj_mutex_lock(rt_test_data[thread_id].mutex); + + /* Create a request message. */ + target = pj_str(rt_target_uri); + from = pj_str(FROM_HDR); + to = pj_str(rt_target_uri); + contact = pj_str(CONTACT_HDR); + call_id = rt_test_data[thread_id].call_id; + + status = pjsip_endpt_create_request( endpt, &pjsip_options_method, + &target, &from, &to, + &contact, &call_id, -1, + NULL, &tdata ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + pj_mutex_unlock(rt_test_data[thread_id].mutex); + return -610; + } + + /* Start time. */ + pj_get_timestamp(&rt_test_data[thread_id].send_time); + + /* Send the message (statelessly). */ + status = pjsip_endpt_send_request_stateless( endpt, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + /* Immediate error! */ + app_perror(" error: send request", status); + pjsip_tx_data_dec_ref(tdata); + pj_mutex_unlock(rt_test_data[thread_id].mutex); + return -620; + } + + /* Update counter. */ + rt_test_data[thread_id].sent_request_count++; + + /* Set timeout timer. */ + if (rt_test_data[thread_id].timeout_timer.user_data != NULL) { + pjsip_endpt_cancel_timer(endpt, &rt_test_data[thread_id].timeout_timer); + } + timeout_delay.sec = 100; timeout_delay.msec = 0; + rt_test_data[thread_id].timeout_timer.user_data = (void*)1; + pjsip_endpt_schedule_timer(endpt, &rt_test_data[thread_id].timeout_timer, + &timeout_delay); + + pj_mutex_unlock(rt_test_data[thread_id].mutex); + return PJ_SUCCESS; +} + +static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata) +{ + if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) { + char *pos = pj_strchr(&rdata->msg_info.cid->id, '/')+1; + int thread_id = (*pos - '0'); + pj_timestamp recv_time; + + pj_mutex_lock(rt_test_data[thread_id].mutex); + + /* Stop timer. */ + pjsip_endpt_cancel_timer(endpt, &rt_test_data[thread_id].timeout_timer); + + /* Update counter and end-time. */ + rt_test_data[thread_id].recv_response_count++; + pj_get_timestamp(&recv_time); + + pj_sub_timestamp(&recv_time, &rt_test_data[thread_id].send_time); + pj_add_timestamp(&rt_test_data[thread_id].total_rt_time, &recv_time); + + if (!rt_stop) { + pj_time_val tx_delay = { 0, 0 }; + pj_assert(rt_test_data[thread_id].tx_timer.user_data == NULL); + rt_test_data[thread_id].tx_timer.user_data = (void*)1; + pjsip_endpt_schedule_timer(endpt, &rt_test_data[thread_id].tx_timer, + &tx_delay); + } + + pj_mutex_unlock(rt_test_data[thread_id].mutex); + + return PJ_TRUE; + } + return PJ_FALSE; +} + +static void rt_timeout_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry ) +{ + pj_mutex_lock(rt_test_data[entry->id].mutex); + + PJ_UNUSED_ARG(timer_heap); + PJ_LOG(3,(THIS_FILE, " timeout waiting for response")); + rt_test_data[entry->id].timeout_timer.user_data = NULL; + + if (rt_test_data[entry->id].tx_timer.user_data == NULL) { + pj_time_val delay = { 0, 0 }; + rt_test_data[entry->id].tx_timer.user_data = (void*)1; + pjsip_endpt_schedule_timer(endpt, &rt_test_data[entry->id].tx_timer, + &delay); + } + + pj_mutex_unlock(rt_test_data[entry->id].mutex); +} + +static void rt_tx_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry ) +{ + pj_mutex_lock(rt_test_data[entry->id].mutex); + + PJ_UNUSED_ARG(timer_heap); + pj_assert(rt_test_data[entry->id].tx_timer.user_data != NULL); + rt_test_data[entry->id].tx_timer.user_data = NULL; + rt_send_request(entry->id); + + pj_mutex_unlock(rt_test_data[entry->id].mutex); +} + + +static int rt_worker_thread(void *arg) +{ + int i; + pj_time_val poll_delay = { 0, 10 }; + + /* Sleep to allow main threads to run. */ + pj_thread_sleep(10); + + while (!rt_stop) { + pjsip_endpt_handle_events(endpt, &poll_delay); + } + + /* Exhaust responses. */ + for (i=0; i<100; ++i) + pjsip_endpt_handle_events(endpt, &poll_delay); + + return 0; +} + +int transport_rt_test( pjsip_transport_type_e tp_type, + pjsip_transport *ref_tp, + char *target_url, + int *lost) +{ + enum { THREADS = 4, INTERVAL = 10 }; + int i; + pj_status_t status; + pj_pool_t *pool; + pj_bool_t logger_enabled; + + pj_timestamp zero_time, total_time; + unsigned usec_rt; + 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..", + INTERVAL)); + + /* Make sure msg logger is disabled. */ + logger_enabled = msg_logger_set_enabled(0); + + /* Register module (if not yet registered) */ + if (rt_module.id == -1) { + status = pjsip_endpt_register_module( endpt, &rt_module ); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to register module", status); + return -600; + } + } + + /* Create pool for this test. */ + pool = pjsip_endpt_create_pool(endpt, NULL, 4000, 4000); + if (!pool) + return -610; + + /* Initialize static test data. */ + pj_ansi_strcpy(rt_target_uri, target_url); + rt_call_id = pj_str("RT-Call-Id/"); + rt_stop = PJ_FALSE; + + /* Initialize thread data. */ + for (i=0; i<THREADS; ++i) { + char buf[1]; + pj_str_t str_id = { buf, 1 }; + + pj_bzero(&rt_test_data[i], sizeof(rt_test_data[i])); + + /* Init timer entry */ + rt_test_data[i].tx_timer.id = i; + rt_test_data[i].tx_timer.cb = &rt_tx_timer; + rt_test_data[i].timeout_timer.id = i; + rt_test_data[i].timeout_timer.cb = &rt_timeout_timer; + + /* Generate Call-ID for each thread. */ + rt_test_data[i].call_id.ptr = (char*) pj_pool_alloc(pool, rt_call_id.slen+1); + pj_strcpy(&rt_test_data[i].call_id, &rt_call_id); + buf[0] = '0' + i; + pj_strcat(&rt_test_data[i].call_id, &str_id); + + /* Init mutex. */ + status = pj_mutex_create_recursive(pool, "rt", &rt_test_data[i].mutex); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create mutex", status); + return -615; + } + + /* Create thread, suspended. */ + status = pj_thread_create(pool, "rttest%p", &rt_worker_thread, (void*)(long)i, 0, + PJ_THREAD_SUSPENDED, &rt_test_data[i].thread); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create thread", status); + return -620; + } + } + + /* Start threads! */ + for (i=0; i<THREADS; ++i) { + pj_time_val delay = {0,0}; + pj_thread_resume(rt_test_data[i].thread); + + /* Schedule first message transmissions. */ + rt_test_data[i].tx_timer.user_data = (void*)1; + pjsip_endpt_schedule_timer(endpt, &rt_test_data[i].tx_timer, &delay); + } + + /* Sleep for some time. */ + pj_thread_sleep(INTERVAL * 1000); + + /* Signal thread to stop. */ + rt_stop = PJ_TRUE; + + /* Wait threads to complete. */ + for (i=0; i<THREADS; ++i) { + pj_thread_join(rt_test_data[i].thread); + pj_thread_destroy(rt_test_data[i].thread); + } + + /* Destroy rt_test_data */ + for (i=0; i<THREADS; ++i) { + pj_mutex_destroy(rt_test_data[i].mutex); + pjsip_endpt_cancel_timer(endpt, &rt_test_data[i].timeout_timer); + } + + /* Gather statistics. */ + pj_bzero(&total_time, sizeof(total_time)); + pj_bzero(&zero_time, sizeof(zero_time)); + usec_rt = total_sent = total_recv = 0; + for (i=0; i<THREADS; ++i) { + total_sent += rt_test_data[i].sent_request_count; + total_recv += rt_test_data[i].recv_response_count; + pj_add_timestamp(&total_time, &rt_test_data[i].total_rt_time); + } + + /* Display statistics. */ + if (total_recv) + total_time.u64 = total_time.u64/total_recv; + else + total_time.u64 = 0; + usec_rt = pj_elapsed_usec(&zero_time, &total_time); + PJ_LOG(3,(THIS_FILE, " done.")); + PJ_LOG(3,(THIS_FILE, " total %d messages sent", total_sent)); + PJ_LOG(3,(THIS_FILE, " average round-trip=%d usec", usec_rt)); + + pjsip_endpt_release_pool(endpt, pool); + + *lost = total_sent-total_recv; + + /* Flush events. */ + flush_events(500); + + /* Restore msg logger. */ + msg_logger_set_enabled(logger_enabled); + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +/* + * Transport load testing + */ +static pj_bool_t load_on_rx_request(pjsip_rx_data *rdata); + +static struct mod_load_test +{ + pjsip_module mod; + pj_int32_t next_seq; + pj_bool_t err; +} mod_load = +{ + { + NULL, NULL, /* prev and next */ + { "mod-load-test", 13}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TSX_LAYER-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &load_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* tsx_handler() */ + } +}; + + +static pj_bool_t load_on_rx_request(pjsip_rx_data *rdata) +{ + if (rdata->msg_info.cseq->cseq != mod_load.next_seq) { + PJ_LOG(1,("THIS_FILE", " err: expecting cseq %u, got %u", + mod_load.next_seq, rdata->msg_info.cseq->cseq)); + mod_load.err = PJ_TRUE; + mod_load.next_seq = rdata->msg_info.cseq->cseq + 1; + } else + mod_load.next_seq++; + return PJ_TRUE; +} + +int transport_load_test(char *target_url) +{ + enum { COUNT = 2000 }; + unsigned i; + pj_status_t status = PJ_SUCCESS; + + /* exhaust packets */ + do { + pj_time_val delay = {1, 0}; + i = 0; + pjsip_endpt_handle_events2(endpt, &delay, &i); + } while (i != 0); + + PJ_LOG(3,(THIS_FILE, " transport load test...")); + + if (mod_load.mod.id == -1) { + status = pjsip_endpt_register_module( endpt, &mod_load.mod); + if (status != PJ_SUCCESS) { + app_perror("error registering module", status); + return -1; + } + } + mod_load.err = PJ_FALSE; + mod_load.next_seq = 0; + + for (i=0; i<COUNT && !mod_load.err; ++i) { + pj_str_t target, from, call_id; + pjsip_tx_data *tdata; + + target = pj_str(target_url); + from = pj_str("<sip:user@host>"); + call_id = pj_str("thecallid"); + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &target, &from, + &target, &from, &call_id, + i, NULL, &tdata ); + if (status != PJ_SUCCESS) { + app_perror("error creating request", status); + goto on_return; + } + + status = pjsip_endpt_send_request_stateless(endpt, tdata, NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror("error sending request", status); + goto on_return; + } + } + + do { + pj_time_val delay = {1, 0}; + i = 0; + pjsip_endpt_handle_events2(endpt, &delay, &i); + } while (i != 0); + + if (mod_load.next_seq != COUNT) { + PJ_LOG(1,("THIS_FILE", " err: expecting %u msg, got only %u", + COUNT, mod_load.next_seq)); + status = -2; + goto on_return; + } + +on_return: + if (mod_load.mod.id != -1) { + pjsip_endpt_unregister_module( endpt, &mod_load.mod); + mod_load.mod.id = -1; + } + if (status != PJ_SUCCESS || mod_load.err) { + return -2; + } + PJ_LOG(3,(THIS_FILE, " success")); + return 0; +} + + diff --git a/pjsip/src/test/transport_udp_test.c b/pjsip/src/test/transport_udp_test.c new file mode 100644 index 00000000..81e8d69c --- /dev/null +++ b/pjsip/src/test/transport_udp_test.c @@ -0,0 +1,128 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "transport_udp_test.c" + + +/* + * UDP transport test. + */ +int transport_udp_test(void) +{ + enum { SEND_RECV_LOOP = 8 }; + pjsip_transport *udp_tp, *tp; + pj_sockaddr_in addr, rem_addr; + pj_str_t s; + pj_status_t status; + int rtt[SEND_RECV_LOOP], min_rtt; + int i, pkt_lost; + + pj_sockaddr_in_init(&addr, NULL, TEST_UDP_PORT); + + /* Start UDP transport. */ + status = pjsip_udp_transport_start( endpt, &addr, NULL, 1, &udp_tp); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to start UDP transport", status); + return -10; + } + + /* UDP transport must have initial reference counter set to 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -20; + + /* Test basic transport attributes */ + status = generic_transport_test(udp_tp); + if (status != PJ_SUCCESS) + return status; + + /* Test that transport manager is returning the correct + * transport. + */ + pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "1.1.1.1"), 80); + status = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, + &rem_addr, sizeof(rem_addr), + NULL, &tp); + if (status != PJ_SUCCESS) + return -50; + if (tp != udp_tp) + return -60; + + /* pjsip_endpt_acquire_transport() adds reference, so we need + * to decrement it. + */ + pjsip_transport_dec_ref(tp); + + /* Check again that reference counter is 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -70; + + /* Basic transport's send/receive loopback test. */ + pj_sockaddr_in_init(&rem_addr, pj_cstr(&s, "127.0.0.1"), TEST_UDP_PORT); + for (i=0; i<SEND_RECV_LOOP; ++i) { + status = transport_send_recv_test(PJSIP_TRANSPORT_UDP, tp, + "sip:alice@127.0.0.1:"TEST_UDP_PORT_STR, + &rtt[i]); + if (status != 0) + return status; + } + + min_rtt = 0xFFFFFFF; + for (i=0; i<SEND_RECV_LOOP; ++i) + if (rtt[i] < min_rtt) min_rtt = rtt[i]; + + report_ival("udp-rtt-usec", min_rtt, "usec", + "Best UDP transport round trip time, in microseconds " + "(time from sending request until response is received. " + "Tests were performed on local machine only)"); + + + /* Multi-threaded round-trip test. */ + status = transport_rt_test(PJSIP_TRANSPORT_UDP, tp, + "sip:alice@127.0.0.1:"TEST_UDP_PORT_STR, + &pkt_lost); + if (status != 0) + return status; + + if (pkt_lost != 0) + PJ_LOG(3,(THIS_FILE, " note: %d packet(s) was lost", pkt_lost)); + + /* Check again that reference counter is 1. */ + if (pj_atomic_get(udp_tp->ref_cnt) != 1) + return -80; + + /* Destroy this transport. */ + pjsip_transport_dec_ref(udp_tp); + + /* Force destroy this transport. */ + status = pjsip_transport_destroy(udp_tp); + if (status != PJ_SUCCESS) + return -90; + + /* Flush events. */ + PJ_LOG(3,(THIS_FILE, " Flushing events, 1 second...")); + flush_events(1000); + + /* Done */ + return 0; +} diff --git a/pjsip/src/test/tsx_basic_test.c b/pjsip/src/test/tsx_basic_test.c new file mode 100644 index 00000000..f030ea06 --- /dev/null +++ b/pjsip/src/test/tsx_basic_test.c @@ -0,0 +1,157 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "tsx_basic_test.c" + +static char TARGET_URI[PJSIP_MAX_URL_SIZE]; +static char FROM_URI[PJSIP_MAX_URL_SIZE]; + + +/* Test transaction layer. */ +static int tsx_layer_test(void) +{ + pj_str_t target, from, tsx_key; + pjsip_tx_data *tdata; + pjsip_transaction *tsx, *found; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " transaction layer test")); + + target = pj_str(TARGET_URI); + from = pj_str(FROM_URI); + + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target, + &from, &target, NULL, NULL, -1, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -110; + } + + status = pjsip_tsx_create_uac(NULL, tdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + return -120; + } + + pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key); + + found = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE); + if (found != tsx) { + return -130; + } + + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + flush_events(500); + + if (pjsip_tx_data_dec_ref(tdata) != PJSIP_EBUFDESTROYED) { + return -140; + } + + return 0; +} + +/* Double terminate test. */ +static int double_terminate(void) +{ + pj_str_t target, from, tsx_key; + pjsip_tx_data *tdata; + pjsip_transaction *tsx; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " double terminate test")); + + target = pj_str(TARGET_URI); + from = pj_str(FROM_URI); + + /* Create request. */ + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target, + &from, &target, NULL, NULL, -1, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -10; + } + + /* Create transaction. */ + status = pjsip_tsx_create_uac(NULL, tdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + return -20; + } + + /* Save transaction key for later. */ + pj_strdup_with_null(tdata->pool, &tsx_key, &tsx->transaction_key); + + /* Add reference to transmit buffer (tsx_send_msg() will dec txdata). */ + pjsip_tx_data_add_ref(tdata); + + /* Send message to start timeout timer. */ + status = pjsip_tsx_send_msg(tsx, NULL); + + /* Terminate transaction. */ + status = pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to terminate transaction", status); + return -30; + } + + tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); + if (tsx) { + /* Terminate transaction again. */ + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to terminate transaction", status); + return -40; + } + pj_mutex_unlock(tsx->mutex); + } + + flush_events(500); + if (pjsip_tx_data_dec_ref(tdata) != PJSIP_EBUFDESTROYED) { + return -50; + } + + return PJ_SUCCESS; +} + +int tsx_basic_test(struct tsx_test_param *param) +{ + int status; + + 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); + + status = tsx_layer_test(); + if (status != 0) + return status; + + status = double_terminate(); + if (status != 0) + return status; + + return 0; +} diff --git a/pjsip/src/test/tsx_bench.c b/pjsip/src/test/tsx_bench.c new file mode 100644 index 00000000..6f217a2b --- /dev/null +++ b/pjsip/src/test/tsx_bench.c @@ -0,0 +1,280 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#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; + pjsip_via_hdr *via; + 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\" <sip:localuser@serviceprovider.com>"); + pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>"); + 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; + } + + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_VIA, + NULL); + + /* Create transaction array */ + tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*)); + + pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user)); + mod_tsx_user.id = -1; + + /* Benchmark */ + elapsed.u64 = 0; + pj_get_timestamp(&t1); + for (i=0; i<working_set; ++i) { + status = pjsip_tsx_create_uac(&mod_tsx_user, request, &tsx[i]); + if (status != PJ_SUCCESS) + goto on_error; + /* Reset branch param */ + via->branch_param.slen = 0; + } + 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; i<working_set; ++i) { + if (tsx[i]) { + pjsip_tsx_terminate(tsx[i], 601); + tsx[i] = NULL; + } + } + pjsip_tx_data_dec_ref(request); + flush_events(2000); + return status; +} + + + +static int uas_tsx_bench(unsigned working_set, pj_timestamp *p_elapsed) +{ + unsigned i; + pjsip_tx_data *request; + pjsip_via_hdr *via; + pjsip_rx_data rdata; + pj_sockaddr_in remote; + pjsip_transaction **tsx; + pj_timestamp t1, t2, elapsed; + char branch_buf[80] = PJSIP_RFC3261_BRANCH_ID "0000000000"; + 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\" <sip:localuser@serviceprovider.com>"); + pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>"); + 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_bzero(&rdata, sizeof(pjsip_rx_data)); + rdata.tp_info.pool = request->pool; + rdata.msg_info.msg = request->msg; + rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL); + rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL); + rdata.msg_info.cid = (pjsip_cid_hdr*) 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), + NULL, &rdata.tp_info.transport); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to get loop transport", status); + return status; + } + + + /* Create transaction array */ + tsx = (pjsip_transaction**) pj_pool_zalloc(request->pool, working_set * sizeof(pj_pool_t*)); + + pj_bzero(&mod_tsx_user, sizeof(mod_tsx_user)); + mod_tsx_user.id = -1; + + + /* Benchmark */ + elapsed.u64 = 0; + pj_get_timestamp(&t1); + for (i=0; i<working_set; ++i) { + via->branch_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; i<working_set; ++i) { + if (tsx[i]) { + pjsip_tsx_terminate(tsx[i], 601); + tsx[i] = NULL; + } + } + pjsip_tx_data_dec_ref(request); + flush_events(2000); + return status; +} + + + +int tsx_bench(void) +{ + enum { WORKING_SET=10000, REPEAT = 4 }; + unsigned i, speed; + pj_timestamp usec[REPEAT], min, freq; + char desc[250]; + int status; + + status = pj_get_timestamp_freq(&freq); + if (status != PJ_SUCCESS) + return status; + + + /* + * Benchmark UAC + */ + PJ_LOG(3,(THIS_FILE, " benchmarking UAC transaction creation:")); + for (i=0; i<REPEAT; ++i) { + PJ_LOG(3,(THIS_FILE, " test %d of %d..", + i+1, REPEAT)); + status = uac_tsx_bench(WORKING_SET, &usec[i]); + if (status != PJ_SUCCESS) + return status; + } + + min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF); + for (i=0; i<REPEAT; ++i) { + if (usec[i].u64 < min.u64) min.u64 = usec[i].u64; + } + + + /* Report time */ + pj_ansi_sprintf(desc, "Time to create %d UAC transactions, in miliseconds", + WORKING_SET); + report_ival("create-uac-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc); + + + /* Write speed */ + speed = (unsigned)(freq.u64 * WORKING_SET / min.u64); + PJ_LOG(3,(THIS_FILE, " UAC created at %d tsx/sec", speed)); + + pj_ansi_sprintf(desc, "Number of UAC transactions that potentially can be created per second " + "with <tt>pjsip_tsx_create_uac()</tt>, 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; i<REPEAT; ++i) { + PJ_LOG(3,(THIS_FILE, " test %d of %d..", + i+1, REPEAT)); + status = uas_tsx_bench(WORKING_SET, &usec[i]); + if (status != PJ_SUCCESS) + return status; + } + + min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF); + for (i=0; i<REPEAT; ++i) { + if (usec[i].u64 < min.u64) min.u64 = usec[i].u64; + } + + + /* Report time */ + pj_ansi_sprintf(desc, "Time to create %d UAS transactions, in miliseconds", + WORKING_SET); + report_ival("create-uas-time", (unsigned)(min.u64 * 1000 / freq.u64), "msec", desc); + + + /* Write speed */ + speed = (unsigned)(freq.u64 * WORKING_SET / min.u64); + PJ_LOG(3,(THIS_FILE, " UAS created at %d tsx/sec", speed)); + + pj_ansi_sprintf(desc, "Number of UAS transactions that potentially can be created per second " + "with <tt>pjsip_tsx_create_uas()</tt>, 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/tsx_uac_test.c b/pjsip/src/test/tsx_uac_test.c new file mode 100644 index 00000000..f725114a --- /dev/null +++ b/pjsip/src/test/tsx_uac_test.c @@ -0,0 +1,1450 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "tsx_uac_test.c" + + +/***************************************************************************** + ** + ** UAC tests. + ** + ** This file performs various tests for UAC transactions. Each test will have + ** a different Via branch param so that message receiver module and + ** transaction user module can identify which test is being carried out. + ** + ** TEST1_BRANCH_ID + ** Perform basic retransmission and timeout test. Message receiver will + ** verify that retransmission is received at correct time. + ** This test verifies the following requirements: + ** - retransmit timer doubles for INVITE + ** - retransmit timer doubles and caps off for non-INVITE + ** - retransmit timer timer is precise + ** - correct timeout and retransmission count + ** Requirements not tested: + ** - retransmit timer only starts after resolving has completed. + ** + ** TEST2_BRANCH_ID + ** Test scenario where resolver is unable to resolve destination host. + ** + ** TEST3_BRANCH_ID + ** Test scenario where transaction is terminated while resolver is still + ** running. + ** + ** TEST4_BRANCH_ID + ** Test scenario where transport failed after several retransmissions. + ** + ** TEST5_BRANCH_ID + ** Test scenario where transaction is terminated by user after several + ** retransmissions. + ** + ** TEST6_BRANCH_ID + ** Test successfull non-INVITE transaction. + ** It tests the following requirements: + ** - transaction correctly moves to COMPLETED state. + ** - retransmission must cease. + ** - tx_data must be maintained until state is terminated. + ** + ** TEST7_BRANCH_ID + ** Test successfull non-INVITE transaction, with provisional response. + ** + ** TEST8_BRANCH_ID + ** Test failed INVITE transaction (e.g. ACK must be received) + ** + ** TEST9_BRANCH_ID + ** Test failed INVITE transaction with provisional response. + ** + ** + ***************************************************************************** + */ + +static char *TEST1_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test1"; +static char *TEST2_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test2"; +static char *TEST3_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test3"; +static char *TEST4_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test4"; +static char *TEST5_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test5"; +static char *TEST6_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test6"; +static char *TEST7_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test7"; +static char *TEST8_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test8"; +static char *TEST9_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAC-Test9"; + +#define TEST1_ALLOWED_DIFF (150) +#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); + +/* UAC transaction user module. */ +static pjsip_module tsx_user = +{ + NULL, NULL, /* prev and next */ + { "Tsx-UAC-User", 12}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + &tsx_user_on_tsx_state, /* on_tsx_state() */ +}; + +/* Module to receive the loop-backed request. */ +static pjsip_module msg_receiver = +{ + NULL, NULL, /* prev and next */ + { "Msg-Receiver", 12}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &msg_receiver_on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +/* Static vars, which will be reset on each test. */ +static int recv_count; +static pj_time_val recv_last; +static pj_bool_t test_complete; + +/* Loop transport instance. */ +static pjsip_transport *loop; + +/* General timer entry to be used by tests. */ +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 + * transaction. It is used to verify that the transaction behaves according + * to the test scenario. + */ +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) +{ + if (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0) { + /* + * Transaction with TEST1_BRANCH_ID should terminate with transaction + * timeout status. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (test_complete == 0) + test_complete = 1; + + /* Test the status code. */ + if (tsx->status_code != PJSIP_SC_TSX_TIMEOUT) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + 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) { + /* + * Transaction with TEST2_BRANCH_ID should terminate with transport error. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Test the status code. */ + if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_TSX_TRANSPORT_ERROR)); + test_complete = -720; + } + + if (test_complete == 0) + test_complete = 1; + } + + } else if (pj_strcmp2(&tsx->branch, TEST3_BRANCH_ID)==0) { + /* + * This test terminates the transaction while resolver is still + * running. + */ + if (tsx->state == PJSIP_TSX_STATE_CALLING) { + + /* Terminate the transaction. */ + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Check if status code is correct. */ + if (tsx->status_code != PJSIP_SC_REQUEST_TERMINATED) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_REQUEST_TERMINATED)); + test_complete = -730; + } + + if (test_complete == 0) + test_complete = 1; + + } + + } else if (pj_strcmp2(&tsx->branch, TEST4_BRANCH_ID)==0) { + /* + * This test simulates transport failure after several + * retransmissions. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Status code must be transport error. */ + if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_TSX_TRANSPORT_ERROR)); + test_complete = -730; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != TEST4_RETRANSMIT_CNT) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, TEST4_RETRANSMIT_CNT)); + test_complete = -731; + } + + if (test_complete == 0) + test_complete = 1; + } + + + } else if (pj_strcmp2(&tsx->branch, TEST5_BRANCH_ID)==0) { + /* + * This test simulates transport failure after several + * retransmissions. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Status code must be PJSIP_SC_REQUEST_TERMINATED. */ + if (tsx->status_code != PJSIP_SC_REQUEST_TERMINATED) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, PJSIP_SC_REQUEST_TERMINATED)); + test_complete = -733; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != TEST5_RETRANSMIT_CNT) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, TEST5_RETRANSMIT_CNT)); + test_complete = -734; + } + + if (test_complete == 0) + test_complete = 1; + } + + + } else if (pj_strcmp2(&tsx->branch, TEST6_BRANCH_ID)==0) { + /* + * Successfull non-INVITE transaction. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Status code must be 202. */ + if (tsx->status_code != 202) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 202)); + test_complete = -736; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, 0)); + test_complete = -737; + } + + /* Must still keep last_tx */ + if (tsx->last_tx == NULL) { + PJ_LOG(3,(THIS_FILE, + " error: transaction lost last_tx")); + test_complete = -738; + } + + if (test_complete == 0) { + test_complete = 1; + pjsip_tsx_terminate(tsx, 202); + } + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Previous state must be COMPLETED. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + test_complete = -7381; + } + + } + + } else if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID)==0) { + /* + * Successfull non-INVITE transaction. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Check prev state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) { + PJ_LOG(3,(THIS_FILE, + " error: prev state is %s instead of %s", + pjsip_tsx_state_str((pjsip_tsx_state_e)e->body.tsx_state.prev_state), + pjsip_tsx_state_str(PJSIP_TSX_STATE_PROCEEDING))); + test_complete = -739; + } + + /* Status code must be 202. */ + if (tsx->status_code != 202) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 202)); + test_complete = -740; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, 0)); + test_complete = -741; + } + + /* Must still keep last_tx */ + if (tsx->last_tx == NULL) { + PJ_LOG(3,(THIS_FILE, + " error: transaction lost last_tx")); + test_complete = -741; + } + + if (test_complete == 0) { + test_complete = 1; + pjsip_tsx_terminate(tsx, 202); + } + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Previous state must be COMPLETED. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + test_complete = -742; + } + + } + + + } else if (pj_strcmp2(&tsx->branch, TEST8_BRANCH_ID)==0) { + /* + * Failed INVITE transaction. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Status code must be 301. */ + if (tsx->status_code != 301) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 301)); + test_complete = -745; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, 0)); + test_complete = -746; + } + + /* Must still keep last_tx */ + if (tsx->last_tx == NULL) { + PJ_LOG(3,(THIS_FILE, + " error: transaction lost last_tx")); + test_complete = -747; + } + + /* last_tx MUST be the INVITE request + * (authorization depends on this behavior) + */ + if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id != + PJSIP_INVITE_METHOD) + { + PJ_LOG(3,(THIS_FILE, + " error: last_tx is not INVITE")); + test_complete = -748; + } + } + else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + test_complete = 1; + + /* Previous state must be COMPLETED. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + test_complete = -750; + } + + /* Status code must be 301. */ + if (tsx->status_code != 301) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 301)); + test_complete = -751; + } + + } + + + } else if (pj_strcmp2(&tsx->branch, TEST9_BRANCH_ID)==0) { + /* + * Failed INVITE transaction with provisional response. + */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Previous state must be PJSIP_TSX_STATE_PROCEEDING. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) { + test_complete = -760; + } + + /* Status code must be 302. */ + if (tsx->status_code != 302) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 302)); + test_complete = -761; + } + + /* Must have correct retransmission count. */ + if (tsx->retransmit_count != 0) { + PJ_LOG(3,(THIS_FILE, + " error: retransmit cnt is %d instead of %d", + tsx->retransmit_count, 0)); + test_complete = -762; + } + + /* Must still keep last_tx */ + if (tsx->last_tx == NULL) { + PJ_LOG(3,(THIS_FILE, + " error: transaction lost last_tx")); + test_complete = -763; + } + + /* last_tx MUST be INVITE. + * (authorization depends on this behavior) + */ + if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id != + PJSIP_INVITE_METHOD) + { + PJ_LOG(3,(THIS_FILE, + " error: last_tx is not INVITE")); + test_complete = -764; + } + + } + else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + test_complete = 1; + + /* Previous state must be COMPLETED. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + test_complete = -767; + } + + /* Status code must be 302. */ + if (tsx->status_code != 302) { + PJ_LOG(3,(THIS_FILE, + " error: status code is %d instead of %d", + tsx->status_code, 302)); + test_complete = -768; + } + + } + + } +} + +/* + * This timer callback is called to send delayed response. + */ +struct response +{ + pjsip_response_addr res_addr; + pjsip_tx_data *tdata; +}; + +static void send_response_callback( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + struct response *r = (struct response*) entry->user_data; + pjsip_transport *tp = r->res_addr.transport; + + pjsip_endpt_send_response(endpt, &r->res_addr, r->tdata, NULL, NULL); + if (tp) + pjsip_transport_dec_ref(tp); +} + +/* Timer callback to terminate a transaction. */ +static void terminate_tsx_callback( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + 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) { + pjsip_tsx_terminate(tsx, status_code); + } +} + + +#define DIFF(a,b) ((a<b) ? (b-a) : (a-b)) + +/* + * This is the handler to receive message for this test. It is used to + * control and verify the behavior of the message transmitted by the + * transaction. + */ +static pj_bool_t msg_receiver_on_rx_request(pjsip_rx_data *rdata) +{ + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST1_BRANCH_ID) == 0) { + /* + * The TEST1_BRANCH_ID test performs the verifications for transaction + * retransmission mechanism. It will not answer the incoming request + * with any response. + */ + pjsip_msg *msg = rdata->msg_info.msg; + + PJ_LOG(4,(THIS_FILE, " received request")); + + /* Only wants to take INVITE or OPTIONS method. */ + if (msg->line.req.method.id != PJSIP_INVITE_METHOD && + msg->line.req.method.id != PJSIP_OPTIONS_METHOD) + { + PJ_LOG(3,(THIS_FILE, " error: received unexpected method %.*s", + msg->line.req.method.name.slen, + msg->line.req.method.name.ptr)); + test_complete = -600; + return PJ_TRUE; + } + + if (recv_count == 0) { + recv_count++; + //pj_gettimeofday(&recv_last); + recv_last = rdata->pkt_info.timestamp; + } else { + pj_time_val now; + unsigned msec_expected, msec_elapsed; + int max_received; + + //pj_gettimeofday(&now); + now = rdata->pkt_info.timestamp; + PJ_TIME_VAL_SUB(now, recv_last); + msec_elapsed = now.sec*1000 + now.msec; + + ++recv_count; + msec_expected = (1<<(recv_count-2))*pjsip_cfg()->tsx.t1; + + if (msg->line.req.method.id != PJSIP_INVITE_METHOD) { + if (msec_expected > pjsip_cfg()->tsx.t2) + msec_expected = pjsip_cfg()->tsx.t2; + max_received = 11; + } else { + max_received = 7; + } + + if (DIFF(msec_expected, msec_elapsed) > TEST1_ALLOWED_DIFF) { + PJ_LOG(3,(THIS_FILE, + " error: expecting retransmission no. %d in %d " + "ms, received in %d ms", + recv_count-1, msec_expected, msec_elapsed)); + test_complete = -610; + } + + + if (recv_count > max_received) { + PJ_LOG(3,(THIS_FILE, + " error: too many messages (%d) received", + recv_count)); + test_complete = -620; + } + + //pj_gettimeofday(&recv_last); + recv_last = rdata->pkt_info.timestamp; + } + return PJ_TRUE; + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST4_BRANCH_ID) == 0) { + /* + * The TEST4_BRANCH_ID test simulates transport failure after several + * retransmissions. + */ + recv_count++; + + if (recv_count == TEST4_RETRANSMIT_CNT) { + /* Simulate transport failure. */ + pjsip_loop_set_failure(loop, 2, NULL); + + } else if (recv_count > TEST4_RETRANSMIT_CNT) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -631; + } + + return PJ_TRUE; + + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST5_BRANCH_ID) == 0) { + /* + * The TEST5_BRANCH_ID test simulates user terminating the transaction + * after several retransmissions. + */ + recv_count++; + + if (recv_count == TEST5_RETRANSMIT_CNT+1) { + pj_str_t key; + pjsip_transaction *tsx; + + pjsip_tsx_create_key( rdata->tp_info.pool, &key, PJSIP_ROLE_UAC, + &rdata->msg_info.msg->line.req.method, rdata); + tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE); + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + pj_mutex_unlock(tsx->mutex); + } else { + PJ_LOG(3,(THIS_FILE, " error: uac transaction not found!")); + test_complete = -633; + } + + } else if (recv_count > TEST5_RETRANSMIT_CNT+1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -634; + } + + return PJ_TRUE; + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST6_BRANCH_ID) == 0) { + /* + * The TEST6_BRANCH_ID test successfull non-INVITE transaction. + */ + pj_status_t status; + + recv_count++; + + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -635; + } + + status = pjsip_endpt_respond_stateless(endpt, rdata, 202, NULL, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to send response", status); + test_complete = -636; + } + + return PJ_TRUE; + + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST7_BRANCH_ID) == 0) { + /* + * The TEST7_BRANCH_ID test successfull non-INVITE transaction + * with provisional response. + */ + pj_status_t status; + pjsip_response_addr res_addr; + struct response *r; + pjsip_tx_data *tdata; + pj_time_val delay = { 2, 0 }; + + recv_count++; + + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -640; + return PJ_TRUE; + } + + /* Respond with provisional response */ + status = pjsip_endpt_create_response(endpt, rdata, 100, NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, + NULL, NULL); + pj_assert(status == PJ_SUCCESS); + + /* Create the final response. */ + status = pjsip_endpt_create_response(endpt, rdata, 202, NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + /* Schedule sending final response in couple of of secs. */ + r = PJ_POOL_ALLOC_T(tdata->pool, struct response); + r->res_addr = res_addr; + r->tdata = tdata; + if (r->res_addr.transport) + pjsip_transport_add_ref(r->res_addr.transport); + + timer.entry.cb = &send_response_callback; + timer.entry.user_data = r; + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); + + return PJ_TRUE; + + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST8_BRANCH_ID) == 0) { + /* + * The TEST8_BRANCH_ID test failed INVITE transaction. + */ + pjsip_method *method; + pj_status_t status; + + method = &rdata->msg_info.msg->line.req.method; + + recv_count++; + + if (method->id == PJSIP_INVITE_METHOD) { + + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -635; + } + + status = pjsip_endpt_respond_stateless(endpt, rdata, 301, NULL, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to send response", status); + test_complete = -636; + } + + } else if (method->id == PJSIP_ACK_METHOD) { + + if (recv_count == 2) { + pj_str_t key; + pj_time_val delay = { 5, 0 }; + + /* Schedule timer to destroy transaction after 5 seconds. + * This is to make sure that transaction does not + * retransmit ACK. + */ + pjsip_tsx_create_key(rdata->tp_info.pool, &key, + PJSIP_ROLE_UAC, &pjsip_invite_method, + rdata); + + pj_strcpy(&timer.tsx_key, &key); + timer.entry.id = 301; + timer.entry.cb = &terminate_tsx_callback; + + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); + } + + if (recv_count > 2) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -638; + } + + + } else { + PJ_LOG(3,(THIS_FILE," error: not expecting %s", + pjsip_rx_data_get_info(rdata))); + test_complete = -639; + + } + + + } else + if (pj_strcmp2(&rdata->msg_info.via->branch_param, TEST9_BRANCH_ID) == 0) { + /* + * The TEST9_BRANCH_ID test failed INVITE transaction with + * provisional response. + */ + pjsip_method *method; + pj_status_t status; + + method = &rdata->msg_info.msg->line.req.method; + + recv_count++; + + if (method->id == PJSIP_INVITE_METHOD) { + + pjsip_response_addr res_addr; + struct response *r; + pjsip_tx_data *tdata; + pj_time_val delay = { 2, 0 }; + + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -650; + return PJ_TRUE; + } + + /* Respond with provisional response */ + status = pjsip_endpt_create_response(endpt, rdata, 100, NULL, + &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_endpt_send_response(endpt, &res_addr, tdata, + NULL, NULL); + pj_assert(status == PJ_SUCCESS); + + /* Create the final response. */ + status = pjsip_endpt_create_response(endpt, rdata, 302, NULL, + &tdata); + pj_assert(status == PJ_SUCCESS); + + /* Schedule sending final response in couple of of secs. */ + r = PJ_POOL_ALLOC_T(tdata->pool, struct response); + r->res_addr = res_addr; + r->tdata = tdata; + if (r->res_addr.transport) + pjsip_transport_add_ref(r->res_addr.transport); + + 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) { + + if (recv_count == 2) { + pj_str_t key; + pj_time_val delay = { 5, 0 }; + + /* Schedule timer to destroy transaction after 5 seconds. + * This is to make sure that transaction does not + * retransmit ACK. + */ + pjsip_tsx_create_key(rdata->tp_info.pool, &key, + PJSIP_ROLE_UAC, &pjsip_invite_method, + rdata); + + pj_strcpy(&timer.tsx_key, &key); + timer.entry.id = 302; + timer.entry.cb = &terminate_tsx_callback; + + pjsip_endpt_schedule_timer(endpt, &timer.entry, &delay); + } + + if (recv_count > 2) { + PJ_LOG(3,(THIS_FILE," error: not expecting %d-th packet!", + recv_count)); + test_complete = -638; + } + + + } else { + PJ_LOG(3,(THIS_FILE," error: not expecting %s", + pjsip_rx_data_get_info(rdata))); + test_complete = -639; + + } + + return PJ_TRUE; + + } + + return PJ_FALSE; +} + +/* + * The generic test framework, used by most of the tests. + */ +static int perform_tsx_test(int dummy, char *target_uri, char *from_uri, + char *branch_param, int test_time, + const pjsip_method *method) +{ + pjsip_tx_data *tdata; + pjsip_transaction *tsx; + pj_str_t target, from, tsx_key; + pjsip_via_hdr *via; + pj_time_val timeout; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, + " please standby, this will take at most %d seconds..", + test_time)); + + /* Reset test. */ + recv_count = 0; + test_complete = 0; + + /* Init headers. */ + target = pj_str(target_uri); + from = pj_str(from_uri); + + /* Create request. */ + status = pjsip_endpt_create_request( endpt, method, &target, + &from, &target, NULL, NULL, -1, + NULL, &tdata); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to create request", status); + return -100; + } + + /* Set the branch param for test 1. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param = pj_str(branch_param); + + /* Add additional reference to tdata to prevent transaction from + * deleting it. + */ + pjsip_tx_data_add_ref(tdata); + + /* Create transaction. */ + status = pjsip_tsx_create_uac( &tsx_user, tdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to create UAC transaction", status); + pjsip_tx_data_dec_ref(tdata); + return -110; + } + + /* Get transaction key. */ + pj_strdup(tdata->pool, &tsx_key, &tsx->transaction_key); + + /* Send the message. */ + status = pjsip_tsx_send_msg(tsx, NULL); + // Ignore send result. Some tests do deliberately triggers error + // when sending message. + if (status != PJ_SUCCESS) { + // app_perror(" Error: unable to send request", status); + pjsip_tx_data_dec_ref(tdata); + // return -120; + } + + + /* Set test completion time. */ + pj_gettimeofday(&timeout); + timeout.sec += test_time; + + /* Wait until test complete. */ + while (!test_complete) { + pj_time_val now, poll_delay = {0, 10}; + + pjsip_endpt_handle_events(endpt, &poll_delay); + + pj_gettimeofday(&now); + if (now.sec > timeout.sec) { + PJ_LOG(3,(THIS_FILE, " Error: test has timed out")); + pjsip_tx_data_dec_ref(tdata); + return -130; + } + } + + if (test_complete < 0) { + tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + pj_mutex_unlock(tsx->mutex); + flush_events(1000); + } + pjsip_tx_data_dec_ref(tdata); + return test_complete; + + } 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) { + PJ_LOG(3,(THIS_FILE, " Error: transaction has not been destroyed")); + pjsip_tx_data_dec_ref(tdata); + return -140; + } + + /* Check tdata reference counter. */ + if (pj_atomic_get(tdata->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " Error: tdata reference counter is %d", + pj_atomic_get(tdata->ref_cnt))); + pjsip_tx_data_dec_ref(tdata); + return -150; + } + + /* Destroy txdata */ + pjsip_tx_data_dec_ref(tdata); + + return PJ_SUCCESS; +} + +/***************************************************************************** + ** + ** TEST1_BRANCH_ID: UAC basic retransmission and timeout test. + ** + ** This will test the retransmission of the UAC transaction. Remote will not + ** answer the transaction, so the transaction should fail. The Via branch prm + ** TEST1_BRANCH_ID will be used for this test. + ** + ***************************************************************************** + */ +static int tsx_uac_retransmit_test(void) +{ + int status, enabled; + int i; + struct { + const pjsip_method *method; + unsigned delay; + } sub_test[] = + { + { &pjsip_invite_method, 0}, + { &pjsip_invite_method, TEST1_ALLOWED_DIFF*2}, + { &pjsip_options_method, 0}, + { &pjsip_options_method, TEST1_ALLOWED_DIFF*2} + }; + + PJ_LOG(3,(THIS_FILE, " test1: basic uac retransmit and timeout test")); + + + /* For this test. message printing shound be disabled because it makes + * incorrect timing. + */ + enabled = msg_logger_set_enabled(0); + + for (i=0; i<(int)PJ_ARRAY_SIZE(sub_test); ++i) { + + PJ_LOG(3,(THIS_FILE, + " variant %c: %s with %d ms network delay", + ('a' + i), + sub_test[i].method->name.ptr, + sub_test[i].delay)); + + /* Configure transport */ + pjsip_loop_set_failure(loop, 0, NULL); + pjsip_loop_set_recv_delay(loop, sub_test[i].delay, NULL); + + /* Do the test. */ + status = perform_tsx_test(-500, TARGET_URI, FROM_URI, + TEST1_BRANCH_ID, + 35, sub_test[i].method); + if (status != 0) + break; + } + + /* Restore transport. */ + pjsip_loop_set_recv_delay(loop, 0, NULL); + + /* Restore msg logger. */ + msg_logger_set_enabled(enabled); + + /* Done. */ + return status; +} + +/***************************************************************************** + ** + ** TEST2_BRANCH_ID: UAC resolve error test. + ** + ** Test the scenario where destination host is unresolvable. There are + ** two variants: + ** (a) resolver returns immediate error + ** (b) resolver returns error via the callback. + ** + ***************************************************************************** + */ +static int tsx_resolve_error_test(void) +{ + int status; + + PJ_LOG(3,(THIS_FILE, " test2: resolve error test")); + + /* + * Variant (a): immediate resolve error. + */ + PJ_LOG(3,(THIS_FILE, " variant a: immediate resolving error")); + + status = perform_tsx_test(-800, + "sip:bob@unresolved-host", + FROM_URI, TEST2_BRANCH_ID, 20, + &pjsip_options_method); + if (status != 0) + return status; + + /* + * Variant (b): error via callback. + */ + PJ_LOG(3,(THIS_FILE, " variant b: error via callback")); + + /* 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, 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); + } + + return status; +} + + +/***************************************************************************** + ** + ** TEST3_BRANCH_ID: UAC terminate while resolving test. + ** + ** Terminate the transaction while resolver is still running. + ** + ***************************************************************************** + */ +static int tsx_terminate_resolving_test(void) +{ + unsigned prev_delay; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " test3: terminate while resolving test")); + + /* Configure transport delay. */ + pjsip_loop_set_send_callback_delay(loop, 100, &prev_delay); + + /* Start the test. */ + status = perform_tsx_test(-900, TARGET_URI, FROM_URI, + TEST3_BRANCH_ID, 2, &pjsip_options_method); + + /* Restore delay. */ + pjsip_loop_set_send_callback_delay(loop, prev_delay, NULL); + + return status; +} + + +/***************************************************************************** + ** + ** TEST4_BRANCH_ID: Transport failed after several retransmissions + ** + ** There are two variants of this test: (a) failure occurs immediately when + ** transaction calls pjsip_transport_send() or (b) failure is reported via + ** transport callback. + ** + ***************************************************************************** + */ +static int tsx_retransmit_fail_test(void) +{ + int i; + unsigned delay[] = {0, 10}; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, + " test4: transport fails after several retransmissions test")); + + + for (i=0; i<(int)PJ_ARRAY_SIZE(delay); ++i) { + + PJ_LOG(3,(THIS_FILE, + " variant %c: transport delay %d ms", ('a'+i), delay[i])); + + /* Configure transport delay. */ + pjsip_loop_set_send_callback_delay(loop, delay[i], NULL); + + /* Restore transport failure mode. */ + pjsip_loop_set_failure(loop, 0, 0); + + /* Start the test. */ + status = perform_tsx_test(-1000, TARGET_URI, FROM_URI, + TEST4_BRANCH_ID, 6, &pjsip_options_method); + + if (status != 0) + break; + + } + + /* Restore delay. */ + pjsip_loop_set_send_callback_delay(loop, 0, NULL); + + /* Restore transport failure mode. */ + pjsip_loop_set_failure(loop, 0, 0); + + return status; +} + + +/***************************************************************************** + ** + ** TEST5_BRANCH_ID: Terminate transaction after several retransmissions + ** + ***************************************************************************** + */ +static int tsx_terminate_after_retransmit_test(void) +{ + int status; + + PJ_LOG(3,(THIS_FILE, " test5: terminate after retransmissions")); + + /* Do the test. */ + status = perform_tsx_test(-1100, TARGET_URI, FROM_URI, + TEST5_BRANCH_ID, + 6, &pjsip_options_method); + + /* Done. */ + return status; +} + + +/***************************************************************************** + ** + ** TEST6_BRANCH_ID: Successfull non-invite transaction + ** TEST7_BRANCH_ID: Successfull non-invite transaction with provisional + ** TEST8_BRANCH_ID: Failed invite transaction + ** TEST9_BRANCH_ID: Failed invite transaction with provisional + ** + ***************************************************************************** + */ +static int perform_generic_test( const char *title, + char *branch_id, + const pjsip_method *method) +{ + int i, status; + unsigned delay[] = { 1, 200 }; + + PJ_LOG(3,(THIS_FILE, " %s", title)); + + /* Do the test. */ + for (i=0; i<(int)PJ_ARRAY_SIZE(delay); ++i) { + + if (test_param->type == 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]); + } + + 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); + + /* Done. */ + return status; +} + + +/***************************************************************************** + ** + ** UAC Transaction Test. + ** + ***************************************************************************** + */ +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((pjsip_transport_type_e)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), NULL, &loop); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " Error: loop transport is not configured!")); + return -10; + } + + /* Register modules. */ + status = pjsip_endpt_register_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -30; + } + status = pjsip_endpt_register_module(endpt, &msg_receiver); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -40; + } + + /* TEST1_BRANCH_ID: Basic retransmit and timeout test. */ + status = tsx_uac_retransmit_test(); + if (status != 0) + return status; + + /* TEST2_BRANCH_ID: Resolve error test. */ + status = tsx_resolve_error_test(); + if (status != 0) + return status; + + /* TEST3_BRANCH_ID: UAC terminate while resolving test. */ + status = tsx_terminate_resolving_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 + * 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", + TEST6_BRANCH_ID, &pjsip_options_method); + if (status != 0) + return status; + + /* TEST7_BRANCH_ID: Successfull non-invite transaction */ + status = perform_generic_test("test7: successfull non-invite transaction " + "with provisional response", + TEST7_BRANCH_ID, &pjsip_options_method); + if (status != 0) + return status; + + /* TEST8_BRANCH_ID: Failed invite transaction */ + status = perform_generic_test("test8: failed invite transaction", + TEST8_BRANCH_ID, &pjsip_invite_method); + if (status != 0) + return status; + + /* TEST9_BRANCH_ID: Failed invite transaction with provisional response */ + status = perform_generic_test("test9: failed invite transaction with " + "provisional response", + TEST9_BRANCH_ID, &pjsip_invite_method); + 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/tsx_uas_test.c b/pjsip/src/test/tsx_uas_test.c new file mode 100644 index 00000000..813e4e50 --- /dev/null +++ b/pjsip/src/test/tsx_uas_test.c @@ -0,0 +1,1656 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "tsx_uas_test.c" + + +/***************************************************************************** + ** + ** UAS tests. + ** + ** This file performs various tests for UAC transactions. Each test will have + ** a different Via branch param so that message receiver module and + ** transaction user module can identify which test is being carried out. + ** + ** 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. + ** + ** TEST3_BRANCH_ID + ** Transaction correctly progressing to PROCEEDING state when provisional + ** response is sent. + ** + ** TEST4_BRANCH_ID + ** Transaction retransmits last response (if any) without notifying + ** transaction user upon receiving request retransmissions on TRYING + ** state + ** + ** TEST5_BRANCH_ID + ** As above, in PROCEEDING state. + ** + ** 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. + ** + ** TEST8_BRANCH_ID + ** As above, for INVITE's 2xx final response (this is PJSIP specific). + ** + ** TEST9_BRANCH_ID + ** INVITE transaction MUST cease retransmission of final response when + ** 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. + ** in TRYING state). + ** + ** TEST12_BRANCH_ID + ** As above, after provisional response is sent but before final + ** response is sent (i.e. in PROCEEDING state). + ** + ** TEST13_BRANCH_ID + ** As above, for INVITE, after final response has been sent but before + ** ACK is received (i.e. in CONNECTED state). + ** + ** TEST14_BRANCH_ID + ** When UAS failed to deliver the response with the selected transport, + ** it should try contacting the client with other transport or begin + ** RFC 3263 server resolution procedure. + ** This should be tested on: + ** a. TRYING state (when delivering first response). + ** b. PROCEEDING state (when failed to retransmit last response + ** upon receiving request retransmission). + ** c. COMPLETED state. + ** + **/ + +static char *TEST1_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test1"; +static char *TEST2_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test2"; +static char *TEST3_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test3"; +static char *TEST4_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test4"; +static char *TEST5_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test5"; +static char *TEST6_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test6"; +static char *TEST7_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test7"; +static char *TEST8_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test8"; +static char *TEST9_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test9"; +static char *TEST10_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test10"; +static char *TEST11_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test11"; +static char *TEST12_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test12"; +//static char *TEST13_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test13"; + +#define TEST1_STATUS_CODE 200 +#define TEST2_STATUS_CODE 301 +#define TEST3_PROVISIONAL_CODE PJSIP_SC_QUEUED +#define TEST3_STATUS_CODE 202 +#define TEST4_STATUS_CODE 200 +#define TEST4_REQUEST_COUNT 2 +#define TEST5_PROVISIONAL_CODE 100 +#define TEST5_STATUS_CODE 200 +#define TEST5_REQUEST_COUNT 2 +#define TEST5_RESPONSE_COUNT 2 +#define TEST6_PROVISIONAL_CODE 100 +#define TEST6_STATUS_CODE 200 /* Must be final */ +#define TEST6_REQUEST_COUNT 2 +#define TEST6_RESPONSE_COUNT 3 +#define TEST7_STATUS_CODE 301 +#define TEST8_STATUS_CODE 302 +#define TEST9_STATUS_CODE 301 + + +#define TEST4_TITLE "test4: absorbing request retransmission" +#define TEST5_TITLE "test5: retransmit last response in PROCEEDING state" +#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 + +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e); +static pj_bool_t on_rx_message(pjsip_rx_data *rdata); + +/* UAC transaction user module. */ +static pjsip_module tsx_user = +{ + NULL, NULL, /* prev and next */ + { "Tsx-UAS-User", 12}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + &tsx_user_on_tsx_state, /* on_tsx_state() */ +}; + +/* Module to send request. */ +static pjsip_module msg_sender = +{ + NULL, NULL, /* prev and next */ + { "Msg-Sender", 10}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_message, /* on_rx_request() */ + &on_rx_message, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +/* Static vars, which will be reset on each test. */ +static int recv_count; +static pj_time_val recv_last; +static pj_bool_t test_complete; + +/* Loop transport instance. */ +static pjsip_transport *loop; + +/* UAS transaction key. */ +static char key_buf[64]; +static pj_str_t tsx_key = { key_buf, 0 }; + + +/* General timer entry to be used by tests. */ +//static pj_timer_entry timer; + +/* Timer to send response via transaction. */ +struct response +{ + pj_str_t tsx_key; + pjsip_tx_data *tdata; +}; + +/* Timer callback to send response. */ +static void send_response_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + pjsip_transaction *tsx; + struct response *r = (struct response*) entry->user_data; + pj_status_t status; + + tsx = pjsip_tsx_layer_find_tsx(&r->tsx_key, PJ_TRUE); + if (!tsx) { + PJ_LOG(3,(THIS_FILE," error: timer unable to find transaction")); + pjsip_tx_data_dec_ref(r->tdata); + return; + } + + status = pjsip_tsx_send_msg(tsx, r->tdata); + if (status != PJ_SUCCESS) { + // Some tests do expect failure! + //PJ_LOG(3,(THIS_FILE," error: timer unable to send response")); + pj_mutex_unlock(tsx->mutex); + pjsip_tx_data_dec_ref(r->tdata); + return; + } + + pj_mutex_unlock(tsx->mutex); +} + +/* Utility to send response. */ +static void send_response( pjsip_rx_data *rdata, + pjsip_transaction *tsx, + int status_code ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_endpt_create_response( endpt, rdata, status_code, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create response", status); + test_complete = -196; + return; + } + + status = pjsip_tsx_send_msg(tsx, tdata); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + // Some tests do expect failure! + //app_perror(" error: unable to send response", status); + //test_complete = -197; + return; + } +} + +/* Schedule timer to send response for the specified UAS transaction */ +static void schedule_send_response( pjsip_rx_data *rdata, + const pj_str_t *tsx_key, + int status_code, + int msec_delay ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + pj_timer_entry *t; + struct response *r; + pj_time_val delay; + + status = pjsip_endpt_create_response( endpt, rdata, status_code, NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create response", status); + test_complete = -198; + return; + } + + r = PJ_POOL_ALLOC_T(tdata->pool, struct response); + pj_strdup(tdata->pool, &r->tsx_key, tsx_key); + r->tdata = tdata; + + delay.sec = 0; + delay.msec = msec_delay; + pj_time_val_normalize(&delay); + + t = PJ_POOL_ZALLOC_T(tdata->pool, pj_timer_entry); + t->user_data = r; + t->cb = &send_response_timer; + + status = pjsip_endpt_schedule_timer(endpt, t, &delay); + if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); + app_perror(" error: unable to schedule timer", status); + test_complete = -199; + return; + } +} + + +/* Find and terminate tsx with the specified key. */ +static void terminate_our_tsx(int status_code) +{ + pjsip_transaction *tsx; + + tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); + if (!tsx) { + PJ_LOG(3,(THIS_FILE," error: timer unable to find transaction")); + return; + } + + pjsip_tsx_terminate(tsx, status_code); + pj_mutex_unlock(tsx->mutex); +} + +#if 0 /* Unused for now */ +/* Timer callback to terminate transaction. */ +static void terminate_tsx_timer( pj_timer_heap_t *timer_heap, + struct pj_timer_entry *entry) +{ + terminate_our_tsx(entry->id); +} + + +/* Schedule timer to terminate transaction. */ +static void schedule_terminate_tsx( pjsip_transaction *tsx, + int status_code, + int msec_delay ) +{ + pj_time_val delay; + + delay.sec = 0; + delay.msec = msec_delay; + pj_time_val_normalize(&delay); + + pj_assert(pj_strcmp(&tsx->transaction_key, &tsx_key)==0); + timer.user_data = NULL; + timer.id = status_code; + timer.cb = &terminate_tsx_timer; + pjsip_endpt_schedule_timer(endpt, &timer, &delay); +} +#endif + + +/* + * This is the handler to receive state changed notification from the + * transaction. It is used to verify that the transaction behaves according + * to the test scenario. + */ +static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) +{ + if (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST2_BRANCH_ID)==0) + { + /* + * TEST1_BRANCH_ID tests that non-INVITE transaction transmits final + * response using correct transport and terminates transaction after + * T4 (PJSIP_T4_TIMEOUT, 5 seconds). + * + * TEST2_BRANCH_ID does similar test for non-2xx final response. + */ + int status_code = (pj_strcmp2(&tsx->branch, TEST1_BRANCH_ID)==0) ? + TEST1_STATUS_CODE : TEST2_STATUS_CODE; + + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + test_complete = 1; + + /* Check that status code is status_code. */ + if (tsx->status_code != status_code) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -100; + } + + /* Previous state must be completed. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -101; + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Previous state must be TRYING. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -102; + } + } + + } + else + if (pj_strcmp2(&tsx->branch, TEST3_BRANCH_ID)==0) { + /* + * TEST3_BRANCH_ID tests sending provisional response. + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + test_complete = 1; + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST3_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -110; + } + + /* Previous state must be completed. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -111; + } + + } else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) { + + /* Previous state must be TRYING. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -112; + } + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST3_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -113; + } + + /* Check that event must be TX_MSG */ + if (e->body.tsx_state.type != PJSIP_EVENT_TX_MSG) { + PJ_LOG(3,(THIS_FILE, " error: incorrect event")); + test_complete = -114; + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Previous state must be PROCEEDING. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -115; + } + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST3_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -116; + } + + /* Check that event must be TX_MSG */ + if (e->body.tsx_state.type != PJSIP_EVENT_TX_MSG) { + PJ_LOG(3,(THIS_FILE, " error: incorrect event")); + test_complete = -117; + } + + } + + } else + if (pj_strcmp2(&tsx->branch, TEST4_BRANCH_ID)==0) { + /* + * TEST4_BRANCH_ID tests receiving retransmissions in TRYING state. + */ + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST4_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, + " error: incorrect status code %d " + "(expecting %d)", tsx->status_code, + TEST4_STATUS_CODE)); + test_complete = -120; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -121; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) + { + PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (122)", + pjsip_tsx_state_str(tsx->state))); + test_complete = -122; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST5_BRANCH_ID)==0) { + /* + * TEST5_BRANCH_ID tests receiving retransmissions in PROCEEDING state + */ + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST5_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -130; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_PROCEEDING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -131; + } + + } else if (tsx->state == PJSIP_TSX_STATE_PROCEEDING) { + + /* Check status code. */ + if (tsx->status_code != TEST5_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -132; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (133)", + pjsip_tsx_state_str(tsx->state))); + test_complete = -133; + + } + + } else + if (pj_strcmp2(&tsx->branch, TEST6_BRANCH_ID)==0) { + /* + * TEST6_BRANCH_ID tests receiving retransmissions in COMPLETED state + */ + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST6_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code %d " + "(expecting %d)", tsx->status_code, + TEST6_STATUS_CODE)); + test_complete = -140; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -141; + } + + } else if (tsx->state != PJSIP_TSX_STATE_PROCEEDING && + tsx->state != PJSIP_TSX_STATE_COMPLETED && + tsx->state != PJSIP_TSX_STATE_DESTROYED) + { + PJ_LOG(3,(THIS_FILE, " error: unexpected state %s (142)", + pjsip_tsx_state_str(tsx->state))); + test_complete = -142; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST8_BRANCH_ID)==0) + { + /* + * TEST7_BRANCH_ID and TEST8_BRANCH_ID test retransmission of + * INVITE final response + */ + int code; + + if (pj_strcmp2(&tsx->branch, TEST7_BRANCH_ID) == 0) + code = TEST7_STATUS_CODE; + else + code = TEST8_STATUS_CODE; + + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (test_complete == 0) + test_complete = 1; + + /* Check status code. */ + if (tsx->status_code != PJSIP_SC_TSX_TIMEOUT) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -150; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + 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. */ + if (tsx->status_code != code) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -152; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -153; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) { + + PJ_LOG(3,(THIS_FILE, " error: unexpected state (154)")); + test_complete = -154; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST9_BRANCH_ID)==0) { + /* + * TEST9_BRANCH_ID tests that retransmission of INVITE final response + * must cease when ACK is received. + */ + + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + /* Request is received. */ + + } else if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (test_complete == 0) + test_complete = 1; + + /* Check status code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -160; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -161; + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -162; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -163; + } + + + } else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -164; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -165; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) { + + PJ_LOG(3,(THIS_FILE, " error: unexpected state (166)")); + test_complete = -166; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST10_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST11_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST12_BRANCH_ID)==0) + { + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (!test_complete) + test_complete = 1; + + if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) { + PJ_LOG(3,(THIS_FILE," error: incorrect status code")); + test_complete = -170; + } + } + } + +} + +/* Save transaction key to global variables. */ +static void save_key(pjsip_transaction *tsx) +{ + pj_str_t key; + + pj_strdup(tsx->pool, &key, &tsx->transaction_key); + pj_strcpy(&tsx_key, &key); +} + +#define DIFF(a,b) ((a<b) ? (b-a) : (a-b)) + +/* + * Message receiver handler. + */ +static pj_bool_t on_rx_message(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + pj_str_t branch_param = rdata->msg_info.via->branch_param; + pj_status_t status; + + if (pj_strcmp2(&branch_param, TEST1_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST2_BRANCH_ID) == 0) + { + /* + * TEST1_BRANCH_ID tests that non-INVITE transaction transmits 2xx + * final response using correct transport and terminates transaction + * after 32 seconds. + * + * TEST2_BRANCH_ID performs similar test for non-2xx final response. + */ + int status_code = (pj_strcmp2(&branch_param, TEST1_BRANCH_ID) == 0) ? + TEST1_STATUS_CODE : TEST2_STATUS_CODE; + + if (msg->type == PJSIP_REQUEST_MSG) { + /* On received request, create UAS and respond with final + * response. + */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -110; + return PJ_TRUE; + } + pjsip_tsx_recv_msg(tsx, rdata); + + save_key(tsx); + send_response(rdata, tsx, status_code); + + } else { + /* Verify the response received. */ + + ++recv_count; + + /* Verify status code. */ + if (msg->line.status.code != status_code) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -113; + } + + /* Verify that no retransmissions is received. */ + if (recv_count > 1) { + PJ_LOG(3,(THIS_FILE, " error: retransmission received")); + test_complete = -114; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST3_BRANCH_ID) == 0) { + + /* TEST3_BRANCH_ID tests provisional response. */ + + if (msg->type == PJSIP_REQUEST_MSG) { + /* On received request, create UAS and respond with provisional + * response, then schedule timer to send final response. + */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -116; + return PJ_TRUE; + } + pjsip_tsx_recv_msg(tsx, rdata); + + save_key(tsx); + + send_response(rdata, tsx, TEST3_PROVISIONAL_CODE); + schedule_send_response(rdata, &tsx->transaction_key, + TEST3_STATUS_CODE, 2000); + + } else { + /* Verify the response received. */ + + ++recv_count; + + if (recv_count == 1) { + /* Verify status code. */ + if (msg->line.status.code != TEST3_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -123; + } + } else if (recv_count == 2) { + /* Verify status code. */ + if (msg->line.status.code != TEST3_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -124; + } + } else { + PJ_LOG(3,(THIS_FILE, " error: retransmission received")); + test_complete = -125; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0) + { + + /* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state. */ + /* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state. */ + /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state. */ + + if (msg->type == PJSIP_REQUEST_MSG) { + /* 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); + test_complete = -130; + return PJ_TRUE; + } + + pjsip_tsx_recv_msg(tsx, rdata); + save_key(tsx); + + if (pj_strcmp2(&branch_param, TEST4_BRANCH_ID) == 0) { + + } else if (pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0) { + 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) { + PJ_LOG(3,(THIS_FILE, " error: not expecting response!")); + test_complete = -132; + + } else if (pj_strcmp2(&branch_param, TEST5_BRANCH_ID) == 0) { + + if (rdata->msg_info.msg->line.status.code!=TEST5_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code!")); + test_complete = -133; + + } + if (recv_count > TEST5_RESPONSE_COUNT) { + PJ_LOG(3,(THIS_FILE, " error: not expecting response!")); + test_complete = -134; + } + + } else if (pj_strcmp2(&branch_param, TEST6_BRANCH_ID) == 0) { + + int code = rdata->msg_info.msg->line.status.code; + + switch (recv_count) { + case 1: + if (code != TEST6_PROVISIONAL_CODE) { + PJ_LOG(3,(THIS_FILE, " error: invalid code!")); + test_complete = -135; + } + break; + case 2: + case 3: + if (code != TEST6_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: invalid code %d " + "(expecting %d)", code, TEST6_STATUS_CODE)); + test_complete = -136; + } + break; + default: + PJ_LOG(3,(THIS_FILE, " error: not expecting response")); + test_complete = -137; + break; + } + } + } + return PJ_TRUE; + + + } else if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST8_BRANCH_ID) == 0) + { + + /* + * TEST7_BRANCH_ID and TEST8_BRANCH_ID test the retransmission + * of INVITE final response + */ + if (msg->type == PJSIP_REQUEST_MSG) { + + /* On received request, create UAS. */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -140; + return PJ_TRUE; + } + + pjsip_tsx_recv_msg(tsx, rdata); + save_key(tsx); + + if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0) { + + send_response(rdata, tsx, TEST7_STATUS_CODE); + + } else { + + send_response(rdata, tsx, TEST8_STATUS_CODE); + + } + + } else { + int code; + + ++recv_count; + + if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0) + code = TEST7_STATUS_CODE; + else + code = TEST8_STATUS_CODE; + + if (recv_count==1) { + + if (rdata->msg_info.msg->line.status.code != code) { + PJ_LOG(3,(THIS_FILE," error: invalid status code")); + test_complete = -141; + } + + recv_last = rdata->pkt_info.timestamp; + + } else { + + pj_time_val now; + unsigned msec, msec_expected; + + now = rdata->pkt_info.timestamp; + + PJ_TIME_VAL_SUB(now, recv_last); + + msec = now.sec*1000 + now.msec; + msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1; + if (msec_expected > pjsip_cfg()->tsx.t2) + msec_expected = pjsip_cfg()->tsx.t2; + + if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) { + PJ_LOG(3,(THIS_FILE, + " error: incorrect retransmission " + "time (%d ms expected, %d ms received", + msec_expected, msec)); + test_complete = -142; + } + + if (recv_count > 11) { + PJ_LOG(3,(THIS_FILE," error: too many responses (%d)", + recv_count)); + test_complete = -143; + } + + recv_last = rdata->pkt_info.timestamp; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST9_BRANCH_ID) == 0) { + + /* + * TEST9_BRANCH_ID tests that the retransmission of INVITE final + * response should cease when ACK is received. Transaction also MUST + * terminate in T4 seconds. + */ + if (msg->type == PJSIP_REQUEST_MSG) { + + /* On received request, create UAS. */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -150; + return PJ_TRUE; + } + + pjsip_tsx_recv_msg(tsx, rdata); + save_key(tsx); + send_response(rdata, tsx, TEST9_STATUS_CODE); + + + } else { + + ++recv_count; + + if (rdata->msg_info.msg->line.status.code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE," error: invalid status code")); + test_complete = -151; + } + + if (recv_count==1) { + + recv_last = rdata->pkt_info.timestamp; + + } else if (recv_count < 5) { + + /* Let UAS retransmit some messages before we send ACK. */ + pj_time_val now; + unsigned msec, msec_expected; + + now = rdata->pkt_info.timestamp; + + PJ_TIME_VAL_SUB(now, recv_last); + + msec = now.sec*1000 + now.msec; + msec_expected = (1 << (recv_count-2)) * pjsip_cfg()->tsx.t1; + if (msec_expected > pjsip_cfg()->tsx.t2) + msec_expected = pjsip_cfg()->tsx.t2; + + if (DIFF(msec, msec_expected) > MAX_ALLOWED_DIFF) { + PJ_LOG(3,(THIS_FILE, + " error: incorrect retransmission " + "time (%d ms expected, %d ms received", + msec_expected, msec)); + test_complete = -152; + } + + recv_last = rdata->pkt_info.timestamp; + + } else if (recv_count == 5) { + pjsip_tx_data *tdata; + pjsip_sip_uri *uri; + pjsip_via_hdr *via; + + status = pjsip_endpt_create_request_from_hdr( + endpt, &pjsip_ack_method, + rdata->msg_info.to->uri, + rdata->msg_info.from, + rdata->msg_info.to, + NULL, + rdata->msg_info.cid, + rdata->msg_info.cseq->cseq, + NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create ACK", status); + test_complete = -153; + return PJ_TRUE; + } + + uri=(pjsip_sip_uri*)pjsip_uri_get_uri(tdata->msg->line.req.uri); + uri->transport_param = pj_str("loop-dgram"); + + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param = pj_str(TEST9_BRANCH_ID); + + status = pjsip_endpt_send_request_stateless(endpt, tdata, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to send ACK", status); + test_complete = -154; + } + + } else { + PJ_LOG(3,(THIS_FILE," error: too many responses (%d)", + recv_count)); + test_complete = -155; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST12_BRANCH_ID) == 0) + { + int test_num, code1, code2; + + if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0) + test_num=10, code1 = 100, code2 = 0; + else if (pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0) + test_num=11, code1 = 100, code2 = 200; + else + test_num=12, code1 = 200, code2 = 0; + + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { + + /* On received response, create UAS. */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -150; + return PJ_TRUE; + } + + pjsip_tsx_recv_msg(tsx, rdata); + save_key(tsx); + + schedule_send_response(rdata, &tsx_key, code1, 1000); + + if (code2) + schedule_send_response(rdata, &tsx_key, code2, 2000); + + } else { + + } + + return PJ_TRUE; + } + + return PJ_FALSE; +} + +/* + * The generic test framework, used by most of the tests. + */ +static int perform_test( char *target_uri, char *from_uri, + char *branch_param, int test_time, + const pjsip_method *method, + int request_cnt, int request_interval_msec, + int expecting_timeout) +{ + pjsip_tx_data *tdata; + pj_str_t target, from; + pjsip_via_hdr *via; + pj_time_val timeout, next_send; + int sent_cnt; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, + " please standby, this will take at most %d seconds..", + test_time)); + + /* Reset test. */ + recv_count = 0; + test_complete = 0; + tsx_key.slen = 0; + + /* Init headers. */ + target = pj_str(target_uri); + from = pj_str(from_uri); + + /* Create request. */ + status = pjsip_endpt_create_request( endpt, method, &target, + &from, &target, NULL, NULL, -1, + NULL, &tdata); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to create request", status); + return -10; + } + + /* Set the branch param for test 1. */ + via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param = pj_str(branch_param); + + /* Schedule first send. */ + sent_cnt = 0; + pj_gettimeofday(&next_send); + pj_time_val_normalize(&next_send); + + /* Set test completion time. */ + pj_gettimeofday(&timeout); + timeout.sec += test_time; + + /* Wait until test complete. */ + while (!test_complete) { + pj_time_val now, poll_delay = {0, 10}; + + pjsip_endpt_handle_events(endpt, &poll_delay); + + pj_gettimeofday(&now); + + if (sent_cnt < request_cnt && PJ_TIME_VAL_GTE(now, next_send)) { + /* Add additional reference to tdata to prevent transaction from + * deleting it. + */ + 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); + pjsip_tx_data_dec_ref(tdata); + return -20; + } + + /* Schedule next send, if any. */ + sent_cnt++; + if (sent_cnt < request_cnt) { + pj_gettimeofday(&next_send); + next_send.msec += request_interval_msec; + pj_time_val_normalize(&next_send); + } + } + + if (now.sec > timeout.sec) { + if (!expecting_timeout) + PJ_LOG(3,(THIS_FILE, " Error: test has timed out")); + pjsip_tx_data_dec_ref(tdata); + return TEST_TIMEOUT_ERROR; + } + } + + if (test_complete < 0) { + pjsip_transaction *tsx; + + tsx = pjsip_tsx_layer_find_tsx(&tsx_key, PJ_TRUE); + if (tsx) { + pjsip_tsx_terminate(tsx, PJSIP_SC_REQUEST_TERMINATED); + pj_mutex_unlock(tsx->mutex); + flush_events(1000); + } + pjsip_tx_data_dec_ref(tdata); + return test_complete; + } + + /* Allow transaction to destroy itself */ + flush_events(500); + + /* Make sure transaction has been destroyed. */ + if (pjsip_tsx_layer_find_tsx(&tsx_key, PJ_FALSE) != NULL) { + PJ_LOG(3,(THIS_FILE, " Error: transaction has not been destroyed")); + pjsip_tx_data_dec_ref(tdata); + return -40; + } + + /* Check tdata reference counter. */ + if (pj_atomic_get(tdata->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " Error: tdata reference counter is %d", + pj_atomic_get(tdata->ref_cnt))); + pjsip_tx_data_dec_ref(tdata); + return -50; + } + + /* Destroy txdata */ + pjsip_tx_data_dec_ref(tdata); + + return PJ_SUCCESS; + +} + + +/***************************************************************************** + ** + ** TEST1_BRANCH_ID: Basic 2xx final response + ** TEST2_BRANCH_ID: Basic non-2xx final response + ** + ***************************************************************************** + */ +static int tsx_basic_final_response_test(void) +{ + unsigned duration; + int status; + + PJ_LOG(3,(THIS_FILE," test1: basic sending 2xx final response")); + + /* 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(TARGET_URI, FROM_URI, TEST2_BRANCH_ID, + duration, &pjsip_options_method, 1, 0, 0); + if (status != 0) + return status; + + return 0; +} + + +/***************************************************************************** + ** + ** TEST3_BRANCH_ID: Sending provisional response + ** + ***************************************************************************** + */ +static int tsx_basic_provisional_response_test(void) +{ + unsigned duration; + int status; + + PJ_LOG(3,(THIS_FILE," test3: basic sending 2xx final response")); + + 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; +} + + +/***************************************************************************** + ** + ** TEST4_BRANCH_ID: Absorbs retransmissions in TRYING state + ** TEST5_BRANCH_ID: Absorbs retransmissions in PROCEEDING state + ** TEST6_BRANCH_ID: Absorbs retransmissions in COMPLETED state + ** + ***************************************************************************** + */ +static int tsx_retransmit_last_response_test(const char *title, + char *branch_id, + int request_cnt, + int status_code) +{ + int status; + + PJ_LOG(3,(THIS_FILE," %s", title)); + + status = perform_test(TARGET_URI, FROM_URI, branch_id, 5, + &pjsip_options_method, + request_cnt, 1000, 1); + if (status && status != TEST_TIMEOUT_ERROR) + return status; + if (!status) { + PJ_LOG(3,(THIS_FILE, " error: expecting timeout")); + return -31; + } + + terminate_our_tsx(status_code); + flush_events(100); + + if (test_complete != 1) + return test_complete; + + flush_events(100); + return 0; +} + +/***************************************************************************** + ** + ** TEST7_BRANCH_ID: INVITE non-2xx final response retransmission test + ** TEST8_BRANCH_ID: INVITE 2xx final response retransmission test + ** + ***************************************************************************** + */ +static int tsx_final_response_retransmission_test(void) +{ + int status; + + PJ_LOG(3,(THIS_FILE, + " test7: INVITE non-2xx final response retransmission")); + + 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) + return status; + + PJ_LOG(3,(THIS_FILE, + " test8: INVITE 2xx final response retransmission")); + + 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) + return status; + + return 0; +} + + +/***************************************************************************** + ** + ** TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must + ** cease when ACK is received + ** + ***************************************************************************** + */ +static int tsx_ack_test(void) +{ + int status; + + PJ_LOG(3,(THIS_FILE, + " test9: receiving ACK for non-2xx final response")); + + status = perform_test(TARGET_URI, FROM_URI, TEST9_BRANCH_ID, + 20, /* allow 5 retransmissions */ + &pjsip_invite_method, 1, 0, 0); + if (status != 0) + return status; + + + return 0; +} + + + +/***************************************************************************** + ** + ** 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. + ** + ***************************************************************************** + */ +static int tsx_transport_failure_test(void) +{ + struct test_desc + { + int transport_delay; + int fail_delay; + char *branch_id; + char *title; + } tests[] = + { + { 0, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (no delay)" }, + { 50, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (50 ms delay)" }, + { 0, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (no delay)" }, + { 50, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (50 ms delay)" }, + { 0, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (no delay)" }, + { 50, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (50 ms delay)" }, + }; + int i, status; + + for (i=0; i<(int)PJ_ARRAY_SIZE(tests); ++i) { + pj_time_val fail_time, end_test, now; + + PJ_LOG(3,(THIS_FILE, " %s", tests[i].title)); + pjsip_loop_set_failure(loop, 0, NULL); + pjsip_loop_set_delay(loop, tests[i].transport_delay); + + 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) { + PJ_LOG(3,(THIS_FILE, " error: expecting timeout")); + return -40; + } + + pj_gettimeofday(&fail_time); + fail_time.msec += tests[i].fail_delay; + pj_time_val_normalize(&fail_time); + + do { + pj_time_val interval = { 0, 1 }; + pj_gettimeofday(&now); + pjsip_endpt_handle_events(endpt, &interval); + } while (PJ_TIME_VAL_LT(now, fail_time)); + + pjsip_loop_set_failure(loop, 1, NULL); + + end_test = now; + end_test.sec += 5; + + do { + pj_time_val interval = { 0, 1 }; + pj_gettimeofday(&now); + pjsip_endpt_handle_events(endpt, &interval); + } while (!test_complete && PJ_TIME_VAL_LT(now, end_test)); + + if (test_complete == 0) { + PJ_LOG(3,(THIS_FILE, " error: test has timed out")); + return -41; + } + + if (test_complete != 1) + return test_complete; + } + + return 0; +} + +/***************************************************************************** + ** + ** UAS Transaction Test. + ** + ***************************************************************************** + */ +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((pjsip_transport_type_e)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), NULL, &loop); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " Error: loop transport is not configured!")); + return -10; + } + /* Register modules. */ + status = pjsip_endpt_register_module(endpt, &tsx_user); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -3; + } + status = pjsip_endpt_register_module(endpt, &msg_sender); + if (status != PJ_SUCCESS) { + app_perror(" Error: unable to register module", status); + return -4; + } + + /* TEST1_BRANCH_ID: Basic 2xx final response. + * TEST2_BRANCH_ID: Basic non-2xx final response. + */ + status = tsx_basic_final_response_test(); + if (status != 0) + return status; + + /* TEST3_BRANCH_ID: with provisional response + */ + status = tsx_basic_provisional_response_test(); + if (status != 0) + return status; + + /* TEST4_BRANCH_ID: absorbs retransmissions in TRYING state + */ + status = tsx_retransmit_last_response_test(TEST4_TITLE, + TEST4_BRANCH_ID, + TEST4_REQUEST_COUNT, + TEST4_STATUS_CODE); + if (status != 0) + return status; + + /* TEST5_BRANCH_ID: retransmit last response in PROCEEDING state + */ + status = tsx_retransmit_last_response_test(TEST5_TITLE, + TEST5_BRANCH_ID, + TEST5_REQUEST_COUNT, + TEST5_STATUS_CODE); + if (status != 0) + return status; + + /* 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. + */ + 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. + */ + 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. + */ + /* Only valid for loop-dgram */ + if (param->type == PJSIP_TRANSPORT_LOOP_DGRAM) { + status = tsx_transport_failure_test(); + if (status != 0) + return status; + } + + + /* 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/txdata_test.c b/pjsip/src/test/txdata_test.c new file mode 100644 index 00000000..9ced25e3 --- /dev/null +++ b/pjsip/src/test/txdata_test.c @@ -0,0 +1,847 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + + +#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. + */ +static int core_txdata_test(void) +{ + pj_status_t status; + pj_str_t target, from, to, contact, body; + pjsip_rx_data dummy_rdata; + pjsip_tx_data *invite, *invite2, *cancel, *response, *ack; + + PJ_LOG(3,(THIS_FILE, " core transmit data test")); + + /* Create INVITE request. */ + target = pj_str("tel:+1"); + from = pj_str("tel:+0"); + to = pj_str("tel:+1"); + contact = pj_str("Bob <sip:+0@example.com;user=phone>"); + body = pj_str("Hello world!"); + + status = pjsip_endpt_create_request( endpt, &pjsip_invite_method, &target, + &from, &to, &contact, NULL, 10, &body, + &invite); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + return -10; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(invite) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -14; + } + /* Reference counter must be set to 1. */ + if (pj_atomic_get(invite->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid reference counter")); + return -15; + } + /* Check message type. */ + if (invite->msg->type != PJSIP_REQUEST_MSG) + return -16; + /* Check method. */ + if (invite->msg->line.req.method.id != PJSIP_INVITE_METHOD) + return -17; + + /* Check that mandatory headers are present. */ + if (HFIND(invite->msg, from, FROM) == 0) + return -20; + if (HFIND(invite->msg, to, TO) == 0) + return -21; + if (HFIND(invite->msg, contact, CONTACT) == 0) + return -22; + if (HFIND(invite->msg, cid, CALL_ID) == 0) + return -23; + if (HFIND(invite->msg, cseq, CSEQ) == 0) + return -24; + do { + pjsip_via_hdr *via = HFIND(invite->msg, via, VIA); + if (via == NULL) + return -25; + /* Branch param must be empty. */ + if (via->branch_param.slen != 0) + return -26; + } while (0); + if (invite->msg->body == NULL) + return -28; + + /* Create another INVITE request from first request. */ + status = pjsip_endpt_create_request_from_hdr( endpt, &pjsip_invite_method, + invite->msg->line.req.uri, + HFIND(invite->msg,from,FROM), + HFIND(invite->msg,to,TO), + HFIND(invite->msg,contact,CONTACT), + HFIND(invite->msg,cid,CALL_ID), + 10, &body, &invite2); + if (status != PJ_SUCCESS) { + app_perror(" error: create second request failed", status); + return -30; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(invite2) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -34; + } + /* Reference counter must be set to 1. */ + if (pj_atomic_get(invite2->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid reference counter")); + return -35; + } + /* Check message type. */ + if (invite2->msg->type != PJSIP_REQUEST_MSG) + return -36; + /* Check method. */ + if (invite2->msg->line.req.method.id != PJSIP_INVITE_METHOD) + return -37; + + /* Check that mandatory headers are again present. */ + if (HFIND(invite2->msg, from, FROM) == 0) + return -40; + if (HFIND(invite2->msg, to, TO) == 0) + return -41; + if (HFIND(invite2->msg, contact, CONTACT) == 0) + return -42; + if (HFIND(invite2->msg, cid, CALL_ID) == 0) + return -43; + if (HFIND(invite2->msg, cseq, CSEQ) == 0) + return -44; + if (HFIND(invite2->msg, via, VIA) == 0) + return -45; + /* + if (HFIND(invite2->msg, ctype, CONTENT_TYPE) == 0) + return -46; + if (HFIND(invite2->msg, clen, CONTENT_LENGTH) == 0) + return -47; + */ + if (invite2->msg->body == NULL) + return -48; + + /* Done checking invite2. We can delete this. */ + if (pjsip_tx_data_dec_ref(invite2) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: request buffer not destroyed!")); + return -49; + } + + /* Initialize dummy rdata (to simulate receiving a request) + * We should never do this in real application, as there are many + * many more fields need to be initialized!! + */ + dummy_rdata.msg_info.cid = HFIND(invite->msg, cid, CALL_ID); + dummy_rdata.msg_info.clen = NULL; + dummy_rdata.msg_info.cseq = HFIND(invite->msg, cseq, CSEQ); + dummy_rdata.msg_info.ctype = NULL; + dummy_rdata.msg_info.from = HFIND(invite->msg, from, FROM); + dummy_rdata.msg_info.max_fwd = NULL; + dummy_rdata.msg_info.msg = invite->msg; + dummy_rdata.msg_info.record_route = NULL; + dummy_rdata.msg_info.require = NULL; + dummy_rdata.msg_info.route = NULL; + dummy_rdata.msg_info.to = HFIND(invite->msg, to, TO); + dummy_rdata.msg_info.via = HFIND(invite->msg, via, VIA); + + /* Create a response message for the request. */ + status = pjsip_endpt_create_response( endpt, &dummy_rdata, 301, NULL, + &response); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create response", status); + return -50; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(response) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -54; + } + /* Check reference counter. */ + if (pj_atomic_get(response->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid ref count in response")); + return -55; + } + /* Check message type. */ + if (response->msg->type != PJSIP_RESPONSE_MSG) + return -56; + /* Check correct status is set. */ + if (response->msg->line.status.code != 301) + return -57; + + /* Check that mandatory headers are again present. */ + if (HFIND(response->msg, from, FROM) == 0) + return -60; + if (HFIND(response->msg, to, TO) == 0) + return -61; + /* + if (HFIND(response->msg, contact, CONTACT) == 0) + return -62; + */ + if (HFIND(response->msg, cid, CALL_ID) == 0) + return -63; + if (HFIND(response->msg, cseq, CSEQ) == 0) + return -64; + if (HFIND(response->msg, via, VIA) == 0) + return -65; + + /* This response message will be used later when creating ACK */ + + /* Create CANCEL request for the original request. */ + status = pjsip_endpt_create_cancel( endpt, invite, &cancel); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create CANCEL request", status); + return -80; + } + + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(cancel) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -84; + } + /* Check reference counter. */ + if (pj_atomic_get(cancel->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid ref count in CANCEL request")); + return -85; + } + /* Check message type. */ + if (cancel->msg->type != PJSIP_REQUEST_MSG) + return -86; + /* Check method. */ + if (cancel->msg->line.req.method.id != PJSIP_CANCEL_METHOD) + return -87; + + /* Check that mandatory headers are again present. */ + if (HFIND(cancel->msg, from, FROM) == 0) + return -90; + if (HFIND(cancel->msg, to, TO) == 0) + return -91; + /* + if (HFIND(cancel->msg, contact, CONTACT) == 0) + return -92; + */ + if (HFIND(cancel->msg, cid, CALL_ID) == 0) + return -93; + if (HFIND(cancel->msg, cseq, CSEQ) == 0) + return -94; + if (HFIND(cancel->msg, via, VIA) == 0) + return -95; + + /* Done checking CANCEL request. */ + if (pjsip_tx_data_dec_ref(cancel) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!")); + return -99; + } + + /* Modify dummy_rdata to simulate receiving response. */ + pj_bzero(&dummy_rdata, sizeof(dummy_rdata)); + dummy_rdata.msg_info.msg = response->msg; + dummy_rdata.msg_info.to = HFIND(response->msg, to, TO); + + /* Create ACK request */ + status = pjsip_endpt_create_ack( endpt, invite, &dummy_rdata, &ack ); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " error: unable to create ACK")); + return -100; + } + /* Buffer must be invalid. */ + if (pjsip_tx_data_is_valid(ack) != 0) { + PJ_LOG(3,(THIS_FILE, " error: buffer must be invalid")); + return -104; + } + /* Check reference counter. */ + if (pj_atomic_get(ack->ref_cnt) != 1) { + PJ_LOG(3,(THIS_FILE, " error: invalid ref count in ACK request")); + return -105; + } + /* Check message type. */ + if (ack->msg->type != PJSIP_REQUEST_MSG) + return -106; + /* Check method. */ + if (ack->msg->line.req.method.id != PJSIP_ACK_METHOD) + return -107; + /* Check Request-URI is present. */ + if (ack->msg->line.req.uri == NULL) + return -108; + + /* Check that mandatory headers are again present. */ + if (HFIND(ack->msg, from, FROM) == 0) + return -110; + if (HFIND(ack->msg, to, TO) == 0) + return -111; + if (HFIND(ack->msg, cid, CALL_ID) == 0) + return -112; + if (HFIND(ack->msg, cseq, CSEQ) == 0) + return -113; + if (HFIND(ack->msg, via, VIA) == 0) + return -114; + if (ack->msg->body != NULL) + return -115; + + /* Done checking invite message. */ + if (pjsip_tx_data_dec_ref(invite) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!")); + return -120; + } + + /* Done checking response message. */ + if (pjsip_tx_data_dec_ref(response) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!")); + return -130; + } + + /* Done checking ack message. */ + if (pjsip_tx_data_dec_ref(ack) != PJSIP_EBUFDESTROYED) { + PJ_LOG(3,(THIS_FILE, " error: response buffer not destroyed!")); + return -140; + } + + /* Done. */ + return 0; +} + + + +/* + * This test demonstrate the bug as reported in: + * http://bugzilla.pjproject.net/show_bug.cgi?id=49 + */ +#if INCLUDE_GCC_TEST +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; +} +#endif + + +/* This tests the request creating functions against the following + * requirements: + * - header params in URI creates header in the request. + * - method and headers params are correctly shown or hidden in + * request URI, From, To, and Contact header. + */ +static int txdata_test_uri_params(void) +{ + 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="); + pj_str_t pname = pj_str("x-param"); + pj_str_t hname = pj_str("X-Hdr-1"); + pj_str_t hemptyname = pj_str("X-Empty-Hdr"); + pjsip_from_hdr *from_hdr; + pjsip_to_hdr *to_hdr; + pjsip_contact_hdr *contact_hdr; + pjsip_generic_string_hdr *hdr; + 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; + + 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; + } + + /* Fill up the Via header to prevent syntax error on parsing */ + via = (pjsip_via_hdr*) 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 + */ + 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 -256; + } + + /* Check the existence of port, other_param, and header param. + * Port is now allowed in To and From header. + */ + /* Port in request URI. */ + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(msg->line.req.uri); + if (uri->port != 5061) { + PJ_LOG(3,(THIS_FILE, " error: port not present in request URI")); + pjsip_tx_data_dec_ref(tdata); + return -260; + } + /* other_param in request_uri */ + param = pjsip_param_find(&uri->other_param, &pname); + if (param == NULL || pj_strcmp2(¶m->value, "param 1") != 0) { + PJ_LOG(3,(THIS_FILE, " error: x-param not present in request URI")); + pjsip_tx_data_dec_ref(tdata); + return -261; + } + /* header param in request uri. */ + if (!pj_list_empty(&uri->header_param)) { + PJ_LOG(3,(THIS_FILE, " error: hparam in request URI")); + pjsip_tx_data_dec_ref(tdata); + return -262; + } + + /* Port in From header. */ + from_hdr = (pjsip_from_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_FROM, NULL); + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(from_hdr->uri); + if (uri->port != 0) { + PJ_LOG(3,(THIS_FILE, " error: port most not exist in From header")); + pjsip_tx_data_dec_ref(tdata); + return -270; + } + /* other_param in From header */ + param = pjsip_param_find(&uri->other_param, &pname); + if (param == NULL || pj_strcmp2(¶m->value, "param 1") != 0) { + PJ_LOG(3,(THIS_FILE, " error: x-param not present in From header")); + pjsip_tx_data_dec_ref(tdata); + return -271; + } + /* header param in From header. */ + if (!pj_list_empty(&uri->header_param)) { + PJ_LOG(3,(THIS_FILE, " error: hparam in From header")); + pjsip_tx_data_dec_ref(tdata); + return -272; + } + + + /* Port in To header. */ + to_hdr = (pjsip_to_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_TO, NULL); + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(to_hdr->uri); + if (uri->port != 0) { + PJ_LOG(3,(THIS_FILE, " error: port most not exist in To header")); + pjsip_tx_data_dec_ref(tdata); + return -280; + } + /* other_param in To header */ + param = pjsip_param_find(&uri->other_param, &pname); + if (param == NULL || pj_strcmp2(¶m->value, "param 1") != 0) { + PJ_LOG(3,(THIS_FILE, " error: x-param not present in To header")); + pjsip_tx_data_dec_ref(tdata); + return -281; + } + /* header param in From header. */ + if (!pj_list_empty(&uri->header_param)) { + PJ_LOG(3,(THIS_FILE, " error: hparam in To header")); + pjsip_tx_data_dec_ref(tdata); + return -282; + } + + + + /* Port in Contact header. */ + contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL); + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(contact_hdr->uri); + if (uri->port != 5061) { + PJ_LOG(3,(THIS_FILE, " error: port not present in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -290; + } + /* other_param in Contact header */ + param = pjsip_param_find(&uri->other_param, &pname); + if (param == NULL || pj_strcmp2(¶m->value, "param 1") != 0) { + PJ_LOG(3,(THIS_FILE, " error: x-param not present in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -291; + } + /* header param in Contact header. */ + if (pj_list_empty(&uri->header_param)) { + PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -292; + } + /* Check for X-Hdr-1 */ + param = pjsip_param_find(&uri->header_param, &hname); + if (param == NULL || pj_strcmp2(¶m->value, "Header 1")!=0) { + PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -293; + } + /* Check for X-Empty-Hdr */ + param = pjsip_param_find(&uri->header_param, &hemptyname); + if (param == NULL || pj_strcmp2(¶m->value, "")!=0) { + PJ_LOG(3,(THIS_FILE, " error: hparam is missing in Contact header")); + pjsip_tx_data_dec_ref(tdata); + return -294; + } + + + /* Check that headers are present in the request. */ + hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(msg, &hname, NULL); + if (hdr == NULL || pj_strcmp2(&hdr->hvalue, "Header 1")!=0) { + PJ_LOG(3,(THIS_FILE, " error: header X-Hdr-1 not created")); + pjsip_tx_data_dec_ref(tdata); + return -300; + } + + hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(msg, &hemptyname, NULL); + if (hdr == NULL || pj_strcmp2(¶m->value, "")!=0) { + PJ_LOG(3,(THIS_FILE, " error: header X-Empty-Hdr not created")); + pjsip_tx_data_dec_ref(tdata); + return -330; + } + + pjsip_tx_data_dec_ref(tdata); + 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\" <sip:localuser@serviceprovider.com>"); + pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>"); + pj_str_t str_contact = str_from; + + elapsed.u64 = 0; + + for (i=0; i<LOOP; i+=COUNT) { + pj_bzero(tdata, sizeof(tdata)); + + pj_get_timestamp(&t1); + + for (j=0; j<COUNT; ++j) { + status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, + &str_target, &str_from, &str_to, + &str_contact, NULL, -1, NULL, + &tdata[j]); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + goto on_error; + } + } + + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&elapsed, &t2); + + for (j=0; j<COUNT; ++j) + pjsip_tx_data_dec_ref(tdata[j]); + } + + p_elapsed->u64 = elapsed.u64; + return PJ_SUCCESS; + +on_error: + for (i=0; i<COUNT; ++i) { + if (tdata[i]) + pjsip_tx_data_dec_ref(tdata[i]); + } + return -400; +} + + + +/* + * create response benchmark + */ +static int create_response_bench(pj_timestamp *p_elapsed) +{ + enum { COUNT = 100 }; + unsigned i, j; + pjsip_via_hdr *via; + pjsip_rx_data rdata; + pjsip_tx_data *request; + pjsip_tx_data *tdata[COUNT]; + 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\" <sip:localuser@serviceprovider.com>"); + pj_str_t str_to = pj_str("\"Remote User\" <sip:remoteuser@serviceprovider.com>"); + 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*) pjsip_hdr_clone(request->pool, via)); + pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*) pjsip_hdr_clone(request->pool, via)); + pjsip_msg_insert_first_hdr(request->msg, (pjsip_hdr*)via); + + + /* Create "dummy" rdata from the tdata */ + pj_bzero(&rdata, sizeof(pjsip_rx_data)); + rdata.tp_info.pool = request->pool; + rdata.msg_info.msg = request->msg; + rdata.msg_info.from = (pjsip_from_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_FROM, NULL); + rdata.msg_info.to = (pjsip_to_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_TO, NULL); + rdata.msg_info.cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(request->msg, PJSIP_H_CSEQ, NULL); + rdata.msg_info.cid = (pjsip_cid_hdr*) 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; i<LOOP; i+=COUNT) { + pj_bzero(tdata, sizeof(tdata)); + + pj_get_timestamp(&t1); + + for (j=0; j<COUNT; ++j) { + status = pjsip_endpt_create_response(endpt, &rdata, 200, NULL, &tdata[j]); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create request", status); + goto on_error; + } + } + + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&elapsed, &t2); + + for (j=0; j<COUNT; ++j) + pjsip_tx_data_dec_ref(tdata[j]); + } + + p_elapsed->u64 = elapsed.u64; + pjsip_tx_data_dec_ref(request); + return PJ_SUCCESS; + +on_error: + for (i=0; i<COUNT; ++i) { + if (tdata[i]) + pjsip_tx_data_dec_ref(tdata[i]); + } + return -400; +} + + +int txdata_test(void) +{ + enum { REPEAT = 4 }; + unsigned i, msgs; + pj_timestamp usec[REPEAT], min, freq; + int status; + + status = pj_get_timestamp_freq(&freq); + if (status != PJ_SUCCESS) + return status; + + status = core_txdata_test(); + if (status != 0) + return status; + +#if INCLUDE_GCC_TEST + status = gcc_test(); + if (status != 0) + return status; +#endif + + status = txdata_test_uri_params(); + if (status != 0) + return status; + + + /* + * Benchmark create_request() + */ + PJ_LOG(3,(THIS_FILE, " benchmarking request creation:")); + for (i=0; i<REPEAT; ++i) { + PJ_LOG(3,(THIS_FILE, " test %d of %d..", + i+1, REPEAT)); + status = create_request_bench(&usec[i]); + if (status != PJ_SUCCESS) + return status; + } + + min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF); + for (i=0; i<REPEAT; ++i) { + if (usec[i].u64 < min.u64) min.u64 = usec[i].u64; + } + + msgs = (unsigned)(freq.u64 * LOOP / min.u64); + + PJ_LOG(3,(THIS_FILE, " Requests created at %d requests/sec", msgs)); + + report_ival("create-request-per-sec", + msgs, "msg/sec", + "Number of typical request messages that can be created " + "per second with <tt>pjsip_endpt_create_request()</tt>"); + + + /* + * Benchmark create_response() + */ + PJ_LOG(3,(THIS_FILE, " benchmarking response creation:")); + for (i=0; i<REPEAT; ++i) { + PJ_LOG(3,(THIS_FILE, " test %d of %d..", + i+1, REPEAT)); + status = create_response_bench(&usec[i]); + if (status != PJ_SUCCESS) + return status; + } + + min.u64 = PJ_UINT64(0xFFFFFFFFFFFFFFF); + for (i=0; i<REPEAT; ++i) { + if (usec[i].u64 < min.u64) min.u64 = usec[i].u64; + } + + msgs = (unsigned)(freq.u64 * LOOP / min.u64); + + PJ_LOG(3,(THIS_FILE, " Responses created at %d responses/sec", msgs)); + + report_ival("create-response-per-sec", + msgs, "msg/sec", + "Number of typical response messages that can be created " + "per second with <tt>pjsip_endpt_create_response()</tt>"); + + + return 0; +} + diff --git a/pjsip/src/test/uri_test.c b/pjsip/src/test/uri_test.c new file mode 100644 index 00000000..d51c6fe5 --- /dev/null +++ b/pjsip/src/test/uri_test.c @@ -0,0 +1,1091 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "uri_test.c" + + +#define ALPHANUM "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "0123456789" +#define MARK "-_.!~*'()" +#define USER_CHAR ALPHANUM MARK "&=+$,;?/" +#define PASS_CHAR ALPHANUM MARK "&=+$," +#define PARAM_CHAR ALPHANUM MARK "[]/:&+$" + +#define POOL_SIZE 8000 +#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 struct +{ + pj_highprec_t parse_len, print_len, cmp_len; + pj_timestamp parse_time, print_time, cmp_time; +} var; + + +/* URI creator functions. */ +static pjsip_uri *create_uri0( pj_pool_t *pool ); +static pjsip_uri *create_uri1( pj_pool_t *pool ); +static pjsip_uri *create_uri2( pj_pool_t *pool ); +static pjsip_uri *create_uri3( pj_pool_t *pool ); +static pjsip_uri *create_uri4( pj_pool_t *pool ); +static pjsip_uri *create_uri5( pj_pool_t *pool ); +static pjsip_uri *create_uri6( pj_pool_t *pool ); +static pjsip_uri *create_uri7( pj_pool_t *pool ); +static pjsip_uri *create_uri8( pj_pool_t *pool ); +static pjsip_uri *create_uri9( pj_pool_t *pool ); +static pjsip_uri *create_uri10( pj_pool_t *pool ); +static pjsip_uri *create_uri11( pj_pool_t *pool ); +static pjsip_uri *create_uri12( pj_pool_t *pool ); +static pjsip_uri *create_uri13( pj_pool_t *pool ); +static pjsip_uri *create_uri14( pj_pool_t *pool ); +static pjsip_uri *create_uri15( pj_pool_t *pool ); +static pjsip_uri *create_uri16( pj_pool_t *pool ); +static pjsip_uri *create_uri17( pj_pool_t *pool ); +static pjsip_uri *create_uri25( pj_pool_t *pool ); +static pjsip_uri *create_uri26( pj_pool_t *pool ); +static pjsip_uri *create_uri27( pj_pool_t *pool ); +static pjsip_uri *create_uri28( pj_pool_t *pool ); +static pjsip_uri *create_uri29( pj_pool_t *pool ); +static pjsip_uri *create_uri30( pj_pool_t *pool ); +static pjsip_uri *create_uri31( pj_pool_t *pool ); +static pjsip_uri *create_uri32( pj_pool_t *pool ); +static pjsip_uri *create_uri33( pj_pool_t *pool ); +static pjsip_uri *create_uri34( pj_pool_t *pool ); +static pjsip_uri *create_uri35( pj_pool_t *pool ); +static pjsip_uri *create_uri36( pj_pool_t *pool ); +static pjsip_uri *create_uri37( pj_pool_t *pool ); +static pjsip_uri *create_uri38( pj_pool_t *pool ); +static pjsip_uri *create_dummy( pj_pool_t *pool ); + +#define ERR_NOT_EQUAL -1001 +#define ERR_SYNTAX_ERR -1002 + +struct uri_test +{ + pj_status_t status; + char str[PJSIP_MAX_URL_SIZE]; + pjsip_uri *(*creator)(pj_pool_t *pool); + const char *printed; + pj_size_t len; +} uri_test_array[] = +{ + { + PJ_SUCCESS, + "sip:localhost", + &create_uri0 + }, + { + PJ_SUCCESS, + "sip:user@localhost", + &create_uri1 + }, + { + PJ_SUCCESS, + "sip:user:password@localhost:5060", + &create_uri2, }, + { + /* Port is specified should not match unspecified port. */ + ERR_NOT_EQUAL, + "sip:localhost:5060", + &create_uri3 + }, + { + /* All recognized parameters. */ + PJ_SUCCESS, + "sip:localhost;transport=tcp;user=ip;ttl=255;lr;maddr=127.0.0.1;method=ACK", + &create_uri4 + }, + { + /* Params mixed with other params and header params. */ + PJ_SUCCESS, + "sip:localhost;pickup=hurry;user=phone;message=I%20am%20sorry" + "?Subject=Hello%20There&Server=SIP%20Server", + &create_uri5 + }, + { + /* SIPS. */ + PJ_SUCCESS, + "sips:localhost", + &create_uri6, + }, + { + /* Name address */ + PJ_SUCCESS, + "<sip:localhost>", + &create_uri7 + }, + { + /* Name address with display name and SIPS scheme with some redundant + * whitespaced. + */ + PJ_SUCCESS, + " Power Administrator <sips:localhost>", + &create_uri8 + }, + { + /* Name address. */ + PJ_SUCCESS, + " \"User\" <sip:user@localhost:5071>", + &create_uri9 + }, + { + /* Escaped sequence in display name (display=Strange User\"\\\"). */ + PJ_SUCCESS, + " \"Strange User\\\"\\\\\\\"\" <sip:localhost>", + &create_uri10, + }, + { + /* Errorneous escaping in display name. */ + ERR_SYNTAX_ERR, + " \"Rogue User\\\" <sip:localhost>", + &create_uri11, + }, + { + /* Dangling quote in display name, but that should be OK. */ + PJ_SUCCESS, + "Strange User\" <sip:localhost>", + &create_uri12, + }, + { + /* Special characters in parameter value must be quoted. */ + PJ_SUCCESS, + "sip:localhost;pvalue=\"hello world\"", + &create_uri13, + }, + { + /* Excercise strange character sets allowed in display, user, password, + * host, and port. + */ + PJ_SUCCESS, + "This is -. !% *_+`'~ me <sip:a19A&=+$,;?/%2c:%40a&Zz=+$,@" + "my_proxy09.MY-domain.com:9801>", + &create_uri14, + }, + { + /* Another excercise to the allowed character sets to the hostname. */ + PJ_SUCCESS, + "sip:" ALPHANUM "-_.com", + &create_uri15, + }, + { + /* Another excercise to the allowed character sets to the username + * and password. + */ + PJ_SUCCESS, + "sip:" USER_CHAR ":" PASS_CHAR "@host", + &create_uri16, + }, + { + /* Excercise to the pname and pvalue, and mixup of other-param + * between 'recognized' params. + */ + PJ_SUCCESS, + "sip:host;user=ip;" PARAM_CHAR "%21=" PARAM_CHAR "%21" + ";lr;other=1;transport=sctp;other2", + &create_uri17, + }, + { + /* 18: This should trigger syntax error. */ + ERR_SYNTAX_ERR, + "sip:", + &create_dummy, + }, + { + /* 19: Syntax error: whitespace after scheme. */ + ERR_SYNTAX_ERR, + "sip :host", + &create_dummy, + }, + { + /* 20: Syntax error: whitespace before hostname. */ + ERR_SYNTAX_ERR, + "sip: host", + &create_dummy, + }, + { + /* 21: Syntax error: invalid port. */ + ERR_SYNTAX_ERR, + "sip:user:password", + &create_dummy, + }, + { + /* 22: Syntax error: no host. */ + ERR_SYNTAX_ERR, + "sip:user@", + &create_dummy, + }, + { + /* 23: Syntax error: no user/host. */ + ERR_SYNTAX_ERR, + "sip:@", + &create_dummy, + }, + { + /* 24: Syntax error: empty string. */ + ERR_SYNTAX_ERR, + "", + &create_dummy, + }, + { + /* 25: Simple tel: URI with global context */ + PJ_SUCCESS, + "tel:+1-201-555-0123", + &create_uri25, + "tel:+1-201-555-0123" + }, + { + /* 26: Simple tel: URI with local context */ + PJ_SUCCESS, + "tel:7042;phone-context=example.com", + &create_uri26, + "tel:7042;phone-context=example.com" + }, + { + /* 27: Simple tel: URI with local context */ + PJ_SUCCESS, + "tel:863-1234;phone-context=+1-914-555", + &create_uri27, + "tel:863-1234;phone-context=+1-914-555" + }, + { + /* 28: Comparison between local and global number */ + ERR_NOT_EQUAL, + "tel:+1", + &create_uri28, + "tel:+1" + }, + { + /* 29: tel: with some visual chars and spaces */ + PJ_SUCCESS, + "tel:(44).1234-*#+Deaf", + &create_uri29, + "tel:(44).1234-*#+Deaf" + }, + { + /* 30: isub parameters */ + PJ_SUCCESS, + "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1%21+=", + &create_uri30, + "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1!+%3d" + }, + { + /* 31: extension number parsing and encoding */ + PJ_SUCCESS, + "tel:+1;ext=+123", + &create_uri31, + "tel:+1;ext=%2b123" + }, + { + /* 32: context parameter parsing and encoding */ + PJ_SUCCESS, + "tel:911;phone-context=+1-911", + &create_uri32, + "tel:911;phone-context=+1-911" + }, + { + /* 33: case-insensitive comparison */ + PJ_SUCCESS, + "tel:911;phone-context=emergency.example.com", + &create_uri33, + "tel:911;phone-context=emergency.example.com" + }, + { + /* 34: parameter only appears in one URL */ + ERR_NOT_EQUAL, + "tel:911;p1=p1;p2=p2", + &create_uri34, + "tel:911;p1=p1;p2=p2" + }, + { + /* 35: IPv6 in host and maddr parameter */ + PJ_SUCCESS, + "sip:user@[::1];maddr=[::01]", + &create_uri35, + "sip:user@[::1];maddr=[::01]" + }, + { + /* 36: IPv6 in host and maddr, without username */ + PJ_SUCCESS, + "sip:[::1];maddr=[::01]", + &create_uri36, + "sip:[::1];maddr=[::01]" + }, + { + /* 37: Non-ASCII UTF-8 in display name, with quote */ + PJ_SUCCESS, + "\"\xC0\x81\" <sip:localhost>", + &create_uri37, + "\"\xC0\x81\" <sip:localhost>" + }, + { + /* 38: Non-ASCII UTF-8 in display name, without quote */ + PJ_SUCCESS, + "\xC0\x81 <sip:localhost>", + &create_uri38, + "\"\xC0\x81\" <sip:localhost>" + } + +}; + +static pjsip_uri *create_uri0(pj_pool_t *pool) +{ + /* "sip:localhost" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri1(pj_pool_t *pool) +{ + /* "sip:user@localhost" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2( pool, &url->user, "user"); + pj_strdup2( pool, &url->host, "localhost"); + + return (pjsip_uri*) url; +} + +static pjsip_uri *create_uri2(pj_pool_t *pool) +{ + /* "sip:user:password@localhost:5060" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2( pool, &url->user, "user"); + pj_strdup2( pool, &url->passwd, "password"); + pj_strdup2( pool, &url->host, "localhost"); + url->port = 5060; + + return (pjsip_uri*) url; +} + +static pjsip_uri *create_uri3(pj_pool_t *pool) +{ + /* Like: "sip:localhost:5060", but without the port. */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri4(pj_pool_t *pool) +{ + /* "sip:localhost;transport=tcp;user=ip;ttl=255;lr;maddr=127.0.0.1;method=ACK" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2(pool, &url->host, "localhost"); + pj_strdup2(pool, &url->transport_param, "tcp"); + pj_strdup2(pool, &url->user_param, "ip"); + url->ttl_param = 255; + url->lr_param = 1; + pj_strdup2(pool, &url->maddr_param, "127.0.0.1"); + pj_strdup2(pool, &url->method_param, "ACK"); + + return (pjsip_uri*)url; +} + +#define param_add(list,pname,pvalue) \ + do { \ + pjsip_param *param; \ + param=PJ_POOL_ALLOC_T(pool, pjsip_param); \ + param->name = pj_str(pname); \ + param->value = pj_str(pvalue); \ + pj_list_insert_before(&list, param); \ + } while (0) + +static pjsip_uri *create_uri5(pj_pool_t *pool) +{ + /* "sip:localhost;pickup=hurry;user=phone;message=I%20am%20sorry" + "?Subject=Hello%20There&Server=SIP%20Server" + */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 0); + + pj_strdup2(pool, &url->host, "localhost"); + pj_strdup2(pool, &url->user_param, "phone"); + + //pj_strdup2(pool, &url->other_param, ";pickup=hurry;message=I%20am%20sorry"); + param_add(url->other_param, "pickup", "hurry"); + param_add(url->other_param, "message", "I am sorry"); + + //pj_strdup2(pool, &url->header_param, "?Subject=Hello%20There&Server=SIP%20Server"); + param_add(url->header_param, "Subject", "Hello There"); + param_add(url->header_param, "Server", "SIP Server"); + return (pjsip_uri*)url; + +} + +static pjsip_uri *create_uri6(pj_pool_t *pool) +{ + /* "sips:localhost" */ + pjsip_sip_uri *url = pjsip_sip_uri_create(pool, 1); + + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri7(pj_pool_t *pool) +{ + /* "<sip:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri8(pj_pool_t *pool) +{ + /* " Power Administrator <sips:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 1); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "Power Administrator"); + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri9(pj_pool_t *pool) +{ + /* " \"User\" <sip:user@localhost:5071>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "User"); + pj_strdup2(pool, &url->user, "user"); + pj_strdup2(pool, &url->host, "localhost"); + url->port = 5071; + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri10(pj_pool_t *pool) +{ + /* " \"Strange User\\\"\\\\\\\"\" <sip:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "Strange User\\\"\\\\\\\""); + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri11(pj_pool_t *pool) +{ + /* " \"Rogue User\\\" <sip:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "Rogue User\\"); + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri12(pj_pool_t *pool) +{ + /* "Strange User\" <sip:localhost>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "Strange User\""); + pj_strdup2(pool, &url->host, "localhost"); + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri13(pj_pool_t *pool) +{ + /* "sip:localhost;pvalue=\"hello world\"" */ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + pj_strdup2(pool, &url->host, "localhost"); + //pj_strdup2(pool, &url->other_param, ";pvalue=\"hello world\""); + param_add(url->other_param, "pvalue", "\"hello world\""); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri14(pj_pool_t *pool) +{ + /* "This is -. !% *_+`'~ me <sip:a19A&=+$,;?/%2c:%40a&Zz=+$,@my_proxy09.my-domain.com:9801>" */ + pjsip_name_addr *name_addr = pjsip_name_addr_create(pool); + pjsip_sip_uri *url; + + url = pjsip_sip_uri_create(pool, 0); + name_addr->uri = (pjsip_uri*) url; + + pj_strdup2(pool, &name_addr->display, "This is -. !% *_+`'~ me"); + pj_strdup2(pool, &url->user, "a19A&=+$,;?/,"); + pj_strdup2(pool, &url->passwd, "@a&Zz=+$,"); + pj_strdup2(pool, &url->host, "my_proxy09.MY-domain.com"); + url->port = 9801; + return (pjsip_uri*)name_addr; +} + +static pjsip_uri *create_uri15(pj_pool_t *pool) +{ + /* "sip:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.com" */ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + pj_strdup2(pool, &url->host, ALPHANUM "-_.com"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri16(pj_pool_t *pool) +{ + /* "sip:" USER_CHAR ":" PASS_CHAR "@host" */ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + pj_strdup2(pool, &url->user, USER_CHAR); + pj_strdup2(pool, &url->passwd, PASS_CHAR); + pj_strdup2(pool, &url->host, "host"); + return (pjsip_uri*)url; +} + +static pjsip_uri *create_uri17(pj_pool_t *pool) +{ + /* "sip:host;user=ip;" PARAM_CHAR "%21=" PARAM_CHAR "%21;lr;other=1;transport=sctp;other2" */ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + pj_strdup2(pool, &url->host, "host"); + pj_strdup2(pool, &url->user_param, "ip"); + pj_strdup2(pool, &url->transport_param, "sctp"); + param_add(url->other_param, PARAM_CHAR "!", PARAM_CHAR "!"); + param_add(url->other_param, "other", "1"); + param_add(url->other_param, "other2", ""); + url->lr_param = 1; + return (pjsip_uri*)url; +} + + +static pjsip_uri *create_uri25(pj_pool_t *pool) +{ + /* "tel:+1-201-555-0123" */ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("+1-201-555-0123"); + return (pjsip_uri*)uri; +} + +static pjsip_uri *create_uri26(pj_pool_t *pool) +{ + /* tel:7042;phone-context=example.com */ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("7042"); + uri->context = pj_str("example.com"); + return (pjsip_uri*)uri; +} + +static pjsip_uri *create_uri27(pj_pool_t *pool) +{ + /* "tel:863-1234;phone-context=+1-914-555" */ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("863-1234"); + uri->context = pj_str("+1-914-555"); + return (pjsip_uri*)uri; +} + +/* "tel:1" */ +static pjsip_uri *create_uri28(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("1"); + return (pjsip_uri*)uri; +} + +/* "tel:(44).1234-*#+Deaf" */ +static pjsip_uri *create_uri29(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("(44).1234-*#+Deaf"); + return (pjsip_uri*)uri; +} + +/* "tel:+1;isub=/:@&$,-_.!~*'()[]/:&$aA1%21+=" */ +static pjsip_uri *create_uri30(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("+1"); + uri->isub_param = pj_str("/:@&$,-_.!~*'()[]/:&$aA1!+="); + return (pjsip_uri*)uri; +} + +/* "tel:+1;ext=+123" */ +static pjsip_uri *create_uri31(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("+1"); + uri->ext_param = pj_str("+123"); + return (pjsip_uri*)uri; +} + +/* "tel:911;phone-context=+1-911" */ +static pjsip_uri *create_uri32(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("911"); + uri->context = pj_str("+1-911"); + return (pjsip_uri*)uri; +} + +/* "tel:911;phone-context=emergency.example.com" */ +static pjsip_uri *create_uri33(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + + uri->number = pj_str("911"); + uri->context = pj_str("EMERGENCY.EXAMPLE.COM"); + return (pjsip_uri*)uri; +} + +/* "tel:911;p1=p1;p2=p2" */ +static pjsip_uri *create_uri34(pj_pool_t *pool) +{ + pjsip_tel_uri *uri = pjsip_tel_uri_create(pool); + pjsip_param *p; + + uri->number = pj_str("911"); + + p = PJ_POOL_ALLOC_T(pool, pjsip_param); + p->name = p->value = pj_str("p1"); + pj_list_insert_before(&uri->other_param, p); + + return (pjsip_uri*)uri; +} + +/* "sip:user@[::1];maddr=[::01]" */ +static pjsip_uri *create_uri35( pj_pool_t *pool ) +{ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + url->user = pj_str("user"); + url->host = pj_str("::1"); + url->maddr_param = pj_str("::01"); + return (pjsip_uri*)url; +} + +/* "sip:[::1];maddr=[::01]" */ +static pjsip_uri *create_uri36( pj_pool_t *pool ) +{ + pjsip_sip_uri *url; + url = pjsip_sip_uri_create(pool, 0); + url->host = pj_str("::1"); + url->maddr_param = pj_str("::01"); + return (pjsip_uri*)url; + +} + +/* "\"\xC0\x81\" <sip:localhost>" */ +static pjsip_uri *create_uri37( pj_pool_t *pool ) +{ + pjsip_name_addr *name; + pjsip_sip_uri *url; + + name = pjsip_name_addr_create(pool); + name->display = pj_str("\xC0\x81"); + + url = pjsip_sip_uri_create(pool, 0); + url->host = pj_str("localhost"); + + name->uri = (pjsip_uri*)url; + + return (pjsip_uri*)name; + +} + +/* "\xC0\x81 <sip:localhost>" */ +static pjsip_uri *create_uri38( pj_pool_t *pool ) +{ + pjsip_name_addr *name; + pjsip_sip_uri *url; + + name = pjsip_name_addr_create(pool); + name->display = pj_str("\xC0\x81"); + + url = pjsip_sip_uri_create(pool, 0); + url->host = pj_str("localhost"); + + name->uri = (pjsip_uri*)url; + + return (pjsip_uri*)name; + +} + +static pjsip_uri *create_dummy(pj_pool_t *pool) +{ + PJ_UNUSED_ARG(pool); + return NULL; +} + +/*****************************************************************************/ + +/* + * Test one test entry. + */ +static pj_status_t do_uri_test(pj_pool_t *pool, struct uri_test *entry) +{ + pj_status_t status; + int len; + char *input; + pjsip_uri *parsed_uri, *ref_uri; + pj_str_t s1 = {NULL, 0}, s2 = {NULL, 0}; + pj_timestamp t1, t2; + + if (entry->len == 0) + entry->len = pj_ansi_strlen(entry->str); + +#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0 + input = pj_pool_alloc(pool, entry->len + 1); + pj_memcpy(input, entry->str, entry->len); + input[entry->len] = '\0'; +#else + input = entry->str; +#endif + + /* Parse URI text. */ + pj_get_timestamp(&t1); + var.parse_len = var.parse_len + entry->len; + parsed_uri = pjsip_parse_uri(pool, input, entry->len, 0); + if (!parsed_uri) { + /* Parsing failed. If the entry says that this is expected, then + * return OK. + */ + status = entry->status==ERR_SYNTAX_ERR ? PJ_SUCCESS : -10; + if (status != 0) { + PJ_LOG(3,(THIS_FILE, " uri parse error!\n" + " uri='%s'\n", + input)); + } + goto on_return; + } + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.parse_time, &t2); + + /* Create the reference URI. */ + ref_uri = entry->creator(pool); + + /* Print both URI. */ + s1.ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + s2.ptr = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + + pj_get_timestamp(&t1); + len = pjsip_uri_print( PJSIP_URI_IN_OTHER, parsed_uri, s1.ptr, PJSIP_MAX_URL_SIZE); + if (len < 1) { + status = -20; + goto on_return; + } + s1.ptr[len] = '\0'; + s1.slen = len; + + var.print_len = var.print_len + len; + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + 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) { + status = -30; + goto on_return; + } + s2.ptr[len] = '\0'; + s2.slen = len; + + /* Full comparison of parsed URI with reference URI. */ + pj_get_timestamp(&t1); + status = pjsip_uri_cmp(PJSIP_URI_IN_OTHER, parsed_uri, ref_uri); + if (status != 0) { + /* Not equal. See if this is the expected status. */ + status = entry->status==ERR_NOT_EQUAL ? PJ_SUCCESS : -40; + if (status != 0) { + PJ_LOG(3,(THIS_FILE, " uri comparison mismatch, status=%d:\n" + " uri1='%s'\n" + " uri2='%s'", + status, s1.ptr, s2.ptr)); + } + goto on_return; + + } else { + /* Equal. See if this is the expected status. */ + status = entry->status==PJ_SUCCESS ? PJ_SUCCESS : -50; + if (status != PJ_SUCCESS) { + goto on_return; + } + } + + var.cmp_len = var.cmp_len + len; + pj_get_timestamp(&t2); + pj_sub_timestamp(&t2, &t1); + pj_add_timestamp(&var.cmp_time, &t2); + + /* Compare text. */ + if (entry->printed) { + if (pj_strcmp2(&s1, entry->printed) != 0) { + /* Not equal. */ + PJ_LOG(3,(THIS_FILE, " uri print mismatch:\n" + " printed='%s'\n" + " expectd='%s'", + s1.ptr, entry->printed)); + status = -60; + } + } else { + if (pj_strcmp(&s1, &s2) != 0) { + /* Not equal. */ + PJ_LOG(3,(THIS_FILE, " uri print mismatch:\n" + " uri1='%s'\n" + " uri2='%s'", + s1.ptr, s2.ptr)); + status = -70; + } + } + +on_return: + return status; +} + + +static int simple_uri_test(void) +{ + unsigned i; + pj_pool_t *pool; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " simple test")); + for (i=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) { + pool = pjsip_endpt_create_pool(endpt, "", POOL_SIZE, POOL_SIZE); + status = do_uri_test(pool, &uri_test_array[i]); + pjsip_endpt_release_pool(endpt, pool); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " error %d when testing entry %d", + status, i)); + return status; + } + } + + return 0; +} + +#if INCLUDE_BENCHMARKS +static int uri_benchmark(unsigned *p_parse, unsigned *p_print, unsigned *p_cmp) +{ + unsigned i, loop; + pj_status_t status; + pj_timestamp zero; + pj_time_val elapsed; + pj_highprec_t avg_parse, avg_print, avg_cmp, kbytes; + + pj_bzero(&var, 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<LOOP_COUNT; ++loop) { + for (i=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) { + pj_pool_t *pool; + pool = pjsip_endpt_create_pool(endpt, "", POOL_SIZE, POOL_SIZE); + status = do_uri_test(pool, &uri_test_array[i]); + pjsip_endpt_release_pool(endpt, pool); + if (status != PJ_SUCCESS) { + PJ_LOG(3,(THIS_FILE, " error %d when testing entry %d", + status, i)); + pjsip_endpt_release_pool(endpt, pool); + goto on_return; + } + } + } + + kbytes = var.parse_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.parse_time); + avg_parse = pj_elapsed_usec(&zero, &var.parse_time); + pj_highprec_mul(avg_parse, AVERAGE_URL_LEN); + pj_highprec_div(avg_parse, var.parse_len); + avg_parse = 1000000 / avg_parse; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB of urls parsed in %d.%03ds (avg=%d urls/sec)", + (unsigned)(var.parse_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_parse)); + + *p_parse = (unsigned)avg_parse; + + kbytes = var.print_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.print_time); + avg_print = pj_elapsed_usec(&zero, &var.print_time); + pj_highprec_mul(avg_print, AVERAGE_URL_LEN); + pj_highprec_div(avg_print, var.parse_len); + avg_print = 1000000 / avg_print; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB of urls printed in %d.%03ds (avg=%d urls/sec)", + (unsigned)(var.print_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_print)); + + *p_print = (unsigned)avg_print; + + kbytes = var.cmp_len; + pj_highprec_mod(kbytes, 1000000); + pj_highprec_div(kbytes, 100000); + elapsed = pj_elapsed_time(&zero, &var.cmp_time); + avg_cmp = pj_elapsed_usec(&zero, &var.cmp_time); + pj_highprec_mul(avg_cmp, AVERAGE_URL_LEN); + pj_highprec_div(avg_cmp, var.cmp_len); + avg_cmp = 1000000 / avg_cmp; + + PJ_LOG(3,(THIS_FILE, + " %u.%u MB of urls compared in %d.%03ds (avg=%d urls/sec)", + (unsigned)(var.cmp_len/1000000), (unsigned)kbytes, + elapsed.sec, elapsed.msec, + (unsigned)avg_cmp)); + + *p_cmp = (unsigned)avg_cmp; + +on_return: + return status; +} +#endif /* INCLUDE_BENCHMARKS */ + +/*****************************************************************************/ + +int uri_test(void) +{ + enum { COUNT = 1, DETECT=0, PARSE=1, PRINT=2 }; + struct { + unsigned parse; + unsigned print; + unsigned cmp; + } run[COUNT]; + unsigned i, max, avg_len; + char desc[200]; + pj_status_t status; + + status = simple_uri_test(); + if (status != PJ_SUCCESS) + return status; + +#if INCLUDE_BENCHMARKS + for (i=0; i<COUNT; ++i) { + PJ_LOG(3,(THIS_FILE, " benchmarking (%d of %d)...", i+1, COUNT)); + status = uri_benchmark(&run[i].parse, &run[i].print, &run[i].cmp); + if (status != PJ_SUCCESS) + return status; + } + + /* Calculate average URI length */ + for (i=0, avg_len=0; i<PJ_ARRAY_SIZE(uri_test_array); ++i) { + avg_len += uri_test_array[i].len; + } + avg_len /= PJ_ARRAY_SIZE(uri_test_array); + + + /* + * Print maximum parse/sec + */ + for (i=0, max=0; i<COUNT; ++i) + if (run[i].parse > 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 <B>parsed</B> with " + "<tt>pjsip_parse_uri()</tt> per second " + "(tested with %d URI set, with average length of " + "%d chars)", + (int)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<COUNT; ++i) + if (run[i].print > 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 <B>printed</B> with " + "<tt>pjsip_uri_print()</tt> per second " + "(tested with %d URI set, with average length of " + "%d chars)", + (int)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<COUNT; ++i) + if (run[i].cmp > 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 <B>compared</B> with " + "<tt>pjsip_uri_cmp()</tt> per second " + "(tested with %d URI set, with average length of " + "%d chars)", + (int)PJ_ARRAY_SIZE(uri_test_array), avg_len); + + report_ival("uri-cmp-per-sec", max, "URI/sec", desc); + +#endif /* INCLUDE_BENCHMARKS */ + + return PJ_SUCCESS; +} + |