summaryrefslogtreecommitdiff
path: root/pjsip/src/test
diff options
context:
space:
mode:
authorDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
committerDavid M. Lee <dlee@digium.com>2013-01-07 14:24:28 -0600
commitf3ab456a17af1c89a6e3be4d20c5944853df1cb0 (patch)
treed00e1a332cd038a6d906a1ea0ac91e1a4458e617 /pjsip/src/test
Import pjproject-2.0.1
Diffstat (limited to 'pjsip/src/test')
-rw-r--r--pjsip/src/test/dlg_core_test.c23
-rw-r--r--pjsip/src/test/dns_test.c618
-rw-r--r--pjsip/src/test/inv_offer_answer_test.c691
-rw-r--r--pjsip/src/test/main.c92
-rw-r--r--pjsip/src/test/main_rtems.c12
-rw-r--r--pjsip/src/test/main_win32.c1
-rw-r--r--pjsip/src/test/msg_err_test.c104
-rw-r--r--pjsip/src/test/msg_logger.c104
-rw-r--r--pjsip/src/test/msg_test.c2084
-rw-r--r--pjsip/src/test/multipart_test.c266
-rw-r--r--pjsip/src/test/regc_test.c1162
-rw-r--r--pjsip/src/test/test.c398
-rw-r--r--pjsip/src/test/test.h127
-rw-r--r--pjsip/src/test/transport_loop_test.c127
-rw-r--r--pjsip/src/test/transport_tcp_test.c155
-rw-r--r--pjsip/src/test/transport_test.c771
-rw-r--r--pjsip/src/test/transport_udp_test.c128
-rw-r--r--pjsip/src/test/tsx_basic_test.c157
-rw-r--r--pjsip/src/test/tsx_bench.c280
-rw-r--r--pjsip/src/test/tsx_uac_test.c1456
-rw-r--r--pjsip/src/test/tsx_uas_test.c1658
-rw-r--r--pjsip/src/test/txdata_test.c856
-rw-r--r--pjsip/src/test/uri_test.c1097
23 files changed, 12367 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 0000000..ef2f35b
--- /dev/null
+++ b/pjsip/src/test/dlg_core_test.c
@@ -0,0 +1,23 @@
+/* $Id: dlg_core_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <pjsip.h>
+
diff --git a/pjsip/src/test/dns_test.c b/pjsip/src/test/dns_test.c
new file mode 100644
index 0000000..7f6f3b0
--- /dev/null
+++ b/pjsip/src/test/dns_test.c
@@ -0,0 +1,618 @@
+/* $Id: dns_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <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 0000000..7fdb11e
--- /dev/null
+++ b/pjsip/src/test/inv_offer_answer_test.c
@@ -0,0 +1,691 @@
+/* $Id: inv_offer_answer_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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;
+
+ PJ_UNUSED_ARG(offer);
+
+ 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_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(p_offer);
+
+ pj_assert(!"Should not happen");
+}
+
+static void on_media_update(pjsip_inv_session *inv_ses,
+ pj_status_t status)
+{
+ PJ_UNUSED_ARG(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;
+
+ PJ_UNUSED_ARG(e);
+
+ 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)
+{
+ PJ_UNUSED_ARG(first_set);
+ PJ_UNUSED_ARG(res);
+
+ return NULL;
+}
+
+
+static void on_new_session(pjsip_inv_session *inv, pjsip_event *e)
+{
+ PJ_UNUSED_ARG(inv);
+ PJ_UNUSED_ARG(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 0000000..423e8cb
--- /dev/null
+++ b/pjsip/src/test/main.c
@@ -0,0 +1,92 @@
+/* $Id: main.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <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;
+
+ PJ_UNUSED_ARG(argc);
+
+ /* 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 0000000..a4d14e5
--- /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 0000000..3043a39
--- /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 0000000..d3c5636
--- /dev/null
+++ b/pjsip/src/test/msg_err_test.c
@@ -0,0 +1,104 @@
+/* $Id: msg_err_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <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)
+{
+ PJ_UNUSED_ARG(msg);
+ PJ_UNUSED_ARG(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 0000000..aaeda7d
--- /dev/null
+++ b/pjsip/src/test/msg_logger.c
@@ -0,0 +1,104 @@
+/* $Id: msg_logger.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <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 0000000..f64906c
--- /dev/null
+++ b/pjsip/src/test/msg_test.c
@@ -0,0 +1,2084 @@
+/* $Id: msg_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <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 = pjsip_hdr_print_on(hdr1, str1.ptr, BUFLEN);
+ if (len < 0) {
+ status = -40;
+ goto on_return;
+ }
+ str1.ptr[len] = '\0';
+ str1.slen = len;
+
+ len = pjsip_hdr_print_on(hdr2, str2.ptr, BUFLEN);
+ if (len < 0) {
+ 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;
+ pjsip_param *prm;
+ 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");
+ prm = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ prm->name = pj_str("charset");
+ prm->value = pj_str("ISO-8859-4");
+ pj_list_push_back(&ctype->media.param, prm);
+
+ /* "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)
+{
+ char stbuf[] = "SIP/2.0 180 Ringing like it never rings before";
+ unsigned i;
+ pjsip_status_line st_line;
+ pj_status_t status;
+
+ PJ_LOG(3,(THIS_FILE, " simple test.."));
+
+ status = pjsip_parse_status_line(stbuf, pj_ansi_strlen(stbuf), &st_line);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ 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_pool_t *pool;
+ int i, loop;
+ pj_timestamp zero;
+ pj_time_val elapsed;
+ pj_highprec_t avg_detect, avg_parse, avg_print, kbytes;
+ pj_status_t status = PJ_SUCCESS;
+
+ 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(&param->name, PARAM_CHAR))
+ return -942;
+
+ if (pj_strcmp2(&param->value, PARAM_CHAR))
+ return -943;
+
+ param = param->next;
+ if (pj_strcmp2(&param->name, "p1"))
+ return -942;
+ if (pj_strcmp2(&param->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(&param->name, "p0"))
+ return -952;
+ if (pj_strcmp2(&param->value, "a"))
+ return -953;
+
+ param = param->next;
+ if (pj_strcmp2(&param->name, "p1"))
+ return -954;
+ if (pj_strcmp2(&param->value, "\"ab:;cd\""))
+ return -955;
+
+ param = param->next;
+ if (pj_strcmp2(&param->name, "p2"))
+ return -956;
+ if (pj_strcmp2(&param->value, "ab:cd"))
+ return -957;
+
+ param = param->next;
+ if (pj_strcmp2(&param->name, "p3"))
+ return -958;
+ if (pj_strcmp2(&param->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(&param->name, "p1"))
+ return -1630;
+
+ if (pj_strcmp2(&param->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;
+ const pjsip_param *prm;
+
+ 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.
+ */
+ prm = hdr->media.param.next;
+ if (prm == &hdr->media.param) return -1940;
+ if (pj_strcmp2(&prm->name, "p0")) return -1941;
+ if (pj_strcmp2(&prm->value, "a")) return -1942;
+
+ prm = prm->next;
+ if (prm == &hdr->media.param) return -1950;
+ if (pj_strcmp2(&prm->name, "p1")) { PJ_LOG(3,("", "%.*s", (int)prm->name.slen, prm->name.ptr)); return -1951; }
+ if (pj_strcmp2(&prm->value, "\"ab:;cd\"")) { PJ_LOG(3,("", "%.*s", (int)prm->value.slen, prm->value.ptr)); return -1952; }
+
+ prm = prm->next;
+ if (prm == &hdr->media.param) return -1960;
+ if (pj_strcmp2(&prm->name, "p2")) return -1961;
+ if (pj_strcmp2(&prm->value, "ab:cd")) return -1962;
+
+ prm = prm->next;
+ if (prm == &hdr->media.param) return -1970;
+ if (pj_strcmp2(&prm->name, "p3")) return -1971;
+ if (pj_strcmp2(&prm->value, "")) return -1972;
+
+ 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 < 0 || 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/multipart_test.c b/pjsip/src/test/multipart_test.c
new file mode 100644
index 0000000..30d6126
--- /dev/null
+++ b/pjsip/src/test/multipart_test.c
@@ -0,0 +1,266 @@
+/* $Id: multipart_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <pjsip.h>
+#include <pjlib.h>
+
+#define THIS_FILE ""
+
+/*
+ * multipart tests
+ */
+typedef pj_status_t (*verify_ptr)(pj_pool_t*,pjsip_msg_body*);
+
+static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body);
+
+static struct test_t
+{
+ char *ctype;
+ char *csubtype;
+ char *boundary;
+ const char *msg;
+ verify_ptr verify;
+} p_tests[] =
+{
+ {
+ /* Content-type */
+ "multipart", "mixed", "12345",
+
+ /* Body: */
+ "This is the prolog, which should be ignored.\r\n"
+ "--12345\r\n"
+ "Content-Type: my/text\r\n"
+ "\r\n"
+ "Header and body\r\n"
+ "--12345 \t\r\n"
+ "Content-Type: hello/world\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ "--12345\r\n"
+ "\r\n"
+ "Body only\r\n"
+ "--12345\r\n"
+ "Content-Type: multipart/mixed;boundary=6789\r\n"
+ "\r\n"
+ "Prolog of the subbody, should be ignored\r\n"
+ "--6789\r\n"
+ "\r\n"
+ "Subbody\r\n"
+ "--6789--\r\n"
+ "Epilogue of the subbody, should be ignored\r\n"
+ "--12345--\r\n"
+ "This is epilogue, which should be ignored too",
+
+ &verify1
+ }
+};
+
+static void init_media_type(pjsip_media_type *mt,
+ char *type, char *subtype, char *boundary)
+{
+ static pjsip_param prm;
+
+ pjsip_media_type_init(mt, NULL, NULL);
+ if (type) mt->type = pj_str(type);
+ if (subtype) mt->subtype = pj_str(subtype);
+ if (boundary) {
+ pj_list_init(&prm);
+ prm.name = pj_str("boundary");
+ prm.value = pj_str(boundary);
+ pj_list_push_back(&mt->param, &prm);
+ }
+}
+
+static int verify_part(pjsip_multipart_part *part,
+ char *h_content_type,
+ char *h_content_subtype,
+ char *boundary,
+ int h_content_length,
+ const char *body)
+{
+ pjsip_ctype_hdr *ctype_hdr = NULL;
+ pjsip_clen_hdr *clen_hdr = NULL;
+ pjsip_hdr *hdr;
+ pj_str_t the_body;
+
+ hdr = part->hdr.next;
+ while (hdr != &part->hdr) {
+ if (hdr->type == PJSIP_H_CONTENT_TYPE)
+ ctype_hdr = (pjsip_ctype_hdr*)hdr;
+ else if (hdr->type == PJSIP_H_CONTENT_LENGTH)
+ clen_hdr = (pjsip_clen_hdr*)hdr;
+ hdr = hdr->next;
+ }
+
+ if (h_content_type) {
+ pjsip_media_type mt;
+
+ if (ctype_hdr == NULL)
+ return -10;
+
+ init_media_type(&mt, h_content_type, h_content_subtype, boundary);
+
+ if (pjsip_media_type_cmp(&ctype_hdr->media, &mt, 2) != 0)
+ return -20;
+
+ } else {
+ if (ctype_hdr)
+ return -30;
+ }
+
+ if (h_content_length >= 0) {
+ if (clen_hdr == NULL)
+ return -50;
+ if (clen_hdr->len != h_content_length)
+ return -60;
+ } else {
+ if (clen_hdr)
+ return -70;
+ }
+
+ the_body.ptr = (char*)part->body->data;
+ the_body.slen = part->body->len;
+
+ if (pj_strcmp2(&the_body, body) != 0)
+ return -90;
+
+ return 0;
+}
+
+static pj_status_t verify1(pj_pool_t *pool, pjsip_msg_body *body)
+{
+ pjsip_media_type mt;
+ pjsip_multipart_part *part;
+ int rc;
+
+ PJ_UNUSED_ARG(pool);
+
+ /* Check content-type: "multipart/mixed;boundary=12345" */
+ init_media_type(&mt, "multipart", "mixed", "12345");
+ if (pjsip_media_type_cmp(&body->content_type, &mt, 2) != 0)
+ return -200;
+
+ /* First part:
+ "Content-Type: my/text\r\n"
+ "\r\n"
+ "Header and body\r\n"
+ */
+ part = pjsip_multipart_get_first_part(body);
+ if (!part)
+ return -210;
+ if (verify_part(part, "my", "text", NULL, -1, "Header and body"))
+ return -220;
+
+ /* Next part:
+ "Content-Type: hello/world\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ */
+ part = pjsip_multipart_get_next_part(body, part);
+ if (!part)
+ return -230;
+ if ((rc=verify_part(part, "hello", "world", NULL, 0, ""))!=0) {
+ PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc));
+ return -240;
+ }
+
+ /* Next part:
+ "\r\n"
+ "Body only\r\n"
+ */
+ part = pjsip_multipart_get_next_part(body, part);
+ if (!part)
+ return -260;
+ if (verify_part(part, NULL, NULL, NULL, -1, "Body only"))
+ return -270;
+
+ /* Next part:
+ "Content-Type: multipart/mixed;boundary=6789\r\n"
+ "\r\n"
+ "Prolog of the subbody, should be ignored\r\n"
+ "--6789\r\n"
+ "\r\n"
+ "Subbody\r\n"
+ "--6789--\r\n"
+ "Epilogue of the subbody, should be ignored\r\n"
+
+ */
+ part = pjsip_multipart_get_next_part(body, part);
+ if (!part)
+ return -280;
+ if ((rc=verify_part(part, "multipart", "mixed", "6789", -1,
+ "Prolog of the subbody, should be ignored\r\n"
+ "--6789\r\n"
+ "\r\n"
+ "Subbody\r\n"
+ "--6789--\r\n"
+ "Epilogue of the subbody, should be ignored"))!=0) {
+ PJ_LOG(3,(THIS_FILE, " err: verify_part rc=%d", rc));
+ return -290;
+ }
+
+ return 0;
+}
+
+static int parse_test(void)
+{
+ unsigned i;
+
+ for (i=0; i<PJ_ARRAY_SIZE(p_tests); ++i) {
+ pj_pool_t *pool;
+ pjsip_media_type ctype;
+ pjsip_msg_body *body;
+ pj_str_t str;
+ int rc;
+
+ pool = pjsip_endpt_create_pool(endpt, NULL, 512, 512);
+
+ init_media_type(&ctype, p_tests[i].ctype, p_tests[i].csubtype,
+ p_tests[i].boundary);
+
+ pj_strdup2_with_null(pool, &str, p_tests[i].msg);
+ body = pjsip_multipart_parse(pool, str.ptr, str.slen, &ctype, 0);
+ if (!body)
+ return -100;
+
+ if (p_tests[i].verify) {
+ rc = p_tests[i].verify(pool, body);
+ } else {
+ rc = 0;
+ }
+
+ pj_pool_release(pool);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+int multipart_test(void)
+{
+ int rc;
+
+ rc = parse_test();
+ if (rc)
+ return rc;
+
+ return rc;
+}
+
diff --git a/pjsip/src/test/regc_test.c b/pjsip/src/test/regc_test.c
new file mode 100644
index 0000000..45912a3
--- /dev/null
+++ b/pjsip/src/test/regc_test.c
@@ -0,0 +1,1162 @@
+/* $Id: regc_test.c 4094 2012-04-26 09:31:00Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <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() */
+ &regs_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,
+ &registrar.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(&registrar.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, &regc);
+ 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, &regc);
+ 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, &regc);
+ 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, &regc);
+ 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, &regc);
+
+ 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, 502, 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, &registrar.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,
+ &reg_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(&registrar_uri);
+ if (rc != 0)
+ goto on_return;
+
+ /* Send error on refresh without destroy on callback */
+ rc = refresh_error(&registrar_uri, PJ_FALSE);
+ if (rc != 0)
+ goto on_return;
+
+ /* Send error on refresh, destroy on callback */
+ rc = refresh_error(&registrar_uri, PJ_TRUE);
+ if (rc != 0)
+ goto on_return;
+
+ /* Updating contact */
+ rc = update_test(&registrar_uri);
+ if (rc != 0)
+ goto on_return;
+
+ /* Send error during auth, don't destroy on callback */
+ rc = auth_send_error(&registrar_uri, PJ_FALSE);
+ if (rc != 0)
+ goto on_return;
+
+ /* Send error during auth, destroy on callback */
+ rc = auth_send_error(&registrar_uri, PJ_FALSE);
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ if (registrar.mod.id != -1) {
+ pjsip_endpt_unregister_module(endpt, &registrar.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 0000000..4d17939
--- /dev/null
+++ b/pjsip/src/test/test.c
@@ -0,0 +1,398 @@
+/* $Id: test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include "test.h"
+#include <pjlib.h>
+#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)
+{
+ PJ_UNUSED_ARG(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(&timestamp);
+ report_ival("timestamp", timestamp.sec, "", "System timestamp of the test");
+
+
+ /* Write time of day */
+ pj_time_decode(&timestamp, &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_MULTIPART_TEST
+ DO_TEST(multipart_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 0000000..d046682
--- /dev/null
+++ b/pjsip/src/test/test.h
@@ -0,0 +1,127 @@
+/* $Id: test.h 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#ifndef __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_MULTIPART_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 multipart_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 0000000..6c60036
--- /dev/null
+++ b/pjsip/src/test/transport_loop_test.c
@@ -0,0 +1,127 @@
+/* $Id: transport_loop_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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 0000000..4bc8c1c
--- /dev/null
+++ b/pjsip/src/test/transport_tcp_test.c
@@ -0,0 +1,155 @@
+/* $Id: transport_tcp_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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 0000000..409ec5a
--- /dev/null
+++ b/pjsip/src/test/transport_test.c
@@ -0,0 +1,771 @@
+/* $Id: transport_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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)
+{
+ PJ_UNUSED_ARG(stateless_data);
+
+ 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_UNUSED_ARG(tp_type);
+ PJ_UNUSED_ARG(ref_tp);
+
+ 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 };
+
+ PJ_UNUSED_ARG(arg);
+
+ /* 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_UNUSED_ARG(tp_type);
+ PJ_UNUSED_ARG(ref_tp);
+
+ 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;
+
+ pj_strset(&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' + (char)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 0000000..3c92632
--- /dev/null
+++ b/pjsip/src/test/transport_udp_test.c
@@ -0,0 +1,128 @@
+/* $Id: transport_udp_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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 0000000..20b93a0
--- /dev/null
+++ b/pjsip/src/test/tsx_basic_test.c
@@ -0,0 +1,157 @@
+/* $Id: tsx_basic_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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 0000000..834f444
--- /dev/null
+++ b/pjsip/src/test/tsx_bench.c
@@ -0,0 +1,280 @@
+/* $Id: tsx_bench.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <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 0000000..c8f3074
--- /dev/null
+++ b/pjsip/src/test/tsx_uac_test.c
@@ -0,0 +1,1456 @@
+/* $Id: tsx_uac_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ 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;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ 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_UNUSED_ARG(dummy);
+
+ 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 = 0, 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 = 0;
+
+ 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_SUCCESS;
+
+ 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 = 0;
+ 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 0000000..045e958
--- /dev/null
+++ b/pjsip/src/test/tsx_uas_test.c
@@ -0,0 +1,1658 @@
+/* $Id: tsx_uas_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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.
+ **
+ **/
+
+#define TEST1_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test1")
+#define TEST2_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test2")
+#define TEST3_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test3")
+#define TEST4_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test4")
+#define TEST5_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test5")
+#define TEST6_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test6")
+#define TEST7_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test7")
+#define TEST8_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test8")
+#define TEST9_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test9")
+#define TEST10_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test10")
+#define TEST11_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test11")
+#define TEST12_BRANCH_ID (PJSIP_RFC3261_BRANCH_ID "-UAS-Test12")
+//#define 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;
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ 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 0000000..fd111e2
--- /dev/null
+++ b/pjsip/src/test/txdata_test.c
@@ -0,0 +1,856 @@
+/* $Id: txdata_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "test.h"
+#include <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 contact;
+ 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"));
+
+ /* Due to #930, contact argument is now parsed as Contact header, so
+ * must enclose it with <> to make it be parsed as URI.
+ */
+ pj_ansi_snprintf(msgbuf, sizeof(msgbuf), "<%.*s>",
+ (int)target.slen, target.ptr);
+ contact.ptr = msgbuf;
+ contact.slen = strlen(msgbuf);
+
+ /* Create request with header param in target URI. */
+ status = pjsip_endpt_create_request(endpt, &pjsip_invite_method, &target,
+ &target, &target, &contact, 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(&param->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(&param->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(&param->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(&param->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(&param->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(&param->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(&param->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 0000000..8527faa
--- /dev/null
+++ b/pjsip/src/test/uri_test.c
@@ -0,0 +1,1097 @@
+/* $Id: uri_test.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include "test.h"
+#include <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_SUCCESS;
+ 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);
+ if (avg_parse == 0)
+ avg_parse = 1;
+ 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);
+ if (avg_print == 0)
+ avg_print = 1;
+ 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);
+ if (avg_cmp == 0)
+ avg_cmp = 1;
+ 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;
+}
+