summaryrefslogtreecommitdiff
path: root/pjsip/src/pjsip
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip/src/pjsip')
-rw-r--r--pjsip/src/pjsip/sip_auth_aka.c204
-rw-r--r--pjsip/src/pjsip/sip_auth_client.c1189
-rw-r--r--pjsip/src/pjsip/sip_auth_msg.c343
-rw-r--r--pjsip/src/pjsip/sip_auth_parser.c308
-rw-r--r--pjsip/src/pjsip/sip_auth_parser_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_auth_server.c225
-rw-r--r--pjsip/src/pjsip/sip_config.c54
-rw-r--r--pjsip/src/pjsip/sip_dialog.c2250
-rw-r--r--pjsip/src/pjsip/sip_dialog_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_endpoint.c1245
-rw-r--r--pjsip/src/pjsip/sip_endpoint_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_errno.c211
-rw-r--r--pjsip/src/pjsip/sip_msg.c2218
-rw-r--r--pjsip/src/pjsip/sip_multipart.c658
-rw-r--r--pjsip/src/pjsip/sip_parser.c2379
-rw-r--r--pjsip/src/pjsip/sip_parser_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_resolve.c520
-rw-r--r--pjsip/src/pjsip/sip_tel_uri.c448
-rw-r--r--pjsip/src/pjsip/sip_tel_uri_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_transaction.c3277
-rw-r--r--pjsip/src/pjsip/sip_transport.c1942
-rw-r--r--pjsip/src/pjsip/sip_transport_loop.c509
-rw-r--r--pjsip/src/pjsip/sip_transport_tcp.c1417
-rw-r--r--pjsip/src/pjsip/sip_transport_tls.c1610
-rw-r--r--pjsip/src/pjsip/sip_transport_udp.c1095
-rw-r--r--pjsip/src/pjsip/sip_transport_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_ua_layer.c978
-rw-r--r--pjsip/src/pjsip/sip_uri.c729
-rw-r--r--pjsip/src/pjsip/sip_util.c1850
-rw-r--r--pjsip/src/pjsip/sip_util_proxy.c389
-rw-r--r--pjsip/src/pjsip/sip_util_proxy_wrap.cpp24
-rw-r--r--pjsip/src/pjsip/sip_util_statefull.c192
-rw-r--r--pjsip/src/pjsip/sip_util_wrap.cpp24
33 files changed, 26432 insertions, 0 deletions
diff --git a/pjsip/src/pjsip/sip_auth_aka.c b/pjsip/src/pjsip/sip_auth_aka.c
new file mode 100644
index 0000000..b3c2dde
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_aka.c
@@ -0,0 +1,204 @@
+/* $Id: sip_auth_aka.c 3999 2012-03-30 07:10:13Z 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 <pjsip/sip_auth_aka.h>
+#include <pjsip/sip_errno.h>
+#include <pjlib-util/base64.h>
+#include <pjlib-util/md5.h>
+#include <pjlib-util/hmac_md5.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if PJSIP_HAS_DIGEST_AKA_AUTH
+
+#include "../../third_party/milenage/milenage.h"
+
+/*
+ * Create MD5-AKA1 digest response.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_create_aka_response(
+ pj_pool_t *pool,
+ const pjsip_digest_challenge*chal,
+ const pjsip_cred_info *cred,
+ const pj_str_t *method,
+ pjsip_digest_credential *auth)
+{
+ pj_str_t nonce_bin;
+ int aka_version;
+ const pj_str_t pjsip_AKAv1_MD5 = { "AKAv1-MD5", 9 };
+ const pj_str_t pjsip_AKAv2_MD5 = { "AKAv2-MD5", 9 };
+ pj_uint8_t *chal_rand, *chal_sqnxoraka, *chal_mac;
+ pj_uint8_t k[PJSIP_AKA_KLEN];
+ pj_uint8_t op[PJSIP_AKA_OPLEN];
+ pj_uint8_t amf[PJSIP_AKA_AMFLEN];
+ pj_uint8_t res[PJSIP_AKA_RESLEN];
+ pj_uint8_t ck[PJSIP_AKA_CKLEN];
+ pj_uint8_t ik[PJSIP_AKA_IKLEN];
+ pj_uint8_t ak[PJSIP_AKA_AKLEN];
+ pj_uint8_t sqn[PJSIP_AKA_SQNLEN];
+ pj_uint8_t xmac[PJSIP_AKA_MACLEN];
+ pjsip_cred_info aka_cred;
+ int i, len;
+ pj_status_t status;
+
+ /* Check the algorithm is supported. */
+ if (chal->algorithm.slen==0 || pj_stricmp2(&chal->algorithm, "md5") == 0) {
+ /*
+ * A normal MD5 authentication is requested. Fallbackt to the usual
+ * MD5 digest creation.
+ */
+ pjsip_auth_create_digest(&auth->response, &auth->nonce, &auth->nc,
+ &auth->cnonce, &auth->qop, &auth->uri,
+ &auth->realm, cred, method);
+ return PJ_SUCCESS;
+
+ } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5) == 0) {
+ /*
+ * AKA version 1 is requested.
+ */
+ aka_version = 1;
+
+ } else if (pj_stricmp(&chal->algorithm, &pjsip_AKAv2_MD5) == 0) {
+ /*
+ * AKA version 2 is requested.
+ */
+ aka_version = 2;
+
+ } else {
+ /* Unsupported algorithm */
+ return PJSIP_EINVALIDALGORITHM;
+ }
+
+ /* Decode nonce */
+ nonce_bin.slen = len = PJ_BASE64_TO_BASE256_LEN(chal->nonce.slen);
+ nonce_bin.ptr = pj_pool_alloc(pool, nonce_bin.slen + 1);
+ status = pj_base64_decode(&chal->nonce, (pj_uint8_t*)nonce_bin.ptr, &len);
+ nonce_bin.slen = len;
+ if (status != PJ_SUCCESS)
+ return PJSIP_EAUTHINNONCE;
+
+ if (nonce_bin.slen < PJSIP_AKA_RANDLEN + PJSIP_AKA_AUTNLEN)
+ return PJSIP_EAUTHINNONCE;
+
+ /* Get RAND, AUTN, and MAC */
+ chal_rand = (pj_uint8_t*)(nonce_bin.ptr + 0);
+ chal_sqnxoraka = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN);
+ chal_mac = (pj_uint8_t*) (nonce_bin.ptr + PJSIP_AKA_RANDLEN +
+ PJSIP_AKA_SQNLEN + PJSIP_AKA_AMFLEN);
+
+ /* Copy k. op, and amf */
+ pj_bzero(k, sizeof(k));
+ pj_bzero(op, sizeof(op));
+ pj_bzero(amf, sizeof(amf));
+
+ if (cred->ext.aka.k.slen)
+ pj_memcpy(k, cred->ext.aka.k.ptr, cred->ext.aka.k.slen);
+ if (cred->ext.aka.op.slen)
+ pj_memcpy(op, cred->ext.aka.op.ptr, cred->ext.aka.op.slen);
+ if (cred->ext.aka.amf.slen)
+ pj_memcpy(amf, cred->ext.aka.amf.ptr, cred->ext.aka.amf.slen);
+
+ /* Given key K and random challenge RAND, compute response RES,
+ * confidentiality key CK, integrity key IK and anonymity key AK.
+ */
+ f2345(k, chal_rand, res, ck, ik, ak, op);
+
+ /* Compute sequence number SQN */
+ for (i=0; i<PJSIP_AKA_SQNLEN; ++i)
+ sqn[i] = (pj_uint8_t) (chal_sqnxoraka[i] ^ ak[i]);
+
+ /* Verify MAC in the challenge */
+ /* Compute XMAC */
+ f1(k, chal_rand, sqn, amf, xmac, op);
+
+ if (pj_memcmp(chal_mac, xmac, PJSIP_AKA_MACLEN) != 0) {
+ return PJSIP_EAUTHINNONCE;
+ }
+
+ /* Build a temporary credential info to create MD5 digest, using
+ * "res" as the password.
+ */
+ pj_memcpy(&aka_cred, cred, sizeof(aka_cred));
+ aka_cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
+
+ /* Create a response */
+ if (aka_version == 1) {
+ /*
+ * For AKAv1, the password is RES
+ */
+ aka_cred.data.ptr = (char*)res;
+ aka_cred.data.slen = PJSIP_AKA_RESLEN;
+
+ pjsip_auth_create_digest(&auth->response, &chal->nonce,
+ &auth->nc, &auth->cnonce, &auth->qop,
+ &auth->uri, &chal->realm, &aka_cred, method);
+
+ } else if (aka_version == 2) {
+
+ /*
+ * For AKAv2, password is base64 encoded [1] parameters:
+ * PRF(RES||IK||CK,"http-digest-akav2-password")
+ *
+ * The pseudo-random function (PRF) is HMAC-MD5 in this case.
+ */
+
+ pj_str_t resikck;
+ const pj_str_t AKAv2_Passwd = { "http-digest-akav2-password", 26 };
+ pj_uint8_t hmac_digest[16];
+ char tmp_buf[48];
+ int hmac64_len;
+
+ resikck.slen = PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN + PJSIP_AKA_CKLEN;
+ pj_assert(resikck.slen <= PJ_ARRAY_SIZE(tmp_buf));
+ resikck.ptr = tmp_buf;
+ pj_memcpy(resikck.ptr + 0, res, PJSIP_AKA_RESLEN);
+ pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN, ik, PJSIP_AKA_IKLEN);
+ pj_memcpy(resikck.ptr + PJSIP_AKA_RESLEN + PJSIP_AKA_IKLEN,
+ ck, PJSIP_AKA_CKLEN);
+
+ pj_hmac_md5((const pj_uint8_t*)AKAv2_Passwd.ptr, AKAv2_Passwd.slen,
+ (const pj_uint8_t*)resikck.ptr, resikck.slen,
+ hmac_digest);
+
+ aka_cred.data.slen = hmac64_len =
+ PJ_BASE256_TO_BASE64_LEN(PJ_ARRAY_SIZE(hmac_digest));
+ pj_assert(aka_cred.data.slen+1 <= PJ_ARRAY_SIZE(tmp_buf));
+ aka_cred.data.ptr = tmp_buf;
+ pj_base64_encode(hmac_digest, PJ_ARRAY_SIZE(hmac_digest),
+ aka_cred.data.ptr, &len);
+ aka_cred.data.slen = hmac64_len;
+
+ pjsip_auth_create_digest(&auth->response, &chal->nonce,
+ &auth->nc, &auth->cnonce, &auth->qop,
+ &auth->uri, &chal->realm, &aka_cred, method);
+
+ } else {
+ pj_assert(!"Bug!");
+ return PJ_EBUG;
+ }
+
+ /* Done */
+ return PJ_SUCCESS;
+}
+
+
+#endif /* PJSIP_HAS_DIGEST_AKA_AUTH */
+
diff --git a/pjsip/src/pjsip/sip_auth_client.c b/pjsip/src/pjsip/sip_auth_client.c
new file mode 100644
index 0000000..ae850b1
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_client.c
@@ -0,0 +1,1189 @@
+/* $Id: sip_auth_client.c 3999 2012-03-30 07:10:13Z 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 <pjsip/sip_auth.h>
+#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */
+#include <pjsip/sip_auth_aka.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_util.h>
+#include <pjlib-util/md5.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/guid.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+
+
+
+/* A macro just to get rid of type mismatch between char and unsigned char */
+#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, len)
+
+/* Logging. */
+#define THIS_FILE "sip_auth_client.c"
+#if 0
+# define AUTH_TRACE_(expr) PJ_LOG(3, expr)
+#else
+# define AUTH_TRACE_(expr)
+#endif
+
+#define PASSWD_MASK 0x000F
+#define EXT_MASK 0x00F0
+
+
+static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src)
+{
+ dst->slen = src->slen;
+
+ if (dst->slen) {
+ dst->ptr = (char*) pj_pool_alloc(pool, src->slen);
+ pj_memcpy(dst->ptr, src->ptr, src->slen);
+ } else {
+ dst->ptr = NULL;
+ }
+}
+
+PJ_DEF(void) pjsip_cred_info_dup(pj_pool_t *pool,
+ pjsip_cred_info *dst,
+ const pjsip_cred_info *src)
+{
+ pj_memcpy(dst, src, sizeof(pjsip_cred_info));
+
+ pj_strdup_with_null(pool, &dst->realm, &src->realm);
+ pj_strdup_with_null(pool, &dst->scheme, &src->scheme);
+ pj_strdup_with_null(pool, &dst->username, &src->username);
+ pj_strdup_with_null(pool, &dst->data, &src->data);
+
+ if ((dst->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+ dup_bin(pool, &dst->ext.aka.k, &src->ext.aka.k);
+ dup_bin(pool, &dst->ext.aka.op, &src->ext.aka.op);
+ dup_bin(pool, &dst->ext.aka.amf, &src->ext.aka.amf);
+ }
+}
+
+
+PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1,
+ const pjsip_cred_info *cred2)
+{
+ int result;
+
+ result = pj_strcmp(&cred1->realm, &cred2->realm);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->scheme, &cred2->scheme);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->username, &cred2->username);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->data, &cred2->data);
+ if (result) goto on_return;
+ result = (cred1->data_type != cred2->data_type);
+ if (result) goto on_return;
+
+ if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+ result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op);
+ if (result) goto on_return;
+ result = pj_strcmp(&cred1->ext.aka.amf, &cred2->ext.aka.amf);
+ if (result) goto on_return;
+ }
+
+on_return:
+ return result;
+}
+
+PJ_DEF(void) pjsip_auth_clt_pref_dup( pj_pool_t *pool,
+ pjsip_auth_clt_pref *dst,
+ const pjsip_auth_clt_pref *src)
+{
+ pj_memcpy(dst, src, sizeof(pjsip_auth_clt_pref));
+ pj_strdup_with_null(pool, &dst->algorithm, &src->algorithm);
+}
+
+
+/* Transform digest to string.
+ * output must be at least PJSIP_MD5STRLEN+1 bytes.
+ *
+ * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
+ */
+static void digest2str(const unsigned char digest[], char *output)
+{
+ int i;
+ for (i = 0; i<16; ++i) {
+ pj_val_to_hex_digit(digest[i], output);
+ output += 2;
+ }
+}
+
+
+/*
+ * Create response digest based on the parameters and store the
+ * digest ASCII in 'result'.
+ */
+PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
+ const pj_str_t *nonce,
+ const pj_str_t *nc,
+ const pj_str_t *cnonce,
+ const pj_str_t *qop,
+ const pj_str_t *uri,
+ const pj_str_t *realm,
+ const pjsip_cred_info *cred_info,
+ const pj_str_t *method)
+{
+ char ha1[PJSIP_MD5STRLEN];
+ char ha2[PJSIP_MD5STRLEN];
+ unsigned char digest[16];
+ pj_md5_context pms;
+
+ pj_assert(result->slen >= PJSIP_MD5STRLEN);
+
+ AUTH_TRACE_((THIS_FILE, "Begin creating digest"));
+
+ if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) {
+ /***
+ *** ha1 = MD5(username ":" realm ":" password)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, realm->ptr, realm->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen);
+ pj_md5_final(&pms, digest);
+
+ digest2str(digest, ha1);
+
+ } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) {
+ pj_assert(cred_info->data.slen == 32);
+ pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
+ } else {
+ pj_assert(!"Invalid data_type");
+ }
+
+ AUTH_TRACE_((THIS_FILE, " ha1=%.32s", ha1));
+
+ /***
+ *** ha2 = MD5(method ":" req_uri)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, method->ptr, method->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, uri->ptr, uri->slen);
+ pj_md5_final(&pms, digest);
+ digest2str(digest, ha2);
+
+ AUTH_TRACE_((THIS_FILE, " ha2=%.32s", ha2));
+
+ /***
+ *** When qop is not used:
+ *** response = MD5(ha1 ":" nonce ":" ha2)
+ ***
+ *** When qop=auth is used:
+ *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
+ ***/
+ pj_md5_init(&pms);
+ MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, nonce->ptr, nonce->slen);
+ if (qop && qop->slen != 0) {
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, nc->ptr, nc->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, qop->ptr, qop->slen);
+ }
+ MD5_APPEND( &pms, ":", 1);
+ MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN);
+
+ /* This is the final response digest. */
+ pj_md5_final(&pms, digest);
+
+ /* Convert digest to string and store in chal->response. */
+ result->slen = PJSIP_MD5STRLEN;
+ digest2str(digest, result->ptr);
+
+ AUTH_TRACE_((THIS_FILE, " digest=%.32s", result->ptr));
+ AUTH_TRACE_((THIS_FILE, "Digest created"));
+}
+
+/*
+ * Finds out if qop offer contains "auth" token.
+ */
+static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
+{
+ pj_str_t qop;
+ char *p;
+
+ pj_strdup_with_null( pool, &qop, qop_offer);
+ p = qop.ptr;
+ while (*p) {
+ *p = (char)pj_tolower(*p);
+ ++p;
+ }
+
+ p = qop.ptr;
+ while (*p) {
+ if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
+ int e = *(p+4);
+ if (e=='"' || e==',' || e==0)
+ return PJ_TRUE;
+ else
+ p += 4;
+ } else {
+ ++p;
+ }
+ }
+
+ return PJ_FALSE;
+}
+
+/*
+ * Generate response digest.
+ * Most of the parameters to generate the digest (i.e. username, realm, uri,
+ * and nonce) are expected to be in the credential. Additional parameters (i.e.
+ * password and method param) should be supplied in the argument.
+ *
+ * The resulting digest will be stored in cred->response.
+ * The pool is used to allocate 32 bytes to store the digest in cred->response.
+ */
+static pj_status_t respond_digest( pj_pool_t *pool,
+ pjsip_digest_credential *cred,
+ const pjsip_digest_challenge *chal,
+ const pj_str_t *uri,
+ const pjsip_cred_info *cred_info,
+ const pj_str_t *cnonce,
+ pj_uint32_t nc,
+ const pj_str_t *method)
+{
+ const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 };
+
+ /* Check algorithm is supported. We support MD5 and AKAv1-MD5. */
+ if (chal->algorithm.slen==0 ||
+ (pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 ||
+ pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0))
+ {
+ ;
+ }
+ else {
+ PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"",
+ chal->algorithm.slen, chal->algorithm.ptr));
+ return PJSIP_EINVALIDALGORITHM;
+ }
+
+ /* Build digest credential from arguments. */
+ pj_strdup(pool, &cred->username, &cred_info->username);
+ pj_strdup(pool, &cred->realm, &chal->realm);
+ pj_strdup(pool, &cred->nonce, &chal->nonce);
+ pj_strdup(pool, &cred->uri, uri);
+ pj_strdup(pool, &cred->algorithm, &chal->algorithm);
+ pj_strdup(pool, &cred->opaque, &chal->opaque);
+
+ /* Allocate memory. */
+ cred->response.ptr = (char*) pj_pool_alloc(pool, PJSIP_MD5STRLEN);
+ cred->response.slen = PJSIP_MD5STRLEN;
+
+ if (chal->qop.slen == 0) {
+ /* Server doesn't require quality of protection. */
+
+ if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+ /* Call application callback to create the response digest */
+ return (*cred_info->ext.aka.cb)(pool, chal, cred_info,
+ method, cred);
+ }
+ else {
+ /* Convert digest to string and store in chal->response. */
+ pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL,
+ NULL, NULL, uri, &chal->realm,
+ cred_info, method);
+ }
+
+ } else if (has_auth_qop(pool, &chal->qop)) {
+ /* Server requires quality of protection.
+ * We respond with selecting "qop=auth" protection.
+ */
+ cred->qop = pjsip_AUTH_STR;
+ cred->nc.ptr = (char*) pj_pool_alloc(pool, 16);
+ cred->nc.slen = pj_ansi_snprintf(cred->nc.ptr, 16, "%08u", nc);
+
+ if (cnonce && cnonce->slen) {
+ pj_strdup(pool, &cred->cnonce, cnonce);
+ } else {
+ pj_str_t dummy_cnonce = { "b39971", 6};
+ pj_strdup(pool, &cred->cnonce, &dummy_cnonce);
+ }
+
+ if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+ /* Call application callback to create the response digest */
+ return (*cred_info->ext.aka.cb)(pool, chal, cred_info,
+ method, cred);
+ }
+ else {
+ pjsip_auth_create_digest( &cred->response, &cred->nonce,
+ &cred->nc, cnonce, &pjsip_AUTH_STR,
+ uri, &chal->realm, cred_info, method );
+ }
+
+ } else {
+ /* Server requires quality protection that we don't support. */
+ PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s",
+ chal->qop.slen, chal->qop.ptr));
+ return PJSIP_EINVALIDQOP;
+ }
+
+ return PJ_SUCCESS;
+}
+
+#if defined(PJSIP_AUTH_QOP_SUPPORT) && PJSIP_AUTH_QOP_SUPPORT!=0
+/*
+ * Update authentication session with a challenge.
+ */
+static void update_digest_session( pj_pool_t *ses_pool,
+ pjsip_cached_auth *cached_auth,
+ const pjsip_www_authenticate_hdr *hdr )
+{
+ if (hdr->challenge.digest.qop.slen == 0) {
+#if PJSIP_AUTH_AUTO_SEND_NEXT!=0
+ if (!cached_auth->last_chal || pj_stricmp2(&hdr->scheme, "digest")) {
+ cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+ pjsip_hdr_clone(ses_pool, hdr);
+ } else {
+ /* Only update if the new challenge is "significantly different"
+ * than the one in the cache, to reduce memory usage.
+ */
+ const pjsip_digest_challenge *d1 =
+ &cached_auth->last_chal->challenge.digest;
+ const pjsip_digest_challenge *d2 = &hdr->challenge.digest;
+
+ if (pj_strcmp(&d1->domain, &d2->domain) ||
+ pj_strcmp(&d1->realm, &d2->realm) ||
+ pj_strcmp(&d1->nonce, &d2->nonce) ||
+ pj_strcmp(&d1->opaque, &d2->opaque) ||
+ pj_strcmp(&d1->algorithm, &d2->algorithm) ||
+ pj_strcmp(&d1->qop, &d2->qop))
+ {
+ cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+ pjsip_hdr_clone(ses_pool, hdr);
+ }
+ }
+#endif
+ return;
+ }
+
+ /* Initialize cnonce and qop if not present. */
+ if (cached_auth->cnonce.slen == 0) {
+ /* Save the whole challenge */
+ cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+ pjsip_hdr_clone(ses_pool, hdr);
+
+ /* Create cnonce */
+ pj_create_unique_string( ses_pool, &cached_auth->cnonce );
+
+ /* Initialize nonce-count */
+ cached_auth->nc = 1;
+
+ /* Save realm. */
+ /* Note: allow empty realm (http://trac.pjsip.org/repos/ticket/1061)
+ pj_assert(cached_auth->realm.slen != 0);
+ */
+ if (cached_auth->realm.slen == 0) {
+ pj_strdup(ses_pool, &cached_auth->realm,
+ &hdr->challenge.digest.realm);
+ }
+
+ } else {
+ /* Update last_nonce and nonce-count */
+ if (!pj_strcmp(&hdr->challenge.digest.nonce,
+ &cached_auth->last_chal->challenge.digest.nonce))
+ {
+ /* Same nonce, increment nonce-count */
+ ++cached_auth->nc;
+ } else {
+ /* Server gives new nonce. */
+ pj_strdup(ses_pool, &cached_auth->last_chal->challenge.digest.nonce,
+ &hdr->challenge.digest.nonce);
+ /* Has the opaque changed? */
+ if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque,
+ &hdr->challenge.digest.opaque))
+ {
+ pj_strdup(ses_pool,
+ &cached_auth->last_chal->challenge.digest.opaque,
+ &hdr->challenge.digest.opaque);
+ }
+ cached_auth->nc = 1;
+ }
+ }
+}
+#endif /* PJSIP_AUTH_QOP_SUPPORT */
+
+
+/* Find cached authentication in the list for the specified realm. */
+static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess,
+ const pj_str_t *realm )
+{
+ pjsip_cached_auth *auth = sess->cached_auth.next;
+ while (auth != &sess->cached_auth) {
+ if (pj_stricmp(&auth->realm, realm) == 0)
+ return auth;
+ auth = auth->next;
+ }
+
+ return NULL;
+}
+
+/* Find credential to use for the specified realm and auth scheme. */
+static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess,
+ const pj_str_t *realm,
+ const pj_str_t *auth_scheme)
+{
+ unsigned i;
+ int wildcard = -1;
+
+ PJ_UNUSED_ARG(auth_scheme);
+
+ for (i=0; i<sess->cred_cnt; ++i) {
+ if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0)
+ return &sess->cred_info[i];
+ else if (sess->cred_info[i].realm.slen == 1 &&
+ sess->cred_info[i].realm.ptr[0] == '*')
+ {
+ wildcard = i;
+ }
+ }
+
+ /* No matching realm. See if we have credential with wildcard ('*')
+ * as the realm.
+ */
+ if (wildcard != -1)
+ return &sess->cred_info[wildcard];
+
+ /* Nothing is suitable */
+ return NULL;
+}
+
+
+/* Init client session. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_init( pjsip_auth_clt_sess *sess,
+ pjsip_endpoint *endpt,
+ pj_pool_t *pool,
+ unsigned options)
+{
+ PJ_ASSERT_RETURN(sess && endpt && pool && (options==0), PJ_EINVAL);
+
+ sess->pool = pool;
+ sess->endpt = endpt;
+ sess->cred_cnt = 0;
+ sess->cred_info = NULL;
+ pj_list_init(&sess->cached_auth);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Clone session. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_clone( pj_pool_t *pool,
+ pjsip_auth_clt_sess *sess,
+ const pjsip_auth_clt_sess *rhs )
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(pool && sess && rhs, PJ_EINVAL);
+
+ pjsip_auth_clt_init(sess, (pjsip_endpoint*)rhs->endpt, pool, 0);
+
+ sess->cred_cnt = rhs->cred_cnt;
+ sess->cred_info = (pjsip_cred_info*)
+ pj_pool_alloc(pool,
+ sess->cred_cnt*sizeof(pjsip_cred_info));
+ for (i=0; i<rhs->cred_cnt; ++i) {
+ pj_strdup(pool, &sess->cred_info[i].realm, &rhs->cred_info[i].realm);
+ pj_strdup(pool, &sess->cred_info[i].scheme, &rhs->cred_info[i].scheme);
+ pj_strdup(pool, &sess->cred_info[i].username,
+ &rhs->cred_info[i].username);
+ sess->cred_info[i].data_type = rhs->cred_info[i].data_type;
+ pj_strdup(pool, &sess->cred_info[i].data, &rhs->cred_info[i].data);
+ }
+
+ /* TODO note:
+ * Cloning the full authentication client is quite a big task.
+ * We do only the necessary bits here, i.e. cloning the credentials.
+ * The drawback of this basic approach is, a forked dialog will have to
+ * re-authenticate itself on the next request because it has lost the
+ * cached authentication headers.
+ */
+ PJ_TODO(FULL_CLONE_OF_AUTH_CLIENT_SESSION);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Set client credentials. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess,
+ int cred_cnt,
+ const pjsip_cred_info *c)
+{
+ PJ_ASSERT_RETURN(sess && c, PJ_EINVAL);
+
+ if (cred_cnt == 0) {
+ sess->cred_cnt = 0;
+ } else {
+ int i;
+ sess->cred_info = (pjsip_cred_info*)
+ pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c));
+ for (i=0; i<cred_cnt; ++i) {
+ sess->cred_info[i].data_type = c[i].data_type;
+
+ /* When data_type is PJSIP_CRED_DATA_EXT_AKA,
+ * callback must be specified.
+ */
+ if ((c[i].data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+
+#if !PJSIP_HAS_DIGEST_AKA_AUTH
+ if (!PJSIP_HAS_DIGEST_AKA_AUTH) {
+ pj_assert(!"PJSIP_HAS_DIGEST_AKA_AUTH is not enabled");
+ return PJSIP_EAUTHINAKACRED;
+ }
+#endif
+
+ /* Callback must be specified */
+ PJ_ASSERT_RETURN(c[i].ext.aka.cb != NULL, PJ_EINVAL);
+
+ /* Verify K len */
+ PJ_ASSERT_RETURN(c[i].ext.aka.k.slen <= PJSIP_AKA_KLEN,
+ PJSIP_EAUTHINAKACRED);
+
+ /* Verify OP len */
+ PJ_ASSERT_RETURN(c[i].ext.aka.op.slen <= PJSIP_AKA_OPLEN,
+ PJSIP_EAUTHINAKACRED);
+
+ /* Verify AMF len */
+ PJ_ASSERT_RETURN(c[i].ext.aka.amf.slen <= PJSIP_AKA_AMFLEN,
+ PJSIP_EAUTHINAKACRED);
+
+ sess->cred_info[i].ext.aka.cb = c[i].ext.aka.cb;
+ pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.k,
+ &c[i].ext.aka.k);
+ pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.op,
+ &c[i].ext.aka.op);
+ pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.amf,
+ &c[i].ext.aka.amf);
+ }
+
+ pj_strdup(sess->pool, &sess->cred_info[i].scheme, &c[i].scheme);
+ pj_strdup(sess->pool, &sess->cred_info[i].realm, &c[i].realm);
+ pj_strdup(sess->pool, &sess->cred_info[i].username, &c[i].username);
+ pj_strdup(sess->pool, &sess->cred_info[i].data, &c[i].data);
+ }
+ sess->cred_cnt = cred_cnt;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set the preference for the client authentication session.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_set_prefs(pjsip_auth_clt_sess *sess,
+ const pjsip_auth_clt_pref *p)
+{
+ PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
+
+ pj_memcpy(&sess->pref, p, sizeof(*p));
+ pj_strdup(sess->pool, &sess->pref.algorithm, &p->algorithm);
+ //if (sess->pref.algorithm.slen == 0)
+ // sess->pref.algorithm = pj_str("md5");
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the preference for the client authentication session.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_get_prefs(pjsip_auth_clt_sess *sess,
+ pjsip_auth_clt_pref *p)
+{
+ PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
+
+ pj_memcpy(p, &sess->pref, sizeof(pjsip_auth_clt_pref));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create Authorization/Proxy-Authorization response header based on the challege
+ * in WWW-Authenticate/Proxy-Authenticate header.
+ */
+static pj_status_t auth_respond( pj_pool_t *req_pool,
+ const pjsip_www_authenticate_hdr *hdr,
+ const pjsip_uri *uri,
+ const pjsip_cred_info *cred_info,
+ const pjsip_method *method,
+ pj_pool_t *sess_pool,
+ pjsip_cached_auth *cached_auth,
+ pjsip_authorization_hdr **p_h_auth)
+{
+ pjsip_authorization_hdr *hauth;
+ char tmp[PJSIP_MAX_URL_SIZE];
+ pj_str_t uri_str;
+ pj_pool_t *pool;
+ pj_status_t status;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(req_pool && hdr && uri && cred_info && method &&
+ sess_pool && cached_auth && p_h_auth, PJ_EINVAL);
+
+ /* Print URL in the original request. */
+ uri_str.ptr = tmp;
+ uri_str.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, tmp,sizeof(tmp));
+ if (uri_str.slen < 1) {
+ pj_assert(!"URL is too long!");
+ return PJSIP_EURITOOLONG;
+ }
+
+# if (PJSIP_AUTH_HEADER_CACHING)
+ {
+ pool = sess_pool;
+ PJ_UNUSED_ARG(req_pool);
+ }
+# else
+ {
+ pool = req_pool;
+ PJ_UNUSED_ARG(sess_pool);
+ }
+# endif
+
+ if (hdr->type == PJSIP_H_WWW_AUTHENTICATE)
+ hauth = pjsip_authorization_hdr_create(pool);
+ else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE)
+ hauth = pjsip_proxy_authorization_hdr_create(pool);
+ else {
+ pj_assert(!"Invalid response header!");
+ return PJSIP_EINVALIDHDR;
+ }
+
+ /* Only support digest scheme at the moment. */
+ if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
+ pj_str_t *cnonce = NULL;
+ pj_uint32_t nc = 1;
+
+ /* Update the session (nonce-count etc) if required. */
+# if PJSIP_AUTH_QOP_SUPPORT
+ {
+ if (cached_auth) {
+ update_digest_session( sess_pool, cached_auth, hdr );
+
+ cnonce = &cached_auth->cnonce;
+ nc = cached_auth->nc;
+ }
+ }
+# endif /* PJSIP_AUTH_QOP_SUPPORT */
+
+ hauth->scheme = pjsip_DIGEST_STR;
+ status = respond_digest( pool, &hauth->credential.digest,
+ &hdr->challenge.digest, &uri_str, cred_info,
+ cnonce, nc, &method->name);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set qop type in auth session the first time only. */
+ if (hdr->challenge.digest.qop.slen != 0 && cached_auth) {
+ if (cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+ pj_str_t *qop_val = &hauth->credential.digest.qop;
+ if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) {
+ cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH;
+ } else {
+ cached_auth->qop_value = PJSIP_AUTH_QOP_UNKNOWN;
+ }
+ }
+ }
+ } else {
+ return PJSIP_EINVALIDAUTHSCHEME;
+ }
+
+ /* Keep the new authorization header in the cache, only
+ * if no qop is not present.
+ */
+# if PJSIP_AUTH_HEADER_CACHING
+ {
+ if (hauth && cached_auth && cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+ pjsip_cached_auth_hdr *cached_hdr;
+
+ /* Delete old header with the same method. */
+ cached_hdr = cached_auth->cached_hdr.next;
+ while (cached_hdr != &cached_auth->cached_hdr) {
+ if (pjsip_method_cmp(method, &cached_hdr->method)==0)
+ break;
+ cached_hdr = cached_hdr->next;
+ }
+
+ /* Save the header to the list. */
+ if (cached_hdr != &cached_auth->cached_hdr) {
+ cached_hdr->hdr = hauth;
+ } else {
+ cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr));
+ pjsip_method_copy( pool, &cached_hdr->method, method);
+ cached_hdr->hdr = hauth;
+ pj_list_insert_before( &cached_auth->cached_hdr, cached_hdr );
+ }
+ }
+
+# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
+ if (hdr != cached_auth->last_chal) {
+ cached_auth->last_chal = pjsip_hdr_clone(sess_pool, hdr);
+ }
+# endif
+ }
+# endif
+
+ *p_h_auth = hauth;
+ return PJ_SUCCESS;
+
+}
+
+
+#if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
+static pj_status_t new_auth_for_req( pjsip_tx_data *tdata,
+ pjsip_auth_clt_sess *sess,
+ pjsip_cached_auth *auth,
+ pjsip_authorization_hdr **p_h_auth)
+{
+ const pjsip_cred_info *cred;
+ pjsip_authorization_hdr *hauth;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL);
+ PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL);
+
+ cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme );
+ if (!cred)
+ return PJSIP_ENOCREDENTIAL;
+
+ status = auth_respond( tdata->pool, auth->last_chal,
+ tdata->msg->line.req.uri,
+ cred, &tdata->msg->line.req.method,
+ sess->pool, auth, &hauth);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth);
+
+ if (p_h_auth)
+ *p_h_auth = hauth;
+
+ return PJ_SUCCESS;
+}
+#endif
+
+
+/* Find credential in list of (Proxy-)Authorization headers */
+static pjsip_authorization_hdr* get_header_for_realm(const pjsip_hdr *hdr_list,
+ const pj_str_t *realm)
+{
+ pjsip_authorization_hdr *h;
+
+ h = (pjsip_authorization_hdr*)hdr_list->next;
+ while (h != (pjsip_authorization_hdr*)hdr_list) {
+ if (pj_stricmp(&h->credential.digest.realm, realm)==0)
+ return h;
+ h = h->next;
+ }
+
+ return NULL;
+}
+
+
+/* Initialize outgoing request. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess,
+ pjsip_tx_data *tdata )
+{
+ const pjsip_method *method;
+ pjsip_cached_auth *auth;
+ pjsip_hdr added;
+
+ PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
+ PJ_ASSERT_RETURN(tdata->msg->type==PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Init list */
+ pj_list_init(&added);
+
+ /* Get the method. */
+ method = &tdata->msg->line.req.method;
+
+ auth = sess->cached_auth.next;
+ while (auth != &sess->cached_auth) {
+ /* Reset stale counter */
+ auth->stale_cnt = 0;
+
+ if (auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+# if defined(PJSIP_AUTH_HEADER_CACHING) && \
+ PJSIP_AUTH_HEADER_CACHING!=0
+ {
+ pjsip_cached_auth_hdr *entry = auth->cached_hdr.next;
+ while (entry != &auth->cached_hdr) {
+ if (pjsip_method_cmp(&entry->method, method)==0) {
+ pjsip_authorization_hdr *hauth;
+ hauth = pjsip_hdr_shallow_clone(tdata->pool, entry->hdr);
+ //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+ pj_list_push_back(&added, hauth);
+ break;
+ }
+ entry = entry->next;
+ }
+
+# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+ PJSIP_AUTH_AUTO_SEND_NEXT!=0
+ {
+ if (entry == &auth->cached_hdr)
+ new_auth_for_req( tdata, sess, auth, NULL);
+ }
+# endif
+
+ }
+# elif defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+ PJSIP_AUTH_AUTO_SEND_NEXT!=0
+ {
+ new_auth_for_req( tdata, sess, auth, NULL);
+ }
+# endif
+
+ }
+# if defined(PJSIP_AUTH_QOP_SUPPORT) && \
+ defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+ (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT)
+ else if (auth->qop_value == PJSIP_AUTH_QOP_AUTH) {
+ /* For qop="auth", we have to re-create the authorization header.
+ */
+ const pjsip_cred_info *cred;
+ pjsip_authorization_hdr *hauth;
+ pj_status_t status;
+
+ cred = auth_find_cred(sess, &auth->realm,
+ &auth->last_chal->scheme);
+ if (!cred) {
+ auth = auth->next;
+ continue;
+ }
+
+ status = auth_respond( tdata->pool, auth->last_chal,
+ tdata->msg->line.req.uri,
+ cred,
+ &tdata->msg->line.req.method,
+ sess->pool, auth, &hauth);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+ pj_list_push_back(&added, hauth);
+ }
+# endif /* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */
+
+ auth = auth->next;
+ }
+
+ if (sess->pref.initial_auth == PJ_FALSE) {
+ pjsip_hdr *h;
+
+ /* Don't want to send initial empty Authorization header, so
+ * just send whatever available in the list (maybe empty).
+ */
+
+ h = added.next;
+ while (h != &added) {
+ pjsip_hdr *next = h->next;
+ pjsip_msg_add_hdr(tdata->msg, h);
+ h = next;
+ }
+ } else {
+ /* For each realm, add either the cached authorization header
+ * or add an empty authorization header.
+ */
+ unsigned i;
+ char *uri_str;
+ int len;
+
+ uri_str = (char*)pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE);
+ len = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, tdata->msg->line.req.uri,
+ uri_str, PJSIP_MAX_URL_SIZE);
+ if (len < 1 || len >= PJSIP_MAX_URL_SIZE)
+ return PJSIP_EURITOOLONG;
+
+ for (i=0; i<sess->cred_cnt; ++i) {
+ pjsip_cred_info *c = &sess->cred_info[i];
+ pjsip_authorization_hdr *h;
+
+ h = get_header_for_realm(&added, &c->realm);
+ if (h) {
+ pj_list_erase(h);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h);
+ } else {
+ pjsip_authorization_hdr *hs;
+
+ hs = pjsip_authorization_hdr_create(tdata->pool);
+ pj_strdup(tdata->pool, &hs->scheme, &c->scheme);
+ pj_strdup(tdata->pool, &hs->credential.digest.username,
+ &c->username);
+ pj_strdup(tdata->pool, &hs->credential.digest.realm,
+ &c->realm);
+ pj_strdup2(tdata->pool, &hs->credential.digest.uri,
+ uri_str);
+ pj_strdup(tdata->pool, &hs->credential.digest.algorithm,
+ &sess->pref.algorithm);
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs);
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* Process authorization challenge */
+static pj_status_t process_auth( pj_pool_t *req_pool,
+ const pjsip_www_authenticate_hdr *hchal,
+ const pjsip_uri *uri,
+ pjsip_tx_data *tdata,
+ pjsip_auth_clt_sess *sess,
+ pjsip_cached_auth *cached_auth,
+ pjsip_authorization_hdr **h_auth)
+{
+ const pjsip_cred_info *cred;
+ pjsip_authorization_hdr *sent_auth = NULL;
+ pjsip_hdr *hdr;
+ pj_status_t status;
+
+ /* See if we have sent authorization header for this realm */
+ hdr = tdata->msg->hdr.next;
+ while (hdr != &tdata->msg->hdr) {
+ if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
+ hdr->type == PJSIP_H_AUTHORIZATION) ||
+ (hchal->type == PJSIP_H_PROXY_AUTHENTICATE &&
+ hdr->type == PJSIP_H_PROXY_AUTHORIZATION))
+ {
+ sent_auth = (pjsip_authorization_hdr*) hdr;
+ if (pj_stricmp(&hchal->challenge.common.realm,
+ &sent_auth->credential.common.realm )==0)
+ {
+ /* If this authorization has empty response, remove it. */
+ if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
+ sent_auth->credential.digest.response.slen == 0)
+ {
+ /* This is empty authorization, remove it. */
+ hdr = hdr->next;
+ pj_list_erase(sent_auth);
+ continue;
+ } else {
+ /* Found previous authorization attempt */
+ break;
+ }
+ }
+ }
+ hdr = hdr->next;
+ }
+
+ /* If we have sent, see if server rejected because of stale nonce or
+ * other causes.
+ */
+ if (hdr != &tdata->msg->hdr) {
+ pj_bool_t stale;
+
+ /* Detect "stale" state */
+ stale = hchal->challenge.digest.stale;
+ if (!stale) {
+ /* If stale is false, check is nonce has changed. Some servers
+ * (broken ones!) want to change nonce but they fail to set
+ * stale to true.
+ */
+ stale = pj_strcmp(&hchal->challenge.digest.nonce,
+ &sent_auth->credential.digest.nonce);
+ }
+
+ if (stale == PJ_FALSE) {
+ /* Our credential is rejected. No point in trying to re-supply
+ * the same credential.
+ */
+ PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
+ "server rejected with stale=false",
+ sent_auth->credential.digest.username.slen,
+ sent_auth->credential.digest.username.ptr,
+ sent_auth->credential.digest.realm.slen,
+ sent_auth->credential.digest.realm.ptr));
+ return PJSIP_EFAILEDCREDENTIAL;
+ }
+
+ cached_auth->stale_cnt++;
+ if (cached_auth->stale_cnt >= PJSIP_MAX_STALE_COUNT) {
+ /* Our credential is rejected. No point in trying to re-supply
+ * the same credential.
+ */
+ PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
+ "maximum number of stale retries exceeded",
+ sent_auth->credential.digest.username.slen,
+ sent_auth->credential.digest.username.ptr,
+ sent_auth->credential.digest.realm.slen,
+ sent_auth->credential.digest.realm.ptr));
+ return PJSIP_EAUTHSTALECOUNT;
+ }
+
+ /* Otherwise remove old, stale authorization header from the mesasge.
+ * We will supply a new one.
+ */
+ pj_list_erase(sent_auth);
+ }
+
+ /* Find credential to be used for the challenge. */
+ cred = auth_find_cred( sess, &hchal->challenge.common.realm,
+ &hchal->scheme);
+ if (!cred) {
+ const pj_str_t *realm = &hchal->challenge.common.realm;
+ PJ_LOG(4,(THIS_FILE,
+ "Unable to set auth for %s: can not find credential for %.*s/%.*s",
+ tdata->obj_name,
+ realm->slen, realm->ptr,
+ hchal->scheme.slen, hchal->scheme.ptr));
+ return PJSIP_ENOCREDENTIAL;
+ }
+
+ /* Respond to authorization challenge. */
+ status = auth_respond( req_pool, hchal, uri, cred,
+ &tdata->msg->line.req.method,
+ sess->pool, cached_auth, h_auth);
+ return status;
+}
+
+
+/* Reinitialize outgoing request after 401/407 response is received.
+ * The purpose of this function is:
+ * - to add a Authorization/Proxy-Authorization header.
+ * - to put the newly created Authorization/Proxy-Authorization header
+ * in cached_list.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req( pjsip_auth_clt_sess *sess,
+ const pjsip_rx_data *rdata,
+ pjsip_tx_data *old_request,
+ pjsip_tx_data **new_request )
+{
+ pjsip_tx_data *tdata;
+ const pjsip_hdr *hdr;
+ unsigned chal_cnt;
+ pjsip_via_hdr *via;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+ PJ_ASSERT_RETURN(old_request->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->line.status.code == 401 ||
+ rdata->msg_info.msg->line.status.code == 407,
+ PJSIP_EINVALIDSTATUS);
+
+ tdata = old_request;
+ tdata->auth_retry = PJ_FALSE;
+
+ /*
+ * Respond to each authentication challenge.
+ */
+ hdr = rdata->msg_info.msg->hdr.next;
+ chal_cnt = 0;
+ while (hdr != &rdata->msg_info.msg->hdr) {
+ pjsip_cached_auth *cached_auth;
+ const pjsip_www_authenticate_hdr *hchal;
+ pjsip_authorization_hdr *hauth;
+
+ /* Find WWW-Authenticate or Proxy-Authenticate header. */
+ while (hdr != &rdata->msg_info.msg->hdr &&
+ hdr->type != PJSIP_H_WWW_AUTHENTICATE &&
+ hdr->type != PJSIP_H_PROXY_AUTHENTICATE)
+ {
+ hdr = hdr->next;
+ }
+ if (hdr == &rdata->msg_info.msg->hdr)
+ break;
+
+ hchal = (const pjsip_www_authenticate_hdr*) hdr;
+ ++chal_cnt;
+
+ /* Find authentication session for this realm, create a new one
+ * if not present.
+ */
+ cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm );
+ if (!cached_auth) {
+ cached_auth = PJ_POOL_ZALLOC_T( sess->pool, pjsip_cached_auth);
+ pj_strdup( sess->pool, &cached_auth->realm, &hchal->challenge.common.realm);
+ cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE);
+# if (PJSIP_AUTH_HEADER_CACHING)
+ {
+ pj_list_init(&cached_auth->cached_hdr);
+ }
+# endif
+ pj_list_insert_before( &sess->cached_auth, cached_auth );
+ }
+
+ /* Create authorization header for this challenge, and update
+ * authorization session.
+ */
+ status = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri,
+ tdata, sess, cached_auth, &hauth);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add to the message. */
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+
+ /* Process next header. */
+ hdr = hdr->next;
+ }
+
+ /* Check if challenge is present */
+ if (chal_cnt == 0)
+ return PJSIP_EAUTHNOCHAL;
+
+ /* Remove branch param in Via header. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+ via->branch_param.slen = 0;
+
+ /* Restore strict route set.
+ * See http://trac.pjsip.org/repos/ticket/492
+ */
+ pjsip_restore_strict_route_set(tdata);
+
+ /* Must invalidate the message! */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Retrying.. */
+ tdata->auth_retry = PJ_TRUE;
+
+ /* Increment reference counter. */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Done. */
+ *new_request = tdata;
+ return PJ_SUCCESS;
+
+}
+
diff --git a/pjsip/src/pjsip/sip_auth_msg.c b/pjsip/src/pjsip/sip_auth_msg.c
new file mode 100644
index 0000000..5167b46
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_msg.c
@@ -0,0 +1,343 @@
+/* $Id: sip_auth_msg.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 <pjsip/sip_auth_msg.h>
+#include <pjsip/sip_auth_parser.h>
+#include <pjsip/sip_parser.h>
+#include <pj/pool.h>
+#include <pj/list.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+#include <pjsip/print_util.h>
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Authorization and Proxy-Authorization header.
+ */
+static pjsip_authorization_hdr* pjsip_authorization_hdr_clone( pj_pool_t *pool,
+ const pjsip_authorization_hdr *hdr);
+static pjsip_authorization_hdr* pjsip_authorization_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_authorization_hdr *hdr);
+static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr,
+ char *buf, pj_size_t size);
+
+static pjsip_hdr_vptr authorization_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_authorization_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_authorization_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_authorization_hdr_print,
+};
+
+
+PJ_DEF(pjsip_authorization_hdr*) pjsip_authorization_hdr_create(pj_pool_t *pool)
+{
+ pjsip_authorization_hdr *hdr;
+ hdr = PJ_POOL_ZALLOC_T(pool, pjsip_authorization_hdr);
+ init_hdr(hdr, PJSIP_H_AUTHORIZATION, &authorization_hdr_vptr);
+ pj_list_init(&hdr->credential.common.other_param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_proxy_authorization_hdr*) pjsip_proxy_authorization_hdr_create(pj_pool_t *pool)
+{
+ pjsip_proxy_authorization_hdr *hdr;
+ hdr = PJ_POOL_ZALLOC_T(pool, pjsip_proxy_authorization_hdr);
+ init_hdr(hdr, PJSIP_H_PROXY_AUTHORIZATION, &authorization_hdr_vptr);
+ pj_list_init(&hdr->credential.common.other_param);
+ return hdr;
+}
+
+static int print_digest_credential(pjsip_digest_credential *cred, char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance_pair_quote_cond(buf, "username=", 9, cred->username, '"', '"');
+ copy_advance_pair_quote_cond(buf, ", realm=", 8, cred->realm, '"', '"');
+ copy_advance_pair_quote(buf, ", nonce=", 8, cred->nonce, '"', '"');
+ copy_advance_pair_quote_cond(buf, ", uri=", 6, cred->uri, '"', '"');
+ copy_advance_pair_quote(buf, ", response=", 11, cred->response, '"', '"');
+ copy_advance_pair(buf, ", algorithm=", 12, cred->algorithm);
+ copy_advance_pair_quote_cond(buf, ", cnonce=", 9, cred->cnonce, '"', '"');
+ copy_advance_pair_quote_cond(buf, ", opaque=", 9, cred->opaque, '"', '"');
+ //Note: there's no dbl-quote in qop in Authorization header
+ // (unlike WWW-Authenticate)
+ //copy_advance_pair_quote_cond(buf, ", qop=", 6, cred->qop, '"', '"');
+ copy_advance_pair(buf, ", qop=", 6, cred->qop);
+ copy_advance_pair(buf, ", nc=", 5, cred->nc);
+
+ printed = pjsip_param_print_on(&cred->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ',');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return (int) (buf-startbuf);
+}
+
+static int print_pgp_credential(pjsip_pgp_credential *cred, char *buf, pj_size_t size)
+{
+ PJ_UNUSED_ARG(cred);
+ PJ_UNUSED_ARG(buf);
+ PJ_UNUSED_ARG(size);
+ return -1;
+}
+
+static int pjsip_authorization_hdr_print( pjsip_authorization_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+
+ copy_advance(buf, hdr->name);
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ copy_advance(buf, hdr->scheme);
+ *buf++ = ' ';
+
+ if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0)
+ {
+ printed = print_digest_credential(&hdr->credential.digest, buf, endbuf - buf);
+ }
+ else if (pj_stricmp(&hdr->scheme, &pjsip_PGP_STR) == 0)
+ {
+ printed = print_pgp_credential(&hdr->credential.pgp, buf, endbuf - buf);
+ }
+ else {
+ pj_assert(0);
+ return -1;
+ }
+
+ if (printed == -1)
+ return -1;
+
+ buf += printed;
+ *buf = '\0';
+ return (int)(buf-startbuf);
+}
+
+static pjsip_authorization_hdr* pjsip_authorization_hdr_clone( pj_pool_t *pool,
+ const pjsip_authorization_hdr *rhs)
+{
+ /* This function also serves Proxy-Authorization header. */
+ pjsip_authorization_hdr *hdr;
+ if (rhs->type == PJSIP_H_AUTHORIZATION)
+ hdr = pjsip_authorization_hdr_create(pool);
+ else
+ hdr = pjsip_proxy_authorization_hdr_create(pool);
+
+ pj_strdup(pool, &hdr->scheme, &rhs->scheme);
+
+ if (pj_stricmp2(&hdr->scheme, "digest") == 0) {
+ pj_strdup(pool, &hdr->credential.digest.username, &rhs->credential.digest.username);
+ pj_strdup(pool, &hdr->credential.digest.realm, &rhs->credential.digest.realm);
+ pj_strdup(pool, &hdr->credential.digest.nonce, &rhs->credential.digest.nonce);
+ pj_strdup(pool, &hdr->credential.digest.uri, &rhs->credential.digest.uri);
+ pj_strdup(pool, &hdr->credential.digest.response, &rhs->credential.digest.response);
+ pj_strdup(pool, &hdr->credential.digest.algorithm, &rhs->credential.digest.algorithm);
+ pj_strdup(pool, &hdr->credential.digest.cnonce, &rhs->credential.digest.cnonce);
+ pj_strdup(pool, &hdr->credential.digest.opaque, &rhs->credential.digest.opaque);
+ pj_strdup(pool, &hdr->credential.digest.qop, &rhs->credential.digest.qop);
+ pj_strdup(pool, &hdr->credential.digest.nc, &rhs->credential.digest.nc);
+ pjsip_param_clone(pool, &hdr->credential.digest.other_param, &rhs->credential.digest.other_param);
+ } else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) {
+ pj_assert(0);
+ return NULL;
+ } else {
+ pj_assert(0);
+ return NULL;
+ }
+
+ return hdr;
+}
+
+static pjsip_authorization_hdr*
+pjsip_authorization_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_authorization_hdr *rhs)
+{
+ /* This function also serves Proxy-Authorization header. */
+ pjsip_authorization_hdr *hdr;
+ hdr = PJ_POOL_ALLOC_T(pool, pjsip_authorization_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->credential.common.other_param,
+ &rhs->credential.common.other_param);
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Proxy-Authenticate and WWW-Authenticate header.
+ */
+static int pjsip_www_authenticate_hdr_print( pjsip_www_authenticate_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_clone( pj_pool_t *pool,
+ const pjsip_www_authenticate_hdr *hdr);
+static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_www_authenticate_hdr *hdr);
+
+static pjsip_hdr_vptr www_authenticate_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_www_authenticate_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_www_authenticate_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_www_authenticate_hdr_print,
+};
+
+
+PJ_DEF(pjsip_www_authenticate_hdr*) pjsip_www_authenticate_hdr_create(pj_pool_t *pool)
+{
+ pjsip_www_authenticate_hdr *hdr;
+ hdr = PJ_POOL_ZALLOC_T(pool, pjsip_www_authenticate_hdr);
+ init_hdr(hdr, PJSIP_H_WWW_AUTHENTICATE, &www_authenticate_hdr_vptr);
+ pj_list_init(&hdr->challenge.common.other_param);
+ return hdr;
+}
+
+
+PJ_DEF(pjsip_proxy_authenticate_hdr*) pjsip_proxy_authenticate_hdr_create(pj_pool_t *pool)
+{
+ pjsip_proxy_authenticate_hdr *hdr;
+ hdr = PJ_POOL_ZALLOC_T(pool, pjsip_proxy_authenticate_hdr);
+ init_hdr(hdr, PJSIP_H_PROXY_AUTHENTICATE, &www_authenticate_hdr_vptr);
+ pj_list_init(&hdr->challenge.common.other_param);
+ return hdr;
+}
+
+static int print_digest_challenge( pjsip_digest_challenge *chal,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ /* Allow empty realm, see http://trac.pjsip.org/repos/ticket/1061 */
+ copy_advance_pair_quote(buf, " realm=", 7, chal->realm, '"', '"');
+ copy_advance_pair_quote_cond(buf, ",domain=", 8, chal->domain, '"', '"');
+ copy_advance_pair_quote_cond(buf, ",nonce=", 7, chal->nonce, '"', '"');
+ copy_advance_pair_quote_cond(buf, ",opaque=", 8, chal->opaque, '"', '"');
+ if (chal->stale) {
+ pj_str_t true_str = { "true", 4 };
+ copy_advance_pair(buf, ",stale=", 7, true_str);
+ }
+ copy_advance_pair(buf, ",algorithm=", 11, chal->algorithm);
+ copy_advance_pair_quote_cond(buf, ",qop=", 5, chal->qop, '"', '"');
+
+ printed = pjsip_param_print_on(&chal->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ',');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return (int)(buf-startbuf);
+}
+
+static int print_pgp_challenge( pjsip_pgp_challenge *chal,
+ char *buf, pj_size_t size)
+{
+ PJ_UNUSED_ARG(chal);
+ PJ_UNUSED_ARG(buf);
+ PJ_UNUSED_ARG(size);
+ return -1;
+}
+
+static int pjsip_www_authenticate_hdr_print( pjsip_www_authenticate_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+
+ copy_advance(buf, hdr->name);
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ copy_advance(buf, hdr->scheme);
+ *buf++ = ' ';
+
+ if (pj_stricmp2(&hdr->scheme, "digest") == 0)
+ printed = print_digest_challenge(&hdr->challenge.digest, buf, endbuf - buf);
+ else if (pj_stricmp2(&hdr->scheme, "pgp") == 0)
+ printed = print_pgp_challenge(&hdr->challenge.pgp, buf, endbuf - buf);
+ else {
+ pj_assert(0);
+ return -1;
+ }
+
+ if (printed == -1)
+ return -1;
+
+ buf += printed;
+ *buf = '\0';
+ return (int)(buf-startbuf);
+}
+
+static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_clone( pj_pool_t *pool,
+ const pjsip_www_authenticate_hdr *rhs)
+{
+ /* This function also serves Proxy-Authenticate header. */
+ pjsip_www_authenticate_hdr *hdr;
+ if (rhs->type == PJSIP_H_WWW_AUTHENTICATE)
+ hdr = pjsip_www_authenticate_hdr_create(pool);
+ else
+ hdr = pjsip_proxy_authenticate_hdr_create(pool);
+
+ pj_strdup(pool, &hdr->scheme, &rhs->scheme);
+
+ if (pj_stricmp2(&hdr->scheme, "digest") == 0) {
+ pj_strdup(pool, &hdr->challenge.digest.realm, &rhs->challenge.digest.realm);
+ pj_strdup(pool, &hdr->challenge.digest.domain, &rhs->challenge.digest.domain);
+ pj_strdup(pool, &hdr->challenge.digest.nonce, &rhs->challenge.digest.nonce);
+ pj_strdup(pool, &hdr->challenge.digest.opaque, &rhs->challenge.digest.opaque);
+ hdr->challenge.digest.stale = rhs->challenge.digest.stale;
+ pj_strdup(pool, &hdr->challenge.digest.algorithm, &rhs->challenge.digest.algorithm);
+ pj_strdup(pool, &hdr->challenge.digest.qop, &rhs->challenge.digest.qop);
+ pjsip_param_clone(pool, &hdr->challenge.digest.other_param,
+ &rhs->challenge.digest.other_param);
+ } else if (pj_stricmp2(&hdr->scheme, "pgp") == 0) {
+ pj_assert(0);
+ return NULL;
+ } else {
+ pj_assert(0);
+ return NULL;
+ }
+
+ return hdr;
+
+}
+
+static pjsip_www_authenticate_hdr* pjsip_www_authenticate_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_www_authenticate_hdr *rhs)
+{
+ /* This function also serves Proxy-Authenticate header. */
+ pjsip_www_authenticate_hdr *hdr;
+ hdr = PJ_POOL_ALLOC_T(pool, pjsip_www_authenticate_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->challenge.common.other_param,
+ &rhs->challenge.common.other_param);
+ return hdr;
+}
+
+
diff --git a/pjsip/src/pjsip/sip_auth_parser.c b/pjsip/src/pjsip/sip_auth_parser.c
new file mode 100644
index 0000000..823372c
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_parser.c
@@ -0,0 +1,308 @@
+/* $Id: sip_auth_parser.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 <pjsip/sip_auth_parser.h>
+#include <pjsip/sip_auth_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+#include <pj/except.h>
+#include <pj/pool.h>
+
+static pjsip_hdr* parse_hdr_authorization ( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_proxy_authorization ( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_www_authenticate ( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_proxy_authenticate ( pjsip_parse_ctx *ctx );
+
+static void parse_digest_credential ( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_digest_credential *cred);
+static void parse_pgp_credential ( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_pgp_credential *cred);
+static void parse_digest_challenge ( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_digest_challenge *chal);
+static void parse_pgp_challenge ( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_pgp_challenge *chal);
+
+const pj_str_t pjsip_USERNAME_STR = { "username", 8 },
+ pjsip_REALM_STR = { "realm", 5},
+ pjsip_NONCE_STR = { "nonce", 5},
+ pjsip_URI_STR = { "uri", 3 },
+ pjsip_RESPONSE_STR = { "response", 8 },
+ pjsip_ALGORITHM_STR = { "algorithm", 9 },
+ pjsip_DOMAIN_STR = { "domain", 6 },
+ pjsip_STALE_STR = { "stale", 5},
+ pjsip_QOP_STR = { "qop", 3},
+ pjsip_CNONCE_STR = { "cnonce", 6},
+ pjsip_OPAQUE_STR = { "opaque", 6},
+ pjsip_NC_STR = { "nc", 2},
+ pjsip_TRUE_STR = { "true", 4},
+ pjsip_QUOTED_TRUE_STR = { "\"true\"", 6},
+ pjsip_FALSE_STR = { "false", 5},
+ pjsip_QUOTED_FALSE_STR = { "\"false\"", 7},
+ pjsip_DIGEST_STR = { "Digest", 6},
+ pjsip_QUOTED_DIGEST_STR = { "\"Digest\"", 8},
+ pjsip_PGP_STR = { "PGP", 3 },
+ pjsip_QUOTED_PGP_STR = { "\"PGP\"", 5 },
+ pjsip_MD5_STR = { "md5", 3 },
+ pjsip_QUOTED_MD5_STR = { "\"md5\"", 5},
+ pjsip_AUTH_STR = { "auth", 4},
+ pjsip_QUOTED_AUTH_STR = { "\"auth\"", 6 };
+
+
+static void parse_digest_credential( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_digest_credential *cred)
+{
+ pj_list_init(&cred->other_param);
+
+ for (;;) {
+ pj_str_t name, value;
+
+ pjsip_parse_param_imp(scanner, pool, &name, &value,
+ PJSIP_PARSE_REMOVE_QUOTE);
+
+ if (!pj_stricmp(&name, &pjsip_USERNAME_STR)) {
+ cred->username = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_REALM_STR)) {
+ cred->realm = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_NONCE_STR)) {
+ cred->nonce = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_URI_STR)) {
+ cred->uri = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_RESPONSE_STR)) {
+ cred->response = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_ALGORITHM_STR)) {
+ cred->algorithm = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_CNONCE_STR)) {
+ cred->cnonce = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_OPAQUE_STR)) {
+ cred->opaque = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_QOP_STR)) {
+ cred->qop = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_NC_STR)) {
+ cred->nc = value;
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = name;
+ p->value = value;
+ pj_list_insert_before(&cred->other_param, p);
+ }
+
+ /* Eat comma */
+ if (!pj_scan_is_eof(scanner) && *scanner->curptr == ',')
+ pj_scan_get_char(scanner);
+ else
+ break;
+ }
+}
+
+static void parse_pgp_credential( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_pgp_credential *cred)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(cred);
+
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+}
+
+static void parse_digest_challenge( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_digest_challenge *chal)
+{
+ pj_list_init(&chal->other_param);
+
+ for (;;) {
+ pj_str_t name, value;
+
+ pjsip_parse_param_imp(scanner, pool, &name, &value,
+ PJSIP_PARSE_REMOVE_QUOTE);
+
+ if (!pj_stricmp(&name, &pjsip_REALM_STR)) {
+ chal->realm = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_DOMAIN_STR)) {
+ chal->domain = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_NONCE_STR)) {
+ chal->nonce = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_OPAQUE_STR)) {
+ chal->opaque = value;
+
+ } else if (!pj_stricmp(&name, &pjsip_STALE_STR)) {
+ if (!pj_stricmp(&value, &pjsip_TRUE_STR) ||
+ !pj_stricmp(&value, &pjsip_QUOTED_TRUE_STR))
+ {
+ chal->stale = 1;
+ }
+
+ } else if (!pj_stricmp(&name, &pjsip_ALGORITHM_STR)) {
+ chal->algorithm = value;
+
+
+ } else if (!pj_stricmp(&name, &pjsip_QOP_STR)) {
+ chal->qop = value;
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = name;
+ p->value = value;
+ pj_list_insert_before(&chal->other_param, p);
+ }
+
+ /* Eat comma */
+ if (!pj_scan_is_eof(scanner) && *scanner->curptr == ',')
+ pj_scan_get_char(scanner);
+ else
+ break;
+ }
+}
+
+static void parse_pgp_challenge( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_pgp_challenge *chal)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(chal);
+
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+}
+
+static void int_parse_hdr_authorization( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_authorization_hdr *hdr)
+{
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ if (*scanner->curptr == '"') {
+ pj_scan_get_quote(scanner, '"', '"', &hdr->scheme);
+ hdr->scheme.ptr++;
+ hdr->scheme.slen -= 2;
+ } else {
+ pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &hdr->scheme);
+ }
+
+ if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
+
+ parse_digest_credential(scanner, pool, &hdr->credential.digest);
+
+ } else if (!pj_stricmp(&hdr->scheme, &pjsip_PGP_STR)) {
+
+ parse_pgp_credential( scanner, pool, &hdr->credential.pgp);
+
+ } else {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ pjsip_parse_end_hdr_imp( scanner );
+}
+
+static void int_parse_hdr_authenticate( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_www_authenticate_hdr *hdr)
+{
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ if (*scanner->curptr == '"') {
+ pj_scan_get_quote(scanner, '"', '"', &hdr->scheme);
+ hdr->scheme.ptr++;
+ hdr->scheme.slen -= 2;
+ } else {
+ pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &hdr->scheme);
+ }
+
+ if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
+
+ parse_digest_challenge(scanner, pool, &hdr->challenge.digest);
+
+ } else if (!pj_stricmp(&hdr->scheme, &pjsip_PGP_STR)) {
+
+ parse_pgp_challenge(scanner, pool, &hdr->challenge.pgp);
+
+ } else {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ pjsip_parse_end_hdr_imp( scanner );
+}
+
+
+static pjsip_hdr* parse_hdr_authorization( pjsip_parse_ctx *ctx )
+{
+ pjsip_authorization_hdr *hdr = pjsip_authorization_hdr_create(ctx->pool);
+ int_parse_hdr_authorization(ctx->scanner, ctx->pool, hdr);
+ return (pjsip_hdr*)hdr;
+}
+
+static pjsip_hdr* parse_hdr_proxy_authorization( pjsip_parse_ctx *ctx )
+{
+ pjsip_proxy_authorization_hdr *hdr =
+ pjsip_proxy_authorization_hdr_create(ctx->pool);
+ int_parse_hdr_authorization(ctx->scanner, ctx->pool, hdr);
+ return (pjsip_hdr*)hdr;
+}
+
+static pjsip_hdr* parse_hdr_www_authenticate( pjsip_parse_ctx *ctx )
+{
+ pjsip_www_authenticate_hdr *hdr =
+ pjsip_www_authenticate_hdr_create(ctx->pool);
+ int_parse_hdr_authenticate(ctx->scanner, ctx->pool, hdr);
+ return (pjsip_hdr*)hdr;
+}
+
+static pjsip_hdr* parse_hdr_proxy_authenticate( pjsip_parse_ctx *ctx )
+{
+ pjsip_proxy_authenticate_hdr *hdr =
+ pjsip_proxy_authenticate_hdr_create(ctx->pool);
+ int_parse_hdr_authenticate(ctx->scanner, ctx->pool, hdr);
+ return (pjsip_hdr*)hdr;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_auth_init_parser()
+{
+ pj_status_t status;
+
+ status = pjsip_register_hdr_parser( "Authorization", NULL,
+ &parse_hdr_authorization);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ status = pjsip_register_hdr_parser( "Proxy-Authorization", NULL,
+ &parse_hdr_proxy_authorization);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ status = pjsip_register_hdr_parser( "WWW-Authenticate", NULL,
+ &parse_hdr_www_authenticate);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ status = pjsip_register_hdr_parser( "Proxy-Authenticate", NULL,
+ &parse_hdr_proxy_authenticate);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(void) pjsip_auth_deinit_parser()
+{
+}
+
diff --git a/pjsip/src/pjsip/sip_auth_parser_wrap.cpp b/pjsip/src/pjsip/sip_auth_parser_wrap.cpp
new file mode 100644
index 0000000..8d4f8a3
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_parser_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_auth_parser_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_auth_parser.c"
diff --git a/pjsip/src/pjsip/sip_auth_server.c b/pjsip/src/pjsip/sip_auth_server.c
new file mode 100644
index 0000000..248e6cc
--- /dev/null
+++ b/pjsip/src/pjsip/sip_auth_server.c
@@ -0,0 +1,225 @@
+/* $Id: sip_auth_server.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 <pjsip/sip_auth.h>
+#include <pjsip/sip_auth_parser.h> /* just to get pjsip_DIGEST_STR */
+#include <pjsip/sip_auth_msg.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_transport.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+
+
+/*
+ * Initialize server authorization session data structure to serve the
+ * specified realm and to use lookup_func function to look for the credential
+ * info.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_init( pj_pool_t *pool,
+ pjsip_auth_srv *auth_srv,
+ const pj_str_t *realm,
+ pjsip_auth_lookup_cred *lookup,
+ unsigned options )
+{
+ PJ_ASSERT_RETURN(pool && auth_srv && realm && lookup, PJ_EINVAL);
+
+ pj_strdup( pool, &auth_srv->realm, realm);
+ auth_srv->lookup = lookup;
+ auth_srv->is_proxy = (options & PJSIP_AUTH_SRV_IS_PROXY);
+
+ return PJ_SUCCESS;
+}
+
+
+/* Verify incoming Authorization/Proxy-Authorization header against the
+ * specified credential.
+ */
+static pj_status_t pjsip_auth_verify( const pjsip_authorization_hdr *hdr,
+ const pj_str_t *method,
+ const pjsip_cred_info *cred_info )
+{
+ if (pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR) == 0) {
+ char digest_buf[PJSIP_MD5STRLEN];
+ pj_str_t digest;
+ const pjsip_digest_credential *dig = &hdr->credential.digest;
+
+ /* Check that username and realm match.
+ * These checks should have been performed before entering this
+ * function.
+ */
+ PJ_ASSERT_RETURN(pj_strcmp(&dig->username, &cred_info->username) == 0,
+ PJ_EINVALIDOP);
+ PJ_ASSERT_RETURN(pj_strcmp(&dig->realm, &cred_info->realm) == 0,
+ PJ_EINVALIDOP);
+
+ /* Prepare for our digest calculation. */
+ digest.ptr = digest_buf;
+ digest.slen = PJSIP_MD5STRLEN;
+
+ /* Create digest for comparison. */
+ pjsip_auth_create_digest(&digest,
+ &hdr->credential.digest.nonce,
+ &hdr->credential.digest.nc,
+ &hdr->credential.digest.cnonce,
+ &hdr->credential.digest.qop,
+ &hdr->credential.digest.uri,
+ &cred_info->realm,
+ cred_info,
+ method );
+
+ /* Compare digest. */
+ return (pj_stricmp(&digest, &hdr->credential.digest.response) == 0) ?
+ PJ_SUCCESS : PJSIP_EAUTHINVALIDDIGEST;
+
+ } else {
+ pj_assert(!"Unsupported authentication scheme");
+ return PJSIP_EINVALIDAUTHSCHEME;
+ }
+}
+
+
+/*
+ * Request the authorization server framework to verify the authorization
+ * information in the specified request in rdata.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_verify( pjsip_auth_srv *auth_srv,
+ pjsip_rx_data *rdata,
+ int *status_code)
+{
+ pjsip_authorization_hdr *h_auth;
+ pjsip_msg *msg = rdata->msg_info.msg;
+ pjsip_hdr_e htype;
+ pj_str_t acc_name;
+ pjsip_cred_info cred_info;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(auth_srv && rdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG);
+
+ htype = auth_srv->is_proxy ? PJSIP_H_PROXY_AUTHORIZATION :
+ PJSIP_H_AUTHORIZATION;
+
+ /* Initialize status with 200. */
+ *status_code = 200;
+
+ /* Find authorization header for our realm. */
+ h_auth = (pjsip_authorization_hdr*) pjsip_msg_find_hdr(msg, htype, NULL);
+ while (h_auth) {
+ if (!pj_stricmp(&h_auth->credential.common.realm, &auth_srv->realm))
+ break;
+
+ h_auth = h_auth->next;
+ if (h_auth == (void*) &msg->hdr) {
+ h_auth = NULL;
+ break;
+ }
+
+ h_auth=(pjsip_authorization_hdr*)pjsip_msg_find_hdr(msg,htype,h_auth);
+ }
+
+ if (!h_auth) {
+ *status_code = auth_srv->is_proxy ? 407 : 401;
+ return PJSIP_EAUTHNOAUTH;
+ }
+
+ /* Check authorization scheme. */
+ if (pj_stricmp(&h_auth->scheme, &pjsip_DIGEST_STR) == 0)
+ acc_name = h_auth->credential.digest.username;
+ else {
+ *status_code = auth_srv->is_proxy ? 407 : 401;
+ return PJSIP_EINVALIDAUTHSCHEME;
+ }
+
+ /* Find the credential information for the account. */
+ status = (*auth_srv->lookup)(rdata->tp_info.pool, &auth_srv->realm,
+ &acc_name, &cred_info);
+ if (status != PJ_SUCCESS) {
+ *status_code = PJSIP_SC_FORBIDDEN;
+ return status;
+ }
+
+ /* Authenticate with the specified credential. */
+ status = pjsip_auth_verify(h_auth, &msg->line.req.method.name,
+ &cred_info);
+ if (status != PJ_SUCCESS) {
+ *status_code = PJSIP_SC_FORBIDDEN;
+ }
+ return status;
+}
+
+
+/*
+ * Add authentication challenge headers to the outgoing response in tdata.
+ * Application may specify its customized nonce and opaque for the challenge,
+ * or can leave the value to NULL to make the function fills them in with
+ * random characters.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_srv_challenge( pjsip_auth_srv *auth_srv,
+ const pj_str_t *qop,
+ const pj_str_t *nonce,
+ const pj_str_t *opaque,
+ pj_bool_t stale,
+ pjsip_tx_data *tdata)
+{
+ pjsip_www_authenticate_hdr *hdr;
+ char nonce_buf[16];
+ pj_str_t random;
+
+ PJ_ASSERT_RETURN( auth_srv && tdata, PJ_EINVAL );
+
+ random.ptr = nonce_buf;
+ random.slen = sizeof(nonce_buf);
+
+ /* Create the header. */
+ if (auth_srv->is_proxy)
+ hdr = pjsip_proxy_authenticate_hdr_create(tdata->pool);
+ else
+ hdr = pjsip_www_authenticate_hdr_create(tdata->pool);
+
+ /* Initialize header.
+ * Note: only support digest authentication now.
+ */
+ hdr->scheme = pjsip_DIGEST_STR;
+ hdr->challenge.digest.algorithm = pjsip_MD5_STR;
+ if (nonce) {
+ pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, nonce);
+ } else {
+ pj_create_random_string(nonce_buf, sizeof(nonce_buf));
+ pj_strdup(tdata->pool, &hdr->challenge.digest.nonce, &random);
+ }
+ if (opaque) {
+ pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, opaque);
+ } else {
+ pj_create_random_string(nonce_buf, sizeof(nonce_buf));
+ pj_strdup(tdata->pool, &hdr->challenge.digest.opaque, &random);
+ }
+ if (qop) {
+ pj_strdup(tdata->pool, &hdr->challenge.digest.qop, qop);
+ } else {
+ hdr->challenge.digest.qop.slen = 0;
+ }
+ pj_strdup(tdata->pool, &hdr->challenge.digest.realm, &auth_srv->realm);
+ hdr->challenge.digest.stale = stale;
+
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr);
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_config.c b/pjsip/src/pjsip/sip_config.c
new file mode 100644
index 0000000..8742c8a
--- /dev/null
+++ b/pjsip/src/pjsip/sip_config.c
@@ -0,0 +1,54 @@
+/* $Id: sip_config.c 3999 2012-03-30 07:10:13Z 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 <pjsip/sip_config.h>
+
+/* pjsip configuration instance, initialized with default values */
+pjsip_cfg_t pjsip_sip_cfg_var =
+{
+ /* Global settings */
+ {
+ PJSIP_ALLOW_PORT_IN_FROMTO_HDR,
+ 0,
+ PJSIP_DONT_SWITCH_TO_TCP
+ },
+
+ /* Transaction settings */
+ {
+ PJSIP_MAX_TSX_COUNT,
+ PJSIP_T1_TIMEOUT,
+ PJSIP_T2_TIMEOUT,
+ PJSIP_T4_TIMEOUT,
+ PJSIP_TD_TIMEOUT
+ },
+
+ /* Client registration client */
+ {
+ PJSIP_REGISTER_CLIENT_CHECK_CONTACT
+ }
+};
+
+
+#ifdef PJ_DLL
+PJ_DEF(pjsip_cfg_t*) pjsip_cfg(void)
+{
+ return &pjsip_sip_cfg_var;
+}
+#endif
diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c
new file mode 100644
index 0000000..008cad2
--- /dev/null
+++ b/pjsip/src/pjsip/sip_dialog.c
@@ -0,0 +1,2250 @@
+/* $Id: sip_dialog.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_ua_layer.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/assert.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/guid.h>
+#include <pj/rand.h>
+#include <pj/array.h>
+#include <pj/except.h>
+#include <pj/hash.h>
+#include <pj/log.h>
+
+#define THIS_FILE "sip_dialog.c"
+
+long pjsip_dlg_lock_tls_id;
+
+/* Config */
+pj_bool_t pjsip_include_allow_hdr_in_dlg = PJSIP_INCLUDE_ALLOW_HDR_IN_DLG;
+
+/* Contact header string */
+static const pj_str_t HCONTACT = { "Contact", 7 };
+
+
+PJ_DEF(pj_bool_t) pjsip_method_creates_dialog(const pjsip_method *m)
+{
+ const pjsip_method subscribe = { PJSIP_OTHER_METHOD, {"SUBSCRIBE", 9}};
+ const pjsip_method refer = { PJSIP_OTHER_METHOD, {"REFER", 5}};
+ const pjsip_method notify = { PJSIP_OTHER_METHOD, {"NOTIFY", 6}};
+ const pjsip_method update = { PJSIP_OTHER_METHOD, {"UPDATE", 6}};
+
+ return m->id == PJSIP_INVITE_METHOD ||
+ (pjsip_method_cmp(m, &subscribe)==0) ||
+ (pjsip_method_cmp(m, &refer)==0) ||
+ (pjsip_method_cmp(m, &notify)==0) ||
+ (pjsip_method_cmp(m, &update)==0);
+}
+
+static pj_status_t create_dialog( pjsip_user_agent *ua,
+ pjsip_dialog **p_dlg)
+{
+ pjsip_endpoint *endpt;
+ pj_pool_t *pool;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ endpt = pjsip_ua_get_endpt(ua);
+ if (!endpt)
+ return PJ_EINVALIDOP;
+
+ pool = pjsip_endpt_create_pool(endpt, "dlg%p",
+ PJSIP_POOL_LEN_DIALOG,
+ PJSIP_POOL_INC_DIALOG);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ dlg = PJ_POOL_ZALLOC_T(pool, pjsip_dialog);
+ PJ_ASSERT_RETURN(dlg != NULL, PJ_ENOMEM);
+
+ dlg->pool = pool;
+ pj_ansi_snprintf(dlg->obj_name, sizeof(dlg->obj_name), "dlg%p", dlg);
+ dlg->ua = ua;
+ dlg->endpt = endpt;
+ dlg->state = PJSIP_DIALOG_STATE_NULL;
+ dlg->add_allow = pjsip_include_allow_hdr_in_dlg;
+
+ pj_list_init(&dlg->inv_hdr);
+ pj_list_init(&dlg->rem_cap_hdr);
+
+ status = pj_mutex_create_recursive(pool, dlg->obj_name, &dlg->mutex_);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pjsip_target_set_init(&dlg->target_set);
+
+ *p_dlg = dlg;
+ return PJ_SUCCESS;
+
+on_error:
+ if (dlg->mutex_)
+ pj_mutex_destroy(dlg->mutex_);
+ pjsip_endpt_release_pool(endpt, pool);
+ return status;
+}
+
+static void destroy_dialog( pjsip_dialog *dlg )
+{
+ if (dlg->mutex_) {
+ pj_mutex_destroy(dlg->mutex_);
+ dlg->mutex_ = NULL;
+ }
+ if (dlg->tp_sel.type != PJSIP_TPSELECTOR_NONE) {
+ pjsip_tpselector_dec_ref(&dlg->tp_sel);
+ pj_bzero(&dlg->tp_sel, sizeof(pjsip_tpselector));
+ }
+ pjsip_endpt_release_pool(dlg->endpt, dlg->pool);
+}
+
+
+/*
+ * Create an UAC dialog.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua,
+ const pj_str_t *local_uri,
+ const pj_str_t *local_contact,
+ const pj_str_t *remote_uri,
+ const pj_str_t *target,
+ pjsip_dialog **p_dlg)
+{
+ pj_status_t status;
+ pj_str_t tmp;
+ pjsip_dialog *dlg;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(ua && local_uri && remote_uri && p_dlg, PJ_EINVAL);
+
+ /* Create dialog instance. */
+ status = create_dialog(ua, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Parse target. */
+ pj_strdup_with_null(dlg->pool, &tmp, target ? target : remote_uri);
+ dlg->target = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0);
+ if (!dlg->target) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ /* Put any header param in the target URI into INVITE header list. */
+ if (PJSIP_URI_SCHEME_IS_SIP(dlg->target) ||
+ PJSIP_URI_SCHEME_IS_SIPS(dlg->target))
+ {
+ pjsip_param *param;
+ pjsip_sip_uri *uri = (pjsip_sip_uri*)pjsip_uri_get_uri(dlg->target);
+
+ param = uri->header_param.next;
+ while (param != &uri->header_param) {
+ pjsip_hdr *hdr;
+ int c;
+
+ c = param->value.ptr[param->value.slen];
+ param->value.ptr[param->value.slen] = '\0';
+
+ hdr = (pjsip_hdr*)
+ pjsip_parse_hdr(dlg->pool, &param->name, param->value.ptr,
+ param->value.slen, NULL);
+
+ param->value.ptr[param->value.slen] = (char)c;
+
+ if (hdr == NULL) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+ pj_list_push_back(&dlg->inv_hdr, hdr);
+
+ param = param->next;
+ }
+
+ /* Now must remove any header params from URL, since that would
+ * create another header in pjsip_endpt_create_request().
+ */
+ pj_list_init(&uri->header_param);
+ }
+
+ /* Add target to the target set */
+ pjsip_target_set_add_uri(&dlg->target_set, dlg->pool, dlg->target, 0);
+
+ /* Init local info. */
+ dlg->local.info = pjsip_from_hdr_create(dlg->pool);
+ pj_strdup_with_null(dlg->pool, &dlg->local.info_str, local_uri);
+ dlg->local.info->uri = pjsip_parse_uri(dlg->pool,
+ dlg->local.info_str.ptr,
+ dlg->local.info_str.slen, 0);
+ if (!dlg->local.info->uri) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ /* Generate local tag. */
+ pj_create_unique_string(dlg->pool, &dlg->local.info->tag);
+
+ /* Calculate hash value of local tag. */
+ dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr,
+ dlg->local.info->tag.slen);
+
+ /* Randomize local CSeq. */
+ dlg->local.first_cseq = pj_rand() & 0x7FFF;
+ dlg->local.cseq = dlg->local.first_cseq;
+
+ /* Init local contact. */
+ pj_strdup_with_null(dlg->pool, &tmp,
+ local_contact ? local_contact : local_uri);
+ dlg->local.contact = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(dlg->pool, &HCONTACT, tmp.ptr,
+ tmp.slen, NULL);
+ if (!dlg->local.contact) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ /* Init remote info. */
+ dlg->remote.info = pjsip_to_hdr_create(dlg->pool);
+ pj_strdup_with_null(dlg->pool, &dlg->remote.info_str, remote_uri);
+ dlg->remote.info->uri = pjsip_parse_uri(dlg->pool,
+ dlg->remote.info_str.ptr,
+ dlg->remote.info_str.slen, 0);
+ if (!dlg->remote.info->uri) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ /* Remove header param from remote.info_str, if any */
+ if (PJSIP_URI_SCHEME_IS_SIP(dlg->remote.info->uri) ||
+ PJSIP_URI_SCHEME_IS_SIPS(dlg->remote.info->uri))
+ {
+ pjsip_sip_uri *sip_uri = (pjsip_sip_uri *)
+ pjsip_uri_get_uri(dlg->remote.info->uri);
+ if (!pj_list_empty(&sip_uri->header_param)) {
+ pj_str_t tmp;
+
+ /* Remove all header param */
+ pj_list_init(&sip_uri->header_param);
+
+ /* Print URI */
+ tmp.ptr = (char*) pj_pool_alloc(dlg->pool,
+ dlg->remote.info_str.slen);
+ tmp.slen = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+ sip_uri, tmp.ptr,
+ dlg->remote.info_str.slen);
+
+ if (tmp.slen < 1) {
+ status = PJSIP_EURITOOLONG;
+ goto on_error;
+ }
+
+ /* Assign remote.info_str */
+ dlg->remote.info_str = tmp;
+ }
+ }
+
+
+ /* Initialize remote's CSeq to -1. */
+ dlg->remote.cseq = dlg->remote.first_cseq = -1;
+
+ /* Initial role is UAC. */
+ dlg->role = PJSIP_ROLE_UAC;
+
+ /* Secure? */
+ dlg->secure = PJSIP_URI_SCHEME_IS_SIPS(dlg->target);
+
+ /* Generate Call-ID header. */
+ dlg->call_id = pjsip_cid_hdr_create(dlg->pool);
+ pj_create_unique_string(dlg->pool, &dlg->call_id->id);
+
+ /* Initial route set is empty. */
+ pj_list_init(&dlg->route_set);
+
+ /* Init client authentication session. */
+ status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt,
+ dlg->pool, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register this dialog to user agent. */
+ status = pjsip_ua_register_dlg( ua, dlg );
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Done! */
+ *p_dlg = dlg;
+
+
+ PJ_LOG(5,(dlg->obj_name, "UAC dialog created"));
+
+ return PJ_SUCCESS;
+
+on_error:
+ destroy_dialog(dlg);
+ return status;
+}
+
+
+/*
+ * Create UAS dialog.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua,
+ pjsip_rx_data *rdata,
+ const pj_str_t *contact,
+ pjsip_dialog **p_dlg)
+{
+ pj_status_t status;
+ pjsip_hdr *pos = NULL;
+ pjsip_contact_hdr *contact_hdr;
+ pjsip_rr_hdr *rr;
+ pjsip_transaction *tsx = NULL;
+ pj_str_t tmp;
+ enum { TMP_LEN=128};
+ pj_ssize_t len;
+ pjsip_dialog *dlg;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(ua && rdata && p_dlg, PJ_EINVAL);
+
+ /* rdata must have request message. */
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Request must not have To tag.
+ * This should have been checked in the user agent (or application?).
+ */
+ PJ_ASSERT_RETURN(rdata->msg_info.to->tag.slen == 0, PJ_EINVALIDOP);
+
+ /* The request must be a dialog establishing request. */
+ PJ_ASSERT_RETURN(
+ pjsip_method_creates_dialog(&rdata->msg_info.msg->line.req.method),
+ PJ_EINVALIDOP);
+
+ /* Create dialog instance. */
+ status = create_dialog(ua, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Temprary string for getting the string representation of
+ * both local and remote URI.
+ */
+ tmp.ptr = (char*) pj_pool_alloc(rdata->tp_info.pool, TMP_LEN);
+
+ /* Init local info from the To header. */
+ dlg->local.info = (pjsip_fromto_hdr*)
+ pjsip_hdr_clone(dlg->pool, rdata->msg_info.to);
+ pjsip_fromto_hdr_set_from(dlg->local.info);
+
+ /* Generate local tag. */
+ pj_create_unique_string(dlg->pool, &dlg->local.info->tag);
+
+
+ /* Print the local info. */
+ len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+ dlg->local.info->uri, tmp.ptr, TMP_LEN);
+ if (len < 1) {
+ pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->");
+ tmp.slen = pj_ansi_strlen(tmp.ptr);
+ } else
+ tmp.slen = len;
+
+ /* Save the local info. */
+ pj_strdup(dlg->pool, &dlg->local.info_str, &tmp);
+
+ /* Calculate hash value of local tag. */
+ dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr,
+ dlg->local.info->tag.slen);
+
+
+ /* Randomize local cseq */
+ dlg->local.first_cseq = pj_rand() & 0x7FFF;
+ dlg->local.cseq = dlg->local.first_cseq;
+
+ /* Init local contact. */
+ /* TODO:
+ * Section 12.1.1, paragraph about using SIPS URI in Contact.
+ * If the request that initiated the dialog contained a SIPS URI
+ * in the Request-URI or in the top Record-Route header field value,
+ * if there was any, or the Contact header field if there was no
+ * Record-Route header field, the Contact header field in the response
+ * MUST be a SIPS URI.
+ */
+ if (contact) {
+ pj_str_t tmp;
+
+ pj_strdup_with_null(dlg->pool, &tmp, contact);
+ dlg->local.contact = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(dlg->pool, &HCONTACT, tmp.ptr,
+ tmp.slen, NULL);
+ if (!dlg->local.contact) {
+ status = PJSIP_EINVALIDURI;
+ goto on_error;
+ }
+
+ } else {
+ dlg->local.contact = pjsip_contact_hdr_create(dlg->pool);
+ dlg->local.contact->uri = dlg->local.info->uri;
+ }
+
+ /* Init remote info from the From header. */
+ dlg->remote.info = (pjsip_fromto_hdr*)
+ pjsip_hdr_clone(dlg->pool, rdata->msg_info.from);
+ pjsip_fromto_hdr_set_to(dlg->remote.info);
+
+ /* Print the remote info. */
+ len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR,
+ dlg->remote.info->uri, tmp.ptr, TMP_LEN);
+ if (len < 1) {
+ pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->");
+ tmp.slen = pj_ansi_strlen(tmp.ptr);
+ } else
+ tmp.slen = len;
+
+ /* Save the remote info. */
+ pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp);
+
+
+ /* Init remote's contact from Contact header.
+ * Iterate the Contact URI until we find sip: or sips: scheme.
+ */
+ do {
+ contact_hdr = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+ pos);
+ if (contact_hdr) {
+ if (!contact_hdr->uri ||
+ (!PJSIP_URI_SCHEME_IS_SIP(contact_hdr->uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(contact_hdr->uri)))
+ {
+ pos = (pjsip_hdr*)contact_hdr->next;
+ if (pos == &rdata->msg_info.msg->hdr)
+ contact_hdr = NULL;
+ } else {
+ break;
+ }
+ }
+ } while (contact_hdr);
+
+ if (!contact_hdr) {
+ status = PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_REQUEST);
+ goto on_error;
+ }
+
+ dlg->remote.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, (pjsip_hdr*)contact_hdr);
+
+ /* Init remote's CSeq from CSeq header */
+ dlg->remote.cseq = dlg->remote.first_cseq = rdata->msg_info.cseq->cseq;
+
+ /* Set initial target to remote's Contact. */
+ dlg->target = dlg->remote.contact->uri;
+
+ /* Initial role is UAS */
+ dlg->role = PJSIP_ROLE_UAS;
+
+ /* Secure?
+ * RFC 3261 Section 12.1.1:
+ * If the request arrived over TLS, and the Request-URI contained a
+ * SIPS URI, the 'secure' flag is set to TRUE.
+ */
+ dlg->secure = PJSIP_TRANSPORT_IS_SECURE(rdata->tp_info.transport) &&
+ PJSIP_URI_SCHEME_IS_SIPS(rdata->msg_info.msg->line.req.uri);
+
+ /* Call-ID */
+ dlg->call_id = (pjsip_cid_hdr*)
+ pjsip_hdr_clone(dlg->pool, rdata->msg_info.cid);
+
+ /* Route set.
+ * RFC 3261 Section 12.1.1:
+ * The route set MUST be set to the list of URIs in the Record-Route
+ * header field from the request, taken in order and preserving all URI
+ * parameters. If no Record-Route header field is present in the request,
+ * the route set MUST be set to the empty set.
+ */
+ pj_list_init(&dlg->route_set);
+ rr = rdata->msg_info.record_route;
+ while (rr != NULL) {
+ pjsip_route_hdr *route;
+
+ /* Clone the Record-Route, change the type to Route header. */
+ route = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, rr);
+ pjsip_routing_hdr_set_route(route);
+
+ /* Add to route set. */
+ pj_list_push_back(&dlg->route_set, route);
+
+ /* Find next Record-Route header. */
+ rr = rr->next;
+ if (rr == (void*)&rdata->msg_info.msg->hdr)
+ break;
+ rr = (pjsip_route_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg,
+ PJSIP_H_RECORD_ROUTE, rr);
+ }
+ dlg->route_set_frozen = PJ_TRUE;
+
+ /* Init client authentication session. */
+ status = pjsip_auth_clt_init(&dlg->auth_sess, dlg->endpt,
+ dlg->pool, 0);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Create UAS transaction for this request. */
+ status = pjsip_tsx_create_uas(dlg->ua, rdata, &tsx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Associate this dialog to the transaction. */
+ tsx->mod_data[dlg->ua->id] = dlg;
+
+ /* Increment tsx counter */
+ ++dlg->tsx_count;
+
+ /* Calculate hash value of remote tag. */
+ dlg->remote.tag_hval = pj_hash_calc(0, dlg->remote.info->tag.ptr,
+ dlg->remote.info->tag.slen);
+
+ /* Update remote capabilities info */
+ pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg, PJ_TRUE);
+
+ /* Register this dialog to user agent. */
+ status = pjsip_ua_register_dlg( ua, dlg );
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Put this dialog in rdata's mod_data */
+ rdata->endpt_info.mod_data[ua->id] = dlg;
+
+ PJ_TODO(DIALOG_APP_TIMER);
+
+ /* Feed the first request to the transaction. */
+ pjsip_tsx_recv_msg(tsx, rdata);
+
+ /* Done. */
+ *p_dlg = dlg;
+ PJ_LOG(5,(dlg->obj_name, "UAS dialog created"));
+ return PJ_SUCCESS;
+
+on_error:
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, 500);
+ pj_assert(dlg->tsx_count>0);
+ --dlg->tsx_count;
+ }
+
+ destroy_dialog(dlg);
+ return status;
+}
+
+
+/*
+ * Bind dialog to a specific transport/listener.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_transport( pjsip_dialog *dlg,
+ const pjsip_tpselector *sel)
+{
+ /* Validate */
+ PJ_ASSERT_RETURN(dlg && sel, PJ_EINVAL);
+
+ /* Start locking the dialog. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Decrement reference counter of previous transport selector */
+ pjsip_tpselector_dec_ref(&dlg->tp_sel);
+
+ /* Copy transport selector structure .*/
+ pj_memcpy(&dlg->tp_sel, sel, sizeof(*sel));
+
+ /* Increment reference counter */
+ pjsip_tpselector_add_ref(&dlg->tp_sel);
+
+ /* Unlock dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Set "sent-by" field of Via header.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_via_sent_by( pjsip_dialog *dlg,
+ pjsip_host_port *via_addr,
+ pjsip_transport *via_tp)
+{
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+
+ if (!via_addr)
+ pj_bzero(&dlg->via_addr, sizeof(dlg->via_addr));
+ else
+ dlg->via_addr = *via_addr;
+ dlg->via_tp = via_tp;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create forked dialog from a response.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_fork( const pjsip_dialog *first_dlg,
+ const pjsip_rx_data *rdata,
+ pjsip_dialog **new_dlg )
+{
+ pjsip_dialog *dlg;
+ const pjsip_msg *msg = rdata->msg_info.msg;
+ const pjsip_hdr *end_hdr, *hdr;
+ const pjsip_contact_hdr *contact;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(first_dlg && rdata && new_dlg, PJ_EINVAL);
+
+ /* rdata must be response message. */
+ PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+
+ /* Status code MUST be 1xx (but not 100), or 2xx */
+ status = msg->line.status.code;
+ PJ_ASSERT_RETURN( (status/100==1 && status!=100) ||
+ (status/100==2), PJ_EBUG);
+
+ /* To tag must present in the response. */
+ PJ_ASSERT_RETURN(rdata->msg_info.to->tag.slen != 0, PJSIP_EMISSINGTAG);
+
+ /* Find Contact header in the response */
+ contact = (const pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(msg, PJSIP_H_CONTACT, NULL);
+ if (contact == NULL || contact->uri == NULL)
+ return PJSIP_EMISSINGHDR;
+
+ /* Create the dialog. */
+ status = create_dialog((pjsip_user_agent*)first_dlg->ua, &dlg);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set remote target from the response. */
+ dlg->target = (pjsip_uri*) pjsip_uri_clone(dlg->pool, contact->uri);
+
+ /* Clone local info. */
+ dlg->local.info = (pjsip_fromto_hdr*)
+ pjsip_hdr_clone(dlg->pool, first_dlg->local.info);
+
+ /* Clone local tag. */
+ pj_strdup(dlg->pool, &dlg->local.info->tag, &first_dlg->local.info->tag);
+ dlg->local.tag_hval = first_dlg->local.tag_hval;
+
+ /* Clone local CSeq. */
+ dlg->local.first_cseq = first_dlg->local.first_cseq;
+ dlg->local.cseq = first_dlg->local.cseq;
+
+ /* Clone local Contact. */
+ dlg->local.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, first_dlg->local.contact);
+
+ /* Clone remote info. */
+ dlg->remote.info = (pjsip_fromto_hdr*)
+ pjsip_hdr_clone(dlg->pool, first_dlg->remote.info);
+
+ /* Set remote tag from the response. */
+ pj_strdup(dlg->pool, &dlg->remote.info->tag, &rdata->msg_info.to->tag);
+
+ /* Initialize remote's CSeq to -1. */
+ dlg->remote.cseq = dlg->remote.first_cseq = -1;
+
+ /* Initial role is UAC. */
+ dlg->role = PJSIP_ROLE_UAC;
+
+ /* Dialog state depends on the response. */
+ status = msg->line.status.code/100;
+ if (status == 1 || status == 2)
+ dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED;
+ else {
+ pj_assert(!"Invalid status code");
+ dlg->state = PJSIP_DIALOG_STATE_NULL;
+ }
+
+ /* Secure? */
+ dlg->secure = PJSIP_URI_SCHEME_IS_SIPS(dlg->target);
+
+ /* Clone Call-ID header. */
+ dlg->call_id = (pjsip_cid_hdr*)
+ pjsip_hdr_clone(dlg->pool, first_dlg->call_id);
+
+ /* Get route-set from the response. */
+ pj_list_init(&dlg->route_set);
+ end_hdr = &msg->hdr;
+ for (hdr=msg->hdr.prev; hdr!=end_hdr; hdr=hdr->prev) {
+ if (hdr->type == PJSIP_H_RECORD_ROUTE) {
+ pjsip_route_hdr *r;
+ r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, hdr);
+ pjsip_routing_hdr_set_route(r);
+ pj_list_push_back(&dlg->route_set, r);
+ }
+ }
+
+ //dlg->route_set_frozen = PJ_TRUE;
+
+ /* Clone client authentication session. */
+ status = pjsip_auth_clt_clone(dlg->pool, &dlg->auth_sess,
+ &first_dlg->auth_sess);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register this dialog to user agent. */
+ status = pjsip_ua_register_dlg(dlg->ua, dlg );
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Done! */
+ *new_dlg = dlg;
+
+ PJ_LOG(5,(dlg->obj_name, "Forked dialog created"));
+ return PJ_SUCCESS;
+
+on_error:
+ destroy_dialog(dlg);
+ return status;
+}
+
+
+/*
+ * Destroy dialog.
+ */
+static pj_status_t unregister_and_destroy_dialog( pjsip_dialog *dlg )
+{
+ pj_status_t status;
+
+ /* Lock must have been held. */
+
+ /* Check dialog state. */
+ /* Number of sessions must be zero. */
+ PJ_ASSERT_RETURN(dlg->sess_count==0, PJ_EINVALIDOP);
+
+ /* MUST not have pending transactions. */
+ PJ_ASSERT_RETURN(dlg->tsx_count==0, PJ_EINVALIDOP);
+
+ /* Unregister from user agent. */
+ status = pjsip_ua_unregister_dlg(dlg->ua, dlg);
+ if (status != PJ_SUCCESS) {
+ pj_assert(!"Unexpected failed unregistration!");
+ return status;
+ }
+
+ /* Log */
+ PJ_LOG(5,(dlg->obj_name, "Dialog destroyed"));
+
+ /* Destroy this dialog. */
+ destroy_dialog(dlg);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Forcefully terminate dialog.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_terminate( pjsip_dialog *dlg )
+{
+ /* Number of sessions must be zero. */
+ PJ_ASSERT_RETURN(dlg->sess_count==0, PJ_EINVALIDOP);
+
+ /* MUST not have pending transactions. */
+ PJ_ASSERT_RETURN(dlg->tsx_count==0, PJ_EINVALIDOP);
+
+ return unregister_and_destroy_dialog(dlg);
+}
+
+
+/*
+ * Set route_set
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_route_set( pjsip_dialog *dlg,
+ const pjsip_route_hdr *route_set )
+{
+ pjsip_route_hdr *r;
+
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Clear route set. */
+ pj_list_init(&dlg->route_set);
+
+ if (!route_set) {
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_SUCCESS;
+ }
+
+ r = route_set->next;
+ while (r != route_set) {
+ pjsip_route_hdr *new_r;
+
+ new_r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, r);
+ pj_list_push_back(&dlg->route_set, new_r);
+
+ r = r->next;
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Increment session counter.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_inc_session( pjsip_dialog *dlg,
+ pjsip_module *mod )
+{
+ PJ_ASSERT_RETURN(dlg && mod, PJ_EINVAL);
+
+ pj_log_push_indent();
+
+ pjsip_dlg_inc_lock(dlg);
+ ++dlg->sess_count;
+ pjsip_dlg_dec_lock(dlg);
+
+ PJ_LOG(5,(dlg->obj_name, "Session count inc to %d by %.*s",
+ dlg->sess_count, (int)mod->name.slen, mod->name.ptr));
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+/*
+ * Lock dialog and increment session counter temporarily
+ * to prevent it from being deleted. In addition, it must lock
+ * the user agent's dialog table first, to prevent deadlock.
+ */
+PJ_DEF(void) pjsip_dlg_inc_lock(pjsip_dialog *dlg)
+{
+ PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+
+ pj_mutex_lock(dlg->mutex_);
+ dlg->sess_count++;
+
+ PJ_LOG(6,(dlg->obj_name, "Leaving pjsip_dlg_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+}
+
+/* Try to acquire dialog's mutex, but bail out if mutex can not be
+ * acquired immediately.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_try_inc_lock(pjsip_dialog *dlg)
+{
+ pj_status_t status;
+
+ PJ_LOG(6,(dlg->obj_name,"Entering pjsip_dlg_try_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+
+ status = pj_mutex_trylock(dlg->mutex_);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(6,(dlg->obj_name, "pjsip_dlg_try_inc_lock() failed"));
+ return status;
+ }
+
+ dlg->sess_count++;
+
+ PJ_LOG(6,(dlg->obj_name, "Leaving pjsip_dlg_try_inc_lock(), sess_count=%d",
+ dlg->sess_count));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Unlock dialog and decrement session counter.
+ * It may delete the dialog!
+ */
+PJ_DEF(void) pjsip_dlg_dec_lock(pjsip_dialog *dlg)
+{
+ PJ_ASSERT_ON_FAIL(dlg!=NULL, return);
+
+ PJ_LOG(6,(dlg->obj_name, "Entering pjsip_dlg_dec_lock(), sess_count=%d",
+ dlg->sess_count));
+
+ pj_assert(dlg->sess_count > 0);
+ --dlg->sess_count;
+
+ if (dlg->sess_count==0 && dlg->tsx_count==0) {
+ pj_mutex_unlock(dlg->mutex_);
+ pj_mutex_lock(dlg->mutex_);
+ unregister_and_destroy_dialog(dlg);
+ } else {
+ pj_mutex_unlock(dlg->mutex_);
+ }
+
+ PJ_LOG(6,(THIS_FILE, "Leaving pjsip_dlg_dec_lock() (dlg=%p)", dlg));
+}
+
+
+
+/*
+ * Decrement session counter.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_dec_session( pjsip_dialog *dlg,
+ pjsip_module *mod)
+{
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+
+ pj_log_push_indent();
+
+ PJ_LOG(5,(dlg->obj_name, "Session count dec to %d by %.*s",
+ dlg->sess_count-1, (int)mod->name.slen, mod->name.ptr));
+
+ pjsip_dlg_inc_lock(dlg);
+ --dlg->sess_count;
+ pjsip_dlg_dec_lock(dlg);
+
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+}
+
+/*
+ * Check if the module is registered as a usage
+ */
+PJ_DEF(pj_bool_t) pjsip_dlg_has_usage( pjsip_dialog *dlg,
+ pjsip_module *mod)
+{
+ unsigned index;
+ pj_bool_t found = PJ_FALSE;
+
+ pjsip_dlg_inc_lock(dlg);
+ for (index=0; index<dlg->usage_cnt; ++index) {
+ if (dlg->usage[index] == mod) {
+ found = PJ_TRUE;
+ break;
+ }
+ }
+ pjsip_dlg_dec_lock(dlg);
+
+ return found;
+}
+
+/*
+ * Add usage.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_add_usage( pjsip_dialog *dlg,
+ pjsip_module *mod,
+ void *mod_data )
+{
+ unsigned index;
+
+ PJ_ASSERT_RETURN(dlg && mod, PJ_EINVAL);
+ PJ_ASSERT_RETURN(mod->id >= 0 && mod->id < PJSIP_MAX_MODULE,
+ PJ_EINVAL);
+ PJ_ASSERT_RETURN(dlg->usage_cnt < PJSIP_MAX_MODULE, PJ_EBUG);
+
+ PJ_LOG(5,(dlg->obj_name,
+ "Module %.*s added as dialog usage, data=%p",
+ (int)mod->name.slen, mod->name.ptr, mod_data));
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Usages are sorted on priority, lowest number first.
+ * Find position to put the new module, also makes sure that
+ * this module has not been registered before.
+ */
+ for (index=0; index<dlg->usage_cnt; ++index) {
+ if (dlg->usage[index] == mod) {
+ /* Module may be registered more than once in the same dialog.
+ * For example, when call transfer fails, application may retry
+ * call transfer on the same dialog.
+ * So return PJ_SUCCESS here.
+ */
+ PJ_LOG(4,(dlg->obj_name,
+ "Module %.*s already registered as dialog usage, "
+ "updating the data %p",
+ (int)mod->name.slen, mod->name.ptr, mod_data));
+ dlg->mod_data[mod->id] = mod_data;
+
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_SUCCESS;
+
+ //pj_assert(!"This module is already registered");
+ //pjsip_dlg_dec_lock(dlg);
+ //return PJSIP_ETYPEEXISTS;
+ }
+
+ if (dlg->usage[index]->priority > mod->priority)
+ break;
+ }
+
+ /* index holds position to put the module.
+ * Insert module at this index.
+ */
+ pj_array_insert(dlg->usage, sizeof(dlg->usage[0]), dlg->usage_cnt,
+ index, &mod);
+
+ /* Set module data. */
+ dlg->mod_data[mod->id] = mod_data;
+
+ /* Increment count. */
+ ++dlg->usage_cnt;
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Attach module specific data to the dialog. Application can also set
+ * the value directly by accessing dlg->mod_data[module_id].
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_mod_data( pjsip_dialog *dlg,
+ int mod_id,
+ void *data )
+{
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(mod_id >= 0 && mod_id < PJSIP_MAX_MODULE,
+ PJ_EINVAL);
+ dlg->mod_data[mod_id] = data;
+ return PJ_SUCCESS;
+}
+
+/**
+ * Get module specific data previously attached to the dialog. Application
+ * can also get value directly by accessing dlg->mod_data[module_id].
+ */
+PJ_DEF(void*) pjsip_dlg_get_mod_data( pjsip_dialog *dlg,
+ int mod_id)
+{
+ PJ_ASSERT_RETURN(dlg, NULL);
+ PJ_ASSERT_RETURN(mod_id >= 0 && mod_id < PJSIP_MAX_MODULE,
+ NULL);
+ return dlg->mod_data[mod_id];
+}
+
+
+/*
+ * Create a new request within dialog (i.e. after the dialog session has been
+ * established). The construction of such requests follows the rule in
+ * RFC3261 section 12.2.1.
+ */
+static pj_status_t dlg_create_request_throw( pjsip_dialog *dlg,
+ const pjsip_method *method,
+ int cseq,
+ pjsip_tx_data **p_tdata )
+{
+ pjsip_tx_data *tdata;
+ pjsip_contact_hdr *contact;
+ pjsip_route_hdr *route, *end_list;
+ pj_status_t status;
+
+ /* Contact Header field.
+ * Contact can only be present in requests that establish dialog (in the
+ * core SIP spec, only INVITE).
+ */
+ if (pjsip_method_creates_dialog(method))
+ contact = dlg->local.contact;
+ else
+ contact = NULL;
+
+ /*
+ * Create the request by cloning from the headers in the
+ * dialog.
+ */
+ status = pjsip_endpt_create_request_from_hdr(dlg->endpt,
+ method,
+ dlg->target,
+ dlg->local.info,
+ dlg->remote.info,
+ contact,
+ dlg->call_id,
+ cseq,
+ NULL,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Just copy dialog route-set to Route header.
+ * The transaction will do the processing as specified in Section 12.2.1
+ * of RFC 3261 in function tsx_process_route() in sip_transaction.c.
+ */
+ route = dlg->route_set.next;
+ end_list = &dlg->route_set;
+ for (; route != end_list; route = route->next ) {
+ pjsip_route_hdr *r;
+ r = (pjsip_route_hdr*) pjsip_hdr_shallow_clone( tdata->pool, route );
+ pjsip_routing_hdr_set_route(r);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)r);
+ }
+
+ /* Copy authorization headers, if request is not ACK or CANCEL. */
+ if (method->id != PJSIP_ACK_METHOD && method->id != PJSIP_CANCEL_METHOD) {
+ status = pjsip_auth_clt_init_req( &dlg->auth_sess, tdata );
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Done. */
+ *p_tdata = tdata;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Create outgoing request.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_create_request( pjsip_dialog *dlg,
+ const pjsip_method *method,
+ int cseq,
+ pjsip_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata = NULL;
+ PJ_USE_EXCEPTION;
+
+ PJ_ASSERT_RETURN(dlg && method && p_tdata, PJ_EINVAL);
+
+ /* Lock dialog. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Use outgoing CSeq and increment it by one. */
+ if (cseq < 0)
+ cseq = dlg->local.cseq + 1;
+
+ /* Keep compiler happy */
+ status = PJ_EBUG;
+
+ /* Create the request. */
+ PJ_TRY {
+ status = dlg_create_request_throw(dlg, method, cseq, &tdata);
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ }
+ PJ_END;
+
+ /* Failed! Delete transmit data. */
+ if (status != PJ_SUCCESS && tdata) {
+ pjsip_tx_data_dec_ref( tdata );
+ tdata = NULL;
+ }
+
+ /* Unlock dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ *p_tdata = tdata;
+
+ return status;
+}
+
+
+/*
+ * Send request statefully, and update dialog'c CSeq.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_send_request( pjsip_dialog *dlg,
+ pjsip_tx_data *tdata,
+ int mod_data_id,
+ void *mod_data)
+{
+ pjsip_transaction *tsx;
+ pjsip_msg *msg = tdata->msg;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(dlg && tdata && tdata->msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ pj_log_push_indent();
+ PJ_LOG(5,(dlg->obj_name, "Sending %s",
+ pjsip_tx_data_get_info(tdata)));
+
+ /* Lock and increment session */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* If via_addr is set, use this address for the Via header. */
+ if (dlg->via_addr.host.slen > 0) {
+ tdata->via_addr = dlg->via_addr;
+ tdata->via_tp = dlg->via_tp;
+ }
+
+ /* Update dialog's CSeq and message's CSeq if request is not
+ * ACK nor CANCEL.
+ */
+ if (msg->line.req.method.id != PJSIP_CANCEL_METHOD &&
+ msg->line.req.method.id != PJSIP_ACK_METHOD)
+ {
+ pjsip_cseq_hdr *ch;
+
+ ch = PJSIP_MSG_CSEQ_HDR(msg);
+ PJ_ASSERT_RETURN(ch!=NULL, PJ_EBUG);
+
+ ch->cseq = dlg->local.cseq++;
+
+ /* Force the whole message to be re-printed. */
+ pjsip_tx_data_invalidate_msg( tdata );
+ }
+
+ /* Create a new transaction if method is not ACK.
+ * The transaction user is the user agent module.
+ */
+ if (msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ int tsx_count;
+
+ status = pjsip_tsx_create_uac(dlg->ua, tdata, &tsx);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Set transport selector */
+ status = pjsip_tsx_set_transport(tsx, &dlg->tp_sel);
+ pj_assert(status == PJ_SUCCESS);
+
+ /* Attach this dialog to the transaction, so that user agent
+ * will dispatch events to this dialog.
+ */
+ tsx->mod_data[dlg->ua->id] = dlg;
+
+ /* Copy optional caller's mod_data, if present */
+ if (mod_data_id >= 0 && mod_data_id < PJSIP_MAX_MODULE)
+ tsx->mod_data[mod_data_id] = mod_data;
+
+ /* Increment transaction counter. */
+ tsx_count = ++dlg->tsx_count;
+
+ /* Send the message. */
+ status = pjsip_tsx_send_msg(tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ if (dlg->tsx_count == tsx_count)
+ pjsip_tsx_terminate(tsx, tsx->status_code);
+ goto on_error;
+ }
+
+ } else {
+ /* Set transport selector */
+ pjsip_tx_data_set_transport(tdata, &dlg->tp_sel);
+
+ /* Send request */
+ status = pjsip_endpt_send_request_stateless(dlg->endpt, tdata,
+ NULL, NULL);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ }
+
+ /* Unlock dialog, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+ return PJ_SUCCESS;
+
+on_error:
+ /* Unlock dialog, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Whatever happen delete the message. */
+ pjsip_tx_data_dec_ref( tdata );
+ pj_log_pop_indent();
+ return status;
+}
+
+/* Add standard headers for certain types of response */
+static void dlg_beautify_response(pjsip_dialog *dlg,
+ pj_bool_t add_headers,
+ int st_code,
+ pjsip_tx_data *tdata)
+{
+ pjsip_cseq_hdr *cseq;
+ int st_class;
+ const pjsip_hdr *c_hdr;
+ pjsip_hdr *hdr;
+
+ cseq = PJSIP_MSG_CSEQ_HDR(tdata->msg);
+ pj_assert(cseq != NULL);
+
+ st_class = st_code / 100;
+
+ /* Contact, Allow, Supported header. */
+ if (add_headers && pjsip_method_creates_dialog(&cseq->method)) {
+ /* Add Contact header for 1xx, 2xx, 3xx and 485 response. */
+ if (st_class==2 || st_class==3 || (st_class==1 && st_code != 100) ||
+ st_code==485)
+ {
+ /* Add contact header only if one is not present. */
+ if (pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL) == 0 &&
+ pjsip_msg_find_hdr_by_name(tdata->msg, &HCONTACT, NULL) == 0)
+ {
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool,
+ dlg->local.contact);
+ pjsip_msg_add_hdr(tdata->msg, hdr);
+ }
+ }
+
+ /* Add Allow header in 18x, 2xx and 405 response. */
+ if ((((st_code/10==18 || st_class==2) && dlg->add_allow)
+ || st_code==405) &&
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ALLOW, NULL)==NULL)
+ {
+ c_hdr = pjsip_endpt_get_capability(dlg->endpt,
+ PJSIP_H_ALLOW, NULL);
+ if (c_hdr) {
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, c_hdr);
+ pjsip_msg_add_hdr(tdata->msg, hdr);
+ }
+ }
+
+ /* Add Supported header in 2xx response. */
+ if (st_class==2 &&
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL)==NULL)
+ {
+ c_hdr = pjsip_endpt_get_capability(dlg->endpt,
+ PJSIP_H_SUPPORTED, NULL);
+ if (c_hdr) {
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, c_hdr);
+ pjsip_msg_add_hdr(tdata->msg, hdr);
+ }
+ }
+
+ }
+
+ /* Add To tag in all responses except 100 */
+ if (st_code != 100) {
+ pjsip_to_hdr *to;
+
+ to = PJSIP_MSG_TO_HDR(tdata->msg);
+ pj_assert(to != NULL);
+
+ to->tag = dlg->local.info->tag;
+
+ if (dlg->state == PJSIP_DIALOG_STATE_NULL)
+ dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED;
+ }
+}
+
+
+/*
+ * Create response.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_create_response( pjsip_dialog *dlg,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ pjsip_tx_data **p_tdata)
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ /* Create generic response.
+ * This will initialize response's Via, To, From, Call-ID, CSeq
+ * and Record-Route headers from the request.
+ */
+ status = pjsip_endpt_create_response(dlg->endpt,
+ rdata, st_code, st_text, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Lock the dialog. */
+ pjsip_dlg_inc_lock(dlg);
+
+ dlg_beautify_response(dlg, PJ_FALSE, st_code, tdata);
+
+ /* Unlock the dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Done. */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+/*
+ * Modify response.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_modify_response( pjsip_dialog *dlg,
+ pjsip_tx_data *tdata,
+ int st_code,
+ const pj_str_t *st_text)
+{
+ pjsip_hdr *hdr;
+
+ PJ_ASSERT_RETURN(dlg && tdata && tdata->msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+ PJ_ASSERT_RETURN(st_code >= 100 && st_code <= 699, PJ_EINVAL);
+
+ /* Lock and increment session */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Replace status code and reason */
+ tdata->msg->line.status.code = st_code;
+ if (st_text) {
+ pj_strdup(tdata->pool, &tdata->msg->line.status.reason, st_text);
+ } else {
+ tdata->msg->line.status.reason = *pjsip_get_status_text(st_code);
+ }
+
+ /* Remove existing Contact header (without this, when dialog sent
+ * 180 and then 302, the Contact in 302 will not get updated).
+ */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
+ if (hdr)
+ pj_list_erase(hdr);
+
+ /* Add tag etc. if necessary */
+ dlg_beautify_response(dlg, st_code/100 <= 2, st_code, tdata);
+
+
+ /* Must add reference counter, since tsx_send_msg() will decrement it */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Force to re-print message. */
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Send response statefully.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_send_response( pjsip_dialog *dlg,
+ pjsip_transaction *tsx,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status;
+
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(dlg && tsx && tdata && tdata->msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG,
+ PJSIP_ENOTRESPONSEMSG);
+
+ /* The transaction must belong to this dialog. */
+ PJ_ASSERT_RETURN(tsx->mod_data[dlg->ua->id] == dlg, PJ_EINVALIDOP);
+
+ pj_log_push_indent();
+
+ PJ_LOG(5,(dlg->obj_name, "Sending %s",
+ pjsip_tx_data_get_info(tdata)));
+
+ /* Check that transaction method and cseq match the response.
+ * This operation is sloooww (search CSeq header twice), that's why
+ * we only do it in debug mode.
+ */
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ PJ_ASSERT_RETURN( PJSIP_MSG_CSEQ_HDR(tdata->msg)->cseq == tsx->cseq &&
+ pjsip_method_cmp(&PJSIP_MSG_CSEQ_HDR(tdata->msg)->method,
+ &tsx->method)==0,
+ PJ_EINVALIDOP);
+#endif
+
+ /* Must acquire dialog first, to prevent deadlock */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Last chance to add mandatory headers before the response is
+ * sent.
+ */
+ dlg_beautify_response(dlg, PJ_TRUE, tdata->msg->line.status.code, tdata);
+
+ /* If the dialog is locked to transport, make sure that transaction
+ * is locked to the same transport too.
+ */
+ if (dlg->tp_sel.type != tsx->tp_sel.type ||
+ dlg->tp_sel.u.ptr != tsx->tp_sel.u.ptr)
+ {
+ status = pjsip_tsx_set_transport(tsx, &dlg->tp_sel);
+ pj_assert(status == PJ_SUCCESS);
+ }
+
+ /* Ask transaction to send the response */
+ status = pjsip_tsx_send_msg(tsx, tdata);
+
+ /* This function must decrement transmit data request counter
+ * regardless of the operation status. The transaction only
+ * decrements the counter if the operation is successful.
+ */
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+
+ return status;
+}
+
+
+/*
+ * Combo function to create and send response statefully.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_respond( pjsip_dialog *dlg,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjsip_hdr *hdr_list,
+ const pjsip_msg_body *body )
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(dlg && rdata && rdata->msg_info.msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* The transaction must belong to this dialog. */
+ PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata) &&
+ pjsip_rdata_get_tsx(rdata)->mod_data[dlg->ua->id] == dlg,
+ PJ_EINVALIDOP);
+
+ /* Create the response. */
+ status = pjsip_dlg_create_response(dlg, rdata, st_code, st_text, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add additional header, if any */
+ if (hdr_list) {
+ const pjsip_hdr *hdr;
+
+ hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
+ hdr = hdr->next;
+ }
+ }
+
+ /* Add the message body, if any. */
+ if (body) {
+ tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body);
+ }
+
+ /* Send the response. */
+ return pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata);
+}
+
+
+/* This function is called by user agent upon receiving incoming request
+ * message.
+ */
+void pjsip_dlg_on_rx_request( pjsip_dialog *dlg, pjsip_rx_data *rdata )
+{
+ pj_status_t status;
+ pjsip_transaction *tsx = NULL;
+ pj_bool_t processed = PJ_FALSE;
+ unsigned i;
+
+ PJ_LOG(5,(dlg->obj_name, "Received %s",
+ pjsip_rx_data_get_info(rdata)));
+ pj_log_push_indent();
+
+ /* Lock dialog and increment session. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Check CSeq */
+ if (rdata->msg_info.cseq->cseq <= dlg->remote.cseq &&
+ rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD &&
+ rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD)
+ {
+ /* Invalid CSeq.
+ * Respond statelessly with 500 (Internal Server Error)
+ */
+ pj_str_t warn_text;
+
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ pj_assert(pjsip_rdata_get_tsx(rdata) == NULL);
+ warn_text = pj_str("Invalid CSeq");
+ pjsip_endpt_respond_stateless(dlg->endpt,
+ rdata, 500, &warn_text, NULL, NULL);
+ pj_log_pop_indent();
+ return;
+ }
+
+ /* Update CSeq. */
+ dlg->remote.cseq = rdata->msg_info.cseq->cseq;
+
+ /* Update To tag if necessary.
+ * This only happens if UAS sends a new request before answering
+ * our request (e.g. UAS sends NOTIFY before answering our
+ * SUBSCRIBE request).
+ */
+ if (dlg->remote.info->tag.slen == 0) {
+ pj_strdup(dlg->pool, &dlg->remote.info->tag,
+ &rdata->msg_info.from->tag);
+ }
+
+ /* Create UAS transaction for this request. */
+ if (pjsip_rdata_get_tsx(rdata) == NULL &&
+ rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD)
+ {
+ status = pjsip_tsx_create_uas(dlg->ua, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ /* Once case for this is when re-INVITE contains same
+ * Via branch value as previous INVITE (ticket #965).
+ */
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t reason;
+
+ reason = pj_strerror(status, errmsg, sizeof(errmsg));
+ pjsip_endpt_respond_stateless(dlg->endpt, rdata, 500, &reason,
+ NULL, NULL);
+ goto on_return;
+ }
+
+ /* Put this dialog in the transaction data. */
+ tsx->mod_data[dlg->ua->id] = dlg;
+
+ /* Add transaction count. */
+ ++dlg->tsx_count;
+ }
+
+ /* Update the target URI if this is a target refresh request.
+ * We have passed the basic checking for the request, I think we
+ * should update the target URI regardless of whether the request
+ * is accepted or not (e.g. when re-INVITE is answered with 488,
+ * we would still need to update the target URI, otherwise our
+ * target URI would be wrong, wouldn't it).
+ */
+ if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method)) {
+ pjsip_contact_hdr *contact;
+
+ contact = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+ NULL);
+ if (contact && contact->uri &&
+ (dlg->remote.contact==NULL ||
+ pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI,
+ dlg->remote.contact->uri,
+ contact->uri)))
+ {
+ dlg->remote.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, contact);
+ dlg->target = dlg->remote.contact->uri;
+ }
+ }
+
+ /* Report the request to dialog usages. */
+ for (i=0; i<dlg->usage_cnt; ++i) {
+
+ if (!dlg->usage[i]->on_rx_request)
+ continue;
+
+ processed = (*dlg->usage[i]->on_rx_request)(rdata);
+
+ if (processed)
+ break;
+ }
+
+ /* Feed the first request to the transaction. */
+ if (tsx)
+ pjsip_tsx_recv_msg(tsx, rdata);
+
+ /* If no dialog usages has claimed the processing of the transaction,
+ * and if transaction has not sent final response, respond with
+ * 500/Internal Server Error.
+ */
+ if (!processed && tsx && tsx->status_code < 200) {
+ pjsip_tx_data *tdata;
+ const pj_str_t reason = { "Unhandled by dialog usages", 26};
+
+ PJ_LOG(4,(tsx->obj_name, "%s was unhandled by "
+ "dialog usages, sending 500 response",
+ pjsip_rx_data_get_info(rdata)));
+
+ status = pjsip_dlg_create_response(dlg, rdata, 500, &reason, &tdata);
+ if (status == PJ_SUCCESS) {
+ status = pjsip_dlg_send_response(dlg, tsx, tdata);
+ }
+ }
+
+on_return:
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+}
+
+/* Update route-set from incoming message */
+static void dlg_update_routeset(pjsip_dialog *dlg, const pjsip_rx_data *rdata)
+{
+ const pjsip_hdr *hdr, *end_hdr;
+ pj_int32_t msg_cseq;
+ const pjsip_msg *msg;
+
+ msg = rdata->msg_info.msg;
+ msg_cseq = rdata->msg_info.cseq->cseq;
+
+ /* Ignore if route set has been frozen */
+ if (dlg->route_set_frozen)
+ return;
+
+ /* Only update route set if this message belongs to the same
+ * transaction as the initial transaction that establishes dialog.
+ */
+ if (dlg->role == PJSIP_ROLE_UAC) {
+
+ /* Ignore subsequent request from remote */
+ if (msg->type != PJSIP_RESPONSE_MSG)
+ return;
+
+ /* Ignore subsequent responses with higher CSeq than initial CSeq.
+ * Unfortunately this would be broken when the first request is
+ * challenged!
+ */
+ //if (msg_cseq != dlg->local.first_cseq)
+ // return;
+
+ } else {
+
+ /* For callee dialog, route set should have been set by initial
+ * request and it will have been rejected by dlg->route_set_frozen
+ * check above.
+ */
+ pj_assert(!"Should not happen");
+
+ }
+
+ /* Based on the checks above, we should only get response message here */
+ pj_assert(msg->type == PJSIP_RESPONSE_MSG);
+
+ /* Ignore if this is not 1xx or 2xx response */
+ if (msg->line.status.code >= 300)
+ return;
+
+ /* Reset route set */
+ pj_list_init(&dlg->route_set);
+
+ /* Update route set */
+ end_hdr = &msg->hdr;
+ for (hdr=msg->hdr.prev; hdr!=end_hdr; hdr=hdr->prev) {
+ if (hdr->type == PJSIP_H_RECORD_ROUTE) {
+ pjsip_route_hdr *r;
+ r = (pjsip_route_hdr*) pjsip_hdr_clone(dlg->pool, hdr);
+ pjsip_routing_hdr_set_route(r);
+ pj_list_push_back(&dlg->route_set, r);
+ }
+ }
+
+ PJ_LOG(5,(dlg->obj_name, "Route-set updated"));
+
+ /* Freeze the route set only when the route set comes in 2xx response.
+ * If it is in 1xx response, prepare to recompute the route set when
+ * the 2xx response comes in.
+ *
+ * There is a debate whether route set should be frozen when the dialog
+ * is established with reliable provisional response, but I think
+ * it is safer to not freeze the route set (thus recompute the route set
+ * upon receiving 2xx response). Also RFC 3261 says so in 13.2.2.4.
+ *
+ * The pjsip_method_creates_dialog() check protects from wrongly
+ * freezing the route set upon receiving 200/OK response for PRACK.
+ */
+ if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+ PJSIP_IS_STATUS_IN_CLASS(msg->line.status.code, 200))
+ {
+ dlg->route_set_frozen = PJ_TRUE;
+ PJ_LOG(5,(dlg->obj_name, "Route-set frozen"));
+ }
+}
+
+
+/* This function is called by user agent upon receiving incoming response
+ * message.
+ */
+void pjsip_dlg_on_rx_response( pjsip_dialog *dlg, pjsip_rx_data *rdata )
+{
+ unsigned i;
+ int res_code;
+
+ PJ_LOG(5,(dlg->obj_name, "Received %s",
+ pjsip_rx_data_get_info(rdata)));
+ pj_log_push_indent();
+
+ /* Lock the dialog and inc session. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Check that rdata already has dialog in mod_data. */
+ pj_assert(pjsip_rdata_get_dlg(rdata) == dlg);
+
+ /* Keep the response's status code */
+ res_code = rdata->msg_info.msg->line.status.code;
+
+ /* When we receive response that establishes dialog, update To tag,
+ * route set and dialog target.
+ *
+ * The second condition of the "if" is a workaround for forking.
+ * Originally, the dialog takes the first To tag seen and set it as
+ * the remote tag. If the tag in 2xx response is different than this
+ * tag, ACK will be sent with wrong To tag and incoming request with
+ * this tag will be rejected with 481.
+ *
+ * The workaround for this is to take the To tag received in the
+ * 2xx response and set it as remote tag.
+ *
+ * New update:
+ * We also need to update the dialog for 1xx responses, to handle the
+ * case when 100rel is used, otherwise PRACK will be sent to the
+ * wrong target.
+ */
+ if ((dlg->state == PJSIP_DIALOG_STATE_NULL &&
+ pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+ (res_code > 100 && res_code < 300) &&
+ rdata->msg_info.to->tag.slen)
+ ||
+ (dlg->role==PJSIP_ROLE_UAC &&
+ !dlg->uac_has_2xx &&
+ res_code > 100 &&
+ res_code/100 <= 2 &&
+ pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+ pj_strcmp(&dlg->remote.info->tag, &rdata->msg_info.to->tag)))
+ {
+ pjsip_contact_hdr *contact;
+
+ /* Update remote capability info, when To tags in the dialog remote
+ * info and the incoming response are different, e.g: first response
+ * with To-tag or forking, apply strict update.
+ */
+ pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg,
+ pj_strcmp(&dlg->remote.info->tag,
+ &rdata->msg_info.to->tag));
+
+ /* Update To tag. */
+ pj_strdup(dlg->pool, &dlg->remote.info->tag, &rdata->msg_info.to->tag);
+ /* No need to update remote's tag_hval since its never used. */
+
+ /* RFC 3271 Section 12.1.2:
+ * The route set MUST be set to the list of URIs in the Record-Route
+ * header field from the response, taken in reverse order and
+ * preserving all URI parameters. If no Record-Route header field
+ * is present in the response, the route set MUST be set to the
+ * empty set. This route set, even if empty, overrides any pre-existing
+ * route set for future requests in this dialog.
+ */
+ dlg_update_routeset(dlg, rdata);
+
+ /* The remote target MUST be set to the URI from the Contact header
+ * field of the response.
+ */
+ contact = (pjsip_contact_hdr*)
+ pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT,
+ NULL);
+ if (contact && contact->uri &&
+ (dlg->remote.contact==NULL ||
+ pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI,
+ dlg->remote.contact->uri,
+ contact->uri)))
+ {
+ dlg->remote.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, contact);
+ dlg->target = dlg->remote.contact->uri;
+ }
+
+ dlg->state = PJSIP_DIALOG_STATE_ESTABLISHED;
+
+ /* Prevent dialog from being updated just in case more 2xx
+ * gets through this dialog (it shouldn't happen).
+ */
+ if (dlg->role==PJSIP_ROLE_UAC && !dlg->uac_has_2xx &&
+ res_code/100==2)
+ {
+ dlg->uac_has_2xx = PJ_TRUE;
+ }
+ }
+
+ /* Update remote target (again) when receiving 2xx response messages
+ * that's defined as target refresh.
+ *
+ * Also upon receiving 2xx response, recheck again the route set.
+ * This is for compatibility with RFC 2543, as described in Section
+ * 13.2.2.4 of RFC 3261:
+
+ If the dialog identifier in the 2xx response matches the dialog
+ identifier of an existing dialog, the dialog MUST be transitioned to
+ the "confirmed" state, and the route set for the dialog MUST be
+ recomputed based on the 2xx response using the procedures of Section
+ 12.2.1.2.
+
+ Note that the only piece of state that is recomputed is the route
+ set. Other pieces of state such as the highest sequence numbers
+ (remote and local) sent within the dialog are not recomputed. The
+ route set only is recomputed for backwards compatibility. RFC
+ 2543 did not mandate mirroring of the Record-Route header field in
+ a 1xx, only 2xx.
+ */
+ if (pjsip_method_creates_dialog(&rdata->msg_info.cseq->method) &&
+ res_code/100 == 2)
+ {
+ pjsip_contact_hdr *contact;
+
+ contact = (pjsip_contact_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg,
+ PJSIP_H_CONTACT,
+ NULL);
+ if (contact && contact->uri &&
+ (dlg->remote.contact==NULL ||
+ pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI,
+ dlg->remote.contact->uri,
+ contact->uri)))
+ {
+ dlg->remote.contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(dlg->pool, contact);
+ dlg->target = dlg->remote.contact->uri;
+ }
+
+ dlg_update_routeset(dlg, rdata);
+
+ /* Update remote capability info after the first 2xx response
+ * (ticket #1539). Note that the remote capability retrieved here
+ * will be assumed to remain unchanged for the duration of the dialog.
+ */
+ if (dlg->role==PJSIP_ROLE_UAC && !dlg->uac_has_2xx) {
+ pjsip_dlg_update_remote_cap(dlg, rdata->msg_info.msg, PJ_FALSE);
+ dlg->uac_has_2xx = PJ_TRUE;
+ }
+ }
+
+ /* Pass to dialog usages. */
+ for (i=0; i<dlg->usage_cnt; ++i) {
+ pj_bool_t processed;
+
+ if (!dlg->usage[i]->on_rx_response)
+ continue;
+
+ processed = (*dlg->usage[i]->on_rx_response)(rdata);
+
+ if (processed)
+ break;
+ }
+
+ /* Handle the case of forked response, when the application creates
+ * the forked dialog but not the invite session. In this case, the
+ * forked 200/OK response will be unhandled, and we must send ACK
+ * here.
+ */
+ if (dlg->usage_cnt==0) {
+ pj_status_t status;
+
+ if (rdata->msg_info.cseq->method.id==PJSIP_INVITE_METHOD &&
+ rdata->msg_info.msg->line.status.code/100 == 2)
+ {
+ pjsip_tx_data *ack;
+
+ status = pjsip_dlg_create_request(dlg, &pjsip_ack_method,
+ rdata->msg_info.cseq->cseq,
+ &ack);
+ if (status == PJ_SUCCESS)
+ status = pjsip_dlg_send_request(dlg, ack, -1, NULL);
+ } else if (rdata->msg_info.msg->line.status.code==401 ||
+ rdata->msg_info.msg->line.status.code==407)
+ {
+ pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata);
+ pjsip_tx_data *tdata;
+
+ status = pjsip_auth_clt_reinit_req( &dlg->auth_sess,
+ rdata, tsx->last_tx,
+ &tdata);
+
+ if (status == PJ_SUCCESS) {
+ /* Re-send request. */
+ status = pjsip_dlg_send_request(dlg, tdata, -1, NULL);
+ }
+ }
+ }
+
+ /* Unhandled response does not necessarily mean error because
+ dialog usages may choose to process the transaction state instead.
+ if (i==dlg->usage_cnt) {
+ PJ_LOG(4,(dlg->obj_name, "%s was not claimed by any dialog usages",
+ pjsip_rx_data_get_info(rdata)));
+ }
+ */
+
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ pj_log_pop_indent();
+}
+
+/* This function is called by user agent upon receiving transaction
+ * state notification.
+ */
+void pjsip_dlg_on_tsx_state( pjsip_dialog *dlg,
+ pjsip_transaction *tsx,
+ pjsip_event *e )
+{
+ unsigned i;
+
+ PJ_LOG(5,(dlg->obj_name, "Transaction %s state changed to %s",
+ tsx->obj_name, pjsip_tsx_state_str(tsx->state)));
+ pj_log_push_indent();
+
+ /* Lock the dialog and increment session. */
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Pass to dialog usages. */
+ for (i=0; i<dlg->usage_cnt; ++i) {
+
+ if (!dlg->usage[i]->on_tsx_state)
+ continue;
+
+ (*dlg->usage[i]->on_tsx_state)(tsx, e);
+ }
+
+
+ /* It is possible that the transaction is terminated and this function
+ * is called while we're calling on_tsx_state(). So only decrement
+ * the tsx_count if we're still attached to the transaction.
+ */
+ if (tsx->state == PJSIP_TSX_STATE_TERMINATED &&
+ tsx->mod_data[dlg->ua->id] == dlg)
+ {
+ pj_assert(dlg->tsx_count>0);
+ --dlg->tsx_count;
+ tsx->mod_data[dlg->ua->id] = NULL;
+ }
+
+ /* Unlock dialog and dec session, may destroy dialog. */
+ pjsip_dlg_dec_lock(dlg);
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Check if the specified capability is supported by remote.
+ */
+PJ_DEF(pjsip_dialog_cap_status) pjsip_dlg_remote_has_cap(
+ pjsip_dialog *dlg,
+ int htype,
+ const pj_str_t *hname,
+ const pj_str_t *token)
+{
+ const pjsip_generic_array_hdr *hdr;
+ pjsip_dialog_cap_status cap_status = PJSIP_DIALOG_CAP_UNSUPPORTED;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(dlg && token, PJSIP_DIALOG_CAP_UNKNOWN);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ hdr = (const pjsip_generic_array_hdr*)
+ pjsip_dlg_get_remote_cap_hdr(dlg, htype, hname);
+ if (!hdr) {
+ cap_status = PJSIP_DIALOG_CAP_UNKNOWN;
+ } else {
+ for (i=0; i<hdr->count; ++i) {
+ if (!pj_stricmp(&hdr->values[i], token)) {
+ cap_status = PJSIP_DIALOG_CAP_SUPPORTED;
+ break;
+ }
+ }
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return cap_status;
+}
+
+
+/*
+ * Update remote capability of ACCEPT, ALLOW, and SUPPORTED from
+ * the received message.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_update_remote_cap(pjsip_dialog *dlg,
+ const pjsip_msg *msg,
+ pj_bool_t strict)
+{
+ pjsip_hdr_e htypes[] =
+ { PJSIP_H_ACCEPT, PJSIP_H_ALLOW, PJSIP_H_SUPPORTED };
+ unsigned i;
+
+ PJ_ASSERT_RETURN(dlg && msg, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Retrieve all specified capability header types */
+ for (i = 0; i < PJ_ARRAY_SIZE(htypes); ++i) {
+ const pjsip_generic_array_hdr *hdr;
+ pj_status_t status;
+
+ /* Find this capability type in the message */
+ hdr = (const pjsip_generic_array_hdr*)
+ pjsip_msg_find_hdr(msg, htypes[i], NULL);
+ if (!hdr) {
+ /* Not found.
+ * If strict update is specified, remote this capability type
+ * from the capability list.
+ */
+ if (strict)
+ pjsip_dlg_remove_remote_cap_hdr(dlg, htypes[i], NULL);
+ } else {
+ /* Found, a capability type may be specified in multiple headers,
+ * so combine all the capability tags/values into a temporary
+ * header.
+ */
+ pjsip_generic_array_hdr tmp_hdr;
+
+ /* Init temporary header */
+ pjsip_generic_array_hdr_init(dlg->pool, &tmp_hdr, NULL);
+ pj_memcpy(&tmp_hdr, hdr, sizeof(pjsip_hdr));
+
+ while (hdr) {
+ unsigned j;
+
+ /* Append the header content to temporary header */
+ for(j=0; j<hdr->count &&
+ tmp_hdr.count<PJSIP_GENERIC_ARRAY_MAX_COUNT; ++j)
+ {
+ tmp_hdr.values[tmp_hdr.count++] = hdr->values[j];
+ }
+
+ /* Get the next header for this capability */
+ hdr = (const pjsip_generic_array_hdr*)
+ pjsip_msg_find_hdr(msg, htypes[i], hdr->next);
+ }
+
+ /* Save this capability */
+ status = pjsip_dlg_set_remote_cap_hdr(dlg, &tmp_hdr);
+ if (status != PJ_SUCCESS) {
+ pjsip_dlg_dec_lock(dlg);
+ return status;
+ }
+ }
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the value of the specified capability header field of remote.
+ */
+PJ_DEF(const pjsip_hdr*) pjsip_dlg_get_remote_cap_hdr(pjsip_dialog *dlg,
+ int htype,
+ const pj_str_t *hname)
+{
+ pjsip_hdr *hdr;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(dlg, NULL);
+ PJ_ASSERT_RETURN((htype != PJSIP_H_OTHER) || (hname && hname->slen),
+ NULL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ hdr = dlg->rem_cap_hdr.next;
+ while (hdr != &dlg->rem_cap_hdr) {
+ if ((htype != PJSIP_H_OTHER && htype == hdr->type) ||
+ (htype == PJSIP_H_OTHER && pj_stricmp(&hdr->name, hname) == 0))
+ {
+ pjsip_dlg_dec_lock(dlg);
+ return hdr;
+ }
+ hdr = hdr->next;
+ }
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return NULL;
+}
+
+
+/*
+ * Set remote capability header from a SIP header containing array
+ * of capability tags/values.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_set_remote_cap_hdr(
+ pjsip_dialog *dlg,
+ const pjsip_generic_array_hdr *cap_hdr)
+{
+ pjsip_generic_array_hdr *hdr;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(dlg && cap_hdr, PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ /* Find the header. */
+ hdr = (pjsip_generic_array_hdr*)
+ pjsip_dlg_get_remote_cap_hdr(dlg, cap_hdr->type, &cap_hdr->name);
+
+ /* Quick compare if the capability is up to date */
+ if (hdr && hdr->count == cap_hdr->count) {
+ unsigned i;
+ pj_bool_t uptodate = PJ_TRUE;
+
+ for (i=0; i<hdr->count; ++i) {
+ if (pj_stricmp(&hdr->values[i], &cap_hdr->values[i]))
+ uptodate = PJ_FALSE;
+ }
+
+ /* Capability is up to date, just return PJ_SUCCESS */
+ if (uptodate) {
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Remove existing capability header if any */
+ if (hdr)
+ pj_list_erase(hdr);
+
+ /* Add the new capability header */
+ hdr = (pjsip_generic_array_hdr*) pjsip_hdr_clone(dlg->pool, cap_hdr);
+ hdr->type = cap_hdr->type;
+ pj_strdup(dlg->pool, &hdr->name, &cap_hdr->name);
+ pj_list_push_back(&dlg->rem_cap_hdr, hdr);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+/*
+ * Remove a remote capability header.
+ */
+PJ_DEF(pj_status_t) pjsip_dlg_remove_remote_cap_hdr(pjsip_dialog *dlg,
+ int htype,
+ const pj_str_t *hname)
+{
+ pjsip_generic_array_hdr *hdr;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(dlg, PJ_EINVAL);
+ PJ_ASSERT_RETURN((htype != PJSIP_H_OTHER) || (hname && hname->slen),
+ PJ_EINVAL);
+
+ pjsip_dlg_inc_lock(dlg);
+
+ hdr = (pjsip_generic_array_hdr*)
+ pjsip_dlg_get_remote_cap_hdr(dlg, htype, hname);
+ if (!hdr) {
+ pjsip_dlg_dec_lock(dlg);
+ return PJ_ENOTFOUND;
+ }
+
+ pj_list_erase(hdr);
+
+ pjsip_dlg_dec_lock(dlg);
+
+ return PJ_SUCCESS;
+}
diff --git a/pjsip/src/pjsip/sip_dialog_wrap.cpp b/pjsip/src/pjsip/sip_dialog_wrap.cpp
new file mode 100644
index 0000000..5bf3935
--- /dev/null
+++ b/pjsip/src/pjsip/sip_dialog_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_dialog_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_dialog.c"
diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c
new file mode 100644
index 0000000..2510d14
--- /dev/null
+++ b/pjsip/src/pjsip/sip_endpoint.c
@@ -0,0 +1,1245 @@
+/* $Id: sip_endpoint.c 4154 2012-06-05 10:41:17Z 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 <pjsip/sip_endpoint.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_private.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_resolve.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_errno.h>
+#include <pj/except.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/hash.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/lock.h>
+
+#define PJSIP_EX_NO_MEMORY pj_NO_MEMORY_EXCEPTION()
+#define THIS_FILE "sip_endpoint.c"
+
+#define MAX_METHODS 32
+
+
+/* List of SIP endpoint exit callback. */
+typedef struct exit_cb
+{
+ PJ_DECL_LIST_MEMBER (struct exit_cb);
+ pjsip_endpt_exit_callback func;
+} exit_cb;
+
+
+/**
+ * The SIP endpoint.
+ */
+struct pjsip_endpoint
+{
+ /** Pool to allocate memory for the endpoint. */
+ pj_pool_t *pool;
+
+ /** Mutex for the pool, hash table, and event list/queue. */
+ pj_mutex_t *mutex;
+
+ /** Pool factory. */
+ pj_pool_factory *pf;
+
+ /** Name. */
+ pj_str_t name;
+
+ /** Timer heap. */
+ pj_timer_heap_t *timer_heap;
+
+ /** Transport manager. */
+ pjsip_tpmgr *transport_mgr;
+
+ /** Ioqueue. */
+ pj_ioqueue_t *ioqueue;
+
+ /** Last ioqueue err */
+ pj_status_t ioq_last_err;
+
+ /** DNS Resolver. */
+ pjsip_resolver_t *resolver;
+
+ /** Modules lock. */
+ pj_rwmutex_t *mod_mutex;
+
+ /** Modules. */
+ pjsip_module *modules[PJSIP_MAX_MODULE];
+
+ /** Module list, sorted by priority. */
+ pjsip_module module_list;
+
+ /** Capability header list. */
+ pjsip_hdr cap_hdr;
+
+ /** Additional request headers. */
+ pjsip_hdr req_hdr;
+
+ /** List of exit callback. */
+ exit_cb exit_cb_list;
+};
+
+
+#if defined(PJSIP_SAFE_MODULE) && PJSIP_SAFE_MODULE!=0
+# define LOCK_MODULE_ACCESS(ept) pj_rwmutex_lock_read(ept->mod_mutex)
+# define UNLOCK_MODULE_ACCESS(ept) pj_rwmutex_unlock_read(ept->mod_mutex)
+#else
+# define LOCK_MODULE_ACCESS(endpt)
+# define UNLOCK_MODULE_ACCESS(endpt)
+#endif
+
+
+
+/*
+ * Prototypes.
+ */
+static void endpt_on_rx_msg( pjsip_endpoint*,
+ pj_status_t, pjsip_rx_data*);
+static pj_status_t endpt_on_tx_msg( pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata );
+static pj_status_t unload_module(pjsip_endpoint *endpt,
+ pjsip_module *mod);
+
+/* Defined in sip_parser.c */
+void init_sip_parser(void);
+void deinit_sip_parser(void);
+
+/* Defined in sip_tel_uri.c */
+pj_status_t pjsip_tel_uri_subsys_init(void);
+
+
+/*
+ * This is the global handler for memory allocation failure, for pools that
+ * are created by the endpoint (by default, all pools ARE allocated by
+ * endpoint). The error is handled by throwing exception, and hopefully,
+ * the exception will be handled by the application (or this library).
+ */
+static void pool_callback( pj_pool_t *pool, pj_size_t size )
+{
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(size);
+
+ PJ_THROW(PJSIP_EX_NO_MEMORY);
+}
+
+
+/* Compare module name, used for searching module based on name. */
+static int cmp_mod_name(void *name, const void *mod)
+{
+ return pj_stricmp((const pj_str_t*)name, &((pjsip_module*)mod)->name);
+}
+
+/*
+ * Register new module to the endpoint.
+ * The endpoint will then call the load and start function in the module to
+ * properly initialize the module, and assign a unique module ID for the
+ * module.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_register_module( pjsip_endpoint *endpt,
+ pjsip_module *mod )
+{
+ pj_status_t status = PJ_SUCCESS;
+ pjsip_module *m;
+ unsigned i;
+
+ pj_rwmutex_lock_write(endpt->mod_mutex);
+
+ /* Make sure that this module has not been registered. */
+ PJ_ASSERT_ON_FAIL( pj_list_find_node(&endpt->module_list, mod) == NULL,
+ {status = PJ_EEXISTS; goto on_return;});
+
+ /* Make sure that no module with the same name has been registered. */
+ PJ_ASSERT_ON_FAIL( pj_list_search(&endpt->module_list, &mod->name,
+ &cmp_mod_name)==NULL,
+ {status = PJ_EEXISTS; goto on_return; });
+
+ /* Find unused ID for this module. */
+ for (i=0; i<PJ_ARRAY_SIZE(endpt->modules); ++i) {
+ if (endpt->modules[i] == NULL)
+ break;
+ }
+ if (i == PJ_ARRAY_SIZE(endpt->modules)) {
+ pj_assert(!"Too many modules registered!");
+ status = PJ_ETOOMANY;
+ goto on_return;
+ }
+
+ /* Assign the ID. */
+ mod->id = i;
+
+ /* Try to load the module. */
+ if (mod->load) {
+ status = (*mod->load)(endpt);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ }
+
+ /* Try to start the module. */
+ if (mod->start) {
+ status = (*mod->start)();
+ if (status != PJ_SUCCESS)
+ goto on_return;
+ }
+
+ /* Save the module. */
+ endpt->modules[i] = mod;
+
+ /* Put in the module list, sorted by priority. */
+ m = endpt->module_list.next;
+ while (m != &endpt->module_list) {
+ if (m->priority > mod->priority)
+ break;
+ m = m->next;
+ }
+ pj_list_insert_before(m, mod);
+
+ /* Done. */
+
+ PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" registered",
+ (int)mod->name.slen, mod->name.ptr));
+
+on_return:
+ pj_rwmutex_unlock_write(endpt->mod_mutex);
+ return status;
+}
+
+/*
+ * Unregister a module from the endpoint.
+ * The endpoint will then call the stop and unload function in the module to
+ * properly shutdown the module.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_unregister_module( pjsip_endpoint *endpt,
+ pjsip_module *mod )
+{
+ pj_status_t status;
+
+ pj_rwmutex_lock_write(endpt->mod_mutex);
+
+ /* Make sure the module exists in the list. */
+ PJ_ASSERT_ON_FAIL( pj_list_find_node(&endpt->module_list, mod) == mod,
+ {status = PJ_ENOTFOUND;goto on_return;} );
+
+ /* Make sure the module exists in the array. */
+ PJ_ASSERT_ON_FAIL( mod->id>=0 &&
+ mod->id<(int)PJ_ARRAY_SIZE(endpt->modules) &&
+ endpt->modules[mod->id] == mod,
+ {status = PJ_ENOTFOUND; goto on_return;});
+
+ /* Try to stop the module. */
+ if (mod->stop) {
+ status = (*mod->stop)();
+ if (status != PJ_SUCCESS) goto on_return;
+ }
+
+ /* Unload module */
+ status = unload_module(endpt, mod);
+
+on_return:
+ pj_rwmutex_unlock_write(endpt->mod_mutex);
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(3,(THIS_FILE, "Module \"%.*s\" can not be unregistered: %s",
+ (int)mod->name.slen, mod->name.ptr, errmsg));
+ }
+
+ return status;
+}
+
+static pj_status_t unload_module(pjsip_endpoint *endpt,
+ pjsip_module *mod)
+{
+ pj_status_t status;
+
+ /* Try to unload the module. */
+ if (mod->unload) {
+ status = (*mod->unload)();
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Module MUST NOT set module ID to -1. */
+ pj_assert(mod->id >= 0);
+
+ /* Remove module from array. */
+ endpt->modules[mod->id] = NULL;
+
+ /* Remove module from list. */
+ pj_list_erase(mod);
+
+ /* Set module Id to -1. */
+ mod->id = -1;
+
+ /* Done. */
+ status = PJ_SUCCESS;
+
+ PJ_LOG(4,(THIS_FILE, "Module \"%.*s\" unregistered",
+ (int)mod->name.slen, mod->name.ptr));
+
+ return status;
+}
+
+
+/*
+ * Get the value of the specified capability header field.
+ */
+PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_capability( pjsip_endpoint *endpt,
+ int htype,
+ const pj_str_t *hname)
+{
+ pjsip_hdr *hdr = endpt->cap_hdr.next;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt != NULL, NULL);
+ PJ_ASSERT_RETURN(htype != PJSIP_H_OTHER || hname, NULL);
+
+ if (htype != PJSIP_H_OTHER) {
+ while (hdr != &endpt->cap_hdr) {
+ if (hdr->type == htype)
+ return hdr;
+ hdr = hdr->next;
+ }
+ }
+ return NULL;
+}
+
+
+/*
+ * Check if the specified capability is supported.
+ */
+PJ_DEF(pj_bool_t) pjsip_endpt_has_capability( pjsip_endpoint *endpt,
+ int htype,
+ const pj_str_t *hname,
+ const pj_str_t *token)
+{
+ const pjsip_generic_array_hdr *hdr;
+ unsigned i;
+
+ hdr = (const pjsip_generic_array_hdr*)
+ pjsip_endpt_get_capability(endpt, htype, hname);
+ if (!hdr)
+ return PJ_FALSE;
+
+ PJ_ASSERT_RETURN(token != NULL, PJ_FALSE);
+
+ for (i=0; i<hdr->count; ++i) {
+ if (!pj_stricmp(&hdr->values[i], token))
+ return PJ_TRUE;
+ }
+
+ return PJ_FALSE;
+}
+
+/*
+ * Add or register new capabilities as indicated by the tags to the
+ * appropriate header fields in the endpoint.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_add_capability( pjsip_endpoint *endpt,
+ pjsip_module *mod,
+ int htype,
+ const pj_str_t *hname,
+ unsigned count,
+ const pj_str_t tags[])
+{
+ pjsip_generic_array_hdr *hdr;
+ unsigned i;
+
+ PJ_UNUSED_ARG(mod);
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt!=NULL && count>0 && tags, PJ_EINVAL);
+ PJ_ASSERT_RETURN(htype==PJSIP_H_ACCEPT ||
+ htype==PJSIP_H_ALLOW ||
+ htype==PJSIP_H_SUPPORTED,
+ PJ_EINVAL);
+
+ /* Find the header. */
+ hdr = (pjsip_generic_array_hdr*) pjsip_endpt_get_capability(endpt,
+ htype, hname);
+
+ /* Create the header when it's not present */
+ if (hdr == NULL) {
+ switch (htype) {
+ case PJSIP_H_ACCEPT:
+ hdr = pjsip_accept_hdr_create(endpt->pool);
+ break;
+ case PJSIP_H_ALLOW:
+ hdr = pjsip_allow_hdr_create(endpt->pool);
+ break;
+ case PJSIP_H_SUPPORTED:
+ hdr = pjsip_supported_hdr_create(endpt->pool);
+ break;
+ default:
+ return PJ_EINVAL;
+ }
+
+ if (hdr) {
+ pj_list_push_back(&endpt->cap_hdr, hdr);
+ }
+ }
+
+ /* Add the tags to the header. */
+ for (i=0; i<count; ++i) {
+ pj_strdup(endpt->pool, &hdr->values[hdr->count], &tags[i]);
+ ++hdr->count;
+ }
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get additional headers to be put in outgoing request message.
+ */
+PJ_DEF(const pjsip_hdr*) pjsip_endpt_get_request_headers(pjsip_endpoint *endpt)
+{
+ return &endpt->req_hdr;
+}
+
+
+/*
+ * Initialize endpoint.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create(pj_pool_factory *pf,
+ const char *name,
+ pjsip_endpoint **p_endpt)
+{
+ pj_status_t status;
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pjsip_max_fwd_hdr *mf_hdr;
+ pj_lock_t *lock = NULL;
+
+
+ status = pj_register_strerror(PJSIP_ERRNO_START, PJ_ERRNO_SPACE_SIZE,
+ &pjsip_strerror);
+ pj_assert(status == PJ_SUCCESS);
+
+ PJ_LOG(5, (THIS_FILE, "Creating endpoint instance..."));
+
+ *p_endpt = NULL;
+
+ /* Create pool */
+ pool = pj_pool_create(pf, "pept%p",
+ PJSIP_POOL_LEN_ENDPT, PJSIP_POOL_INC_ENDPT,
+ &pool_callback);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Create endpoint. */
+ endpt = PJ_POOL_ZALLOC_T(pool, pjsip_endpoint);
+ endpt->pool = pool;
+ endpt->pf = pf;
+
+ /* Init modules list. */
+ pj_list_init(&endpt->module_list);
+
+ /* Initialize exit callback list. */
+ pj_list_init(&endpt->exit_cb_list);
+
+ /* Create R/W mutex for module manipulation. */
+ status = pj_rwmutex_create(endpt->pool, "ept%p", &endpt->mod_mutex);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init parser. */
+ init_sip_parser();
+
+ /* Init tel: uri */
+ pjsip_tel_uri_subsys_init();
+
+ /* Get name. */
+ if (name != NULL) {
+ pj_str_t temp;
+ pj_strdup_with_null(endpt->pool, &endpt->name, pj_cstr(&temp, name));
+ } else {
+ pj_strdup_with_null(endpt->pool, &endpt->name, pj_gethostname());
+ }
+
+ /* Create mutex for the events, etc. */
+ status = pj_mutex_create_recursive( endpt->pool, "ept%p", &endpt->mutex );
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Create timer heap to manage all timers within this endpoint. */
+ status = pj_timer_heap_create( endpt->pool, PJSIP_MAX_TIMER_COUNT,
+ &endpt->timer_heap);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Set recursive lock for the timer heap. */
+ status = pj_lock_create_recursive_mutex( endpt->pool, "edpt%p", &lock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+ pj_timer_heap_set_lock(endpt->timer_heap, lock, PJ_TRUE);
+
+ /* Set maximum timed out entries to process in a single poll. */
+ pj_timer_heap_set_max_timed_out_per_poll(endpt->timer_heap,
+ PJSIP_MAX_TIMED_OUT_ENTRIES);
+
+ /* Create ioqueue. */
+ status = pj_ioqueue_create( endpt->pool, PJSIP_MAX_TRANSPORTS, &endpt->ioqueue);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Create transport manager. */
+ status = pjsip_tpmgr_create( endpt->pool, endpt,
+ &endpt_on_rx_msg,
+ &endpt_on_tx_msg,
+ &endpt->transport_mgr);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Create asynchronous DNS resolver. */
+ status = pjsip_resolver_create(endpt->pool, &endpt->resolver);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(4, (THIS_FILE, "Error creating resolver instance"));
+ goto on_error;
+ }
+
+ /* Initialize request headers. */
+ pj_list_init(&endpt->req_hdr);
+
+ /* Add "Max-Forwards" for request header. */
+ mf_hdr = pjsip_max_fwd_hdr_create(endpt->pool,
+ PJSIP_MAX_FORWARDS_VALUE);
+ pj_list_insert_before( &endpt->req_hdr, mf_hdr);
+
+ /* Initialize capability header list. */
+ pj_list_init(&endpt->cap_hdr);
+
+
+ /* Done. */
+ *p_endpt = endpt;
+ return status;
+
+on_error:
+ if (endpt->transport_mgr) {
+ pjsip_tpmgr_destroy(endpt->transport_mgr);
+ endpt->transport_mgr = NULL;
+ }
+ if (endpt->ioqueue) {
+ pj_ioqueue_destroy(endpt->ioqueue);
+ endpt->ioqueue = NULL;
+ }
+ if (endpt->timer_heap) {
+ pj_timer_heap_destroy(endpt->timer_heap);
+ endpt->timer_heap = NULL;
+ }
+ if (endpt->mutex) {
+ pj_mutex_destroy(endpt->mutex);
+ endpt->mutex = NULL;
+ }
+ if (endpt->mod_mutex) {
+ pj_rwmutex_destroy(endpt->mod_mutex);
+ endpt->mod_mutex = NULL;
+ }
+ pj_pool_release( endpt->pool );
+
+ PJ_LOG(4, (THIS_FILE, "Error creating endpoint"));
+ return status;
+}
+
+/*
+ * Destroy endpoint.
+ */
+PJ_DEF(void) pjsip_endpt_destroy(pjsip_endpoint *endpt)
+{
+ pjsip_module *mod;
+ exit_cb *ecb;
+
+ PJ_LOG(5, (THIS_FILE, "Destroying endpoing instance.."));
+
+ /* Phase 1: stop all modules */
+ mod = endpt->module_list.prev;
+ while (mod != &endpt->module_list) {
+ pjsip_module *prev = mod->prev;
+ if (mod->stop) {
+ (*mod->stop)();
+ }
+ mod = prev;
+ }
+
+ /* Phase 2: unload modules. */
+ mod = endpt->module_list.prev;
+ while (mod != &endpt->module_list) {
+ pjsip_module *prev = mod->prev;
+ unload_module(endpt, mod);
+ mod = prev;
+ }
+
+ /* Destroy resolver */
+ pjsip_resolver_destroy(endpt->resolver);
+
+ /* Shutdown and destroy all transports. */
+ pjsip_tpmgr_destroy(endpt->transport_mgr);
+
+ /* Destroy ioqueue */
+ pj_ioqueue_destroy(endpt->ioqueue);
+
+ /* Destroy timer heap */
+#if PJ_TIMER_DEBUG
+ pj_timer_heap_dump(endpt->timer_heap);
+#endif
+ pj_timer_heap_destroy(endpt->timer_heap);
+
+ /* Call all registered exit callbacks */
+ ecb = endpt->exit_cb_list.next;
+ while (ecb != &endpt->exit_cb_list) {
+ (*ecb->func)(endpt);
+ ecb = ecb->next;
+ }
+
+ /* Delete endpoint mutex. */
+ pj_mutex_destroy(endpt->mutex);
+
+ /* Deinit parser */
+ deinit_sip_parser();
+
+ /* Delete module's mutex */
+ pj_rwmutex_destroy(endpt->mod_mutex);
+
+ /* Finally destroy pool. */
+ pj_pool_release(endpt->pool);
+
+ PJ_LOG(4, (THIS_FILE, "Endpoint %p destroyed", endpt));
+}
+
+/*
+ * Get endpoint name.
+ */
+PJ_DEF(const pj_str_t*) pjsip_endpt_name(const pjsip_endpoint *endpt)
+{
+ return &endpt->name;
+}
+
+
+/*
+ * Create new pool.
+ */
+PJ_DEF(pj_pool_t*) pjsip_endpt_create_pool( pjsip_endpoint *endpt,
+ const char *pool_name,
+ pj_size_t initial,
+ pj_size_t increment )
+{
+ pj_pool_t *pool;
+
+ /* Lock endpoint mutex. */
+ /* No need to lock mutex. Factory is thread safe.
+ pj_mutex_lock(endpt->mutex);
+ */
+
+ /* Create pool */
+ pool = pj_pool_create( endpt->pf, pool_name,
+ initial, increment, &pool_callback);
+
+ /* Unlock mutex. */
+ /* No need to lock mutex. Factory is thread safe.
+ pj_mutex_unlock(endpt->mutex);
+ */
+
+ if (!pool) {
+ PJ_LOG(4, (THIS_FILE, "Unable to create pool %s!", pool_name));
+ }
+
+ return pool;
+}
+
+/*
+ * Return back pool to endpoint's pool manager to be either destroyed or
+ * recycled.
+ */
+PJ_DEF(void) pjsip_endpt_release_pool( pjsip_endpoint *endpt, pj_pool_t *pool )
+{
+ PJ_LOG(6, (THIS_FILE, "Releasing pool %s", pj_pool_getobjname(pool)));
+
+ /* Don't need to acquire mutex since pool factory is thread safe
+ pj_mutex_lock(endpt->mutex);
+ */
+ pj_pool_release( pool );
+
+ PJ_UNUSED_ARG(endpt);
+ /*
+ pj_mutex_unlock(endpt->mutex);
+ */
+}
+
+
+PJ_DEF(pj_status_t) pjsip_endpt_handle_events2(pjsip_endpoint *endpt,
+ const pj_time_val *max_timeout,
+ unsigned *p_count)
+{
+ /* timeout is 'out' var. This just to make compiler happy. */
+ pj_time_val timeout = { 0, 0};
+ unsigned count = 0, net_event_count = 0;
+ int c;
+
+ PJ_LOG(6, (THIS_FILE, "pjsip_endpt_handle_events()"));
+
+ /* Poll the timer. The timer heap has its own mutex for better
+ * granularity, so we don't need to lock end endpoint.
+ */
+ timeout.sec = timeout.msec = 0;
+ c = pj_timer_heap_poll( endpt->timer_heap, &timeout );
+ if (c > 0)
+ count += c;
+
+ /* timer_heap_poll should never ever returns negative value, or otherwise
+ * ioqueue_poll() will block forever!
+ */
+ pj_assert(timeout.sec >= 0 && timeout.msec >= 0);
+ if (timeout.msec >= 1000) timeout.msec = 999;
+
+ /* If caller specifies maximum time to wait, then compare the value with
+ * the timeout to wait from timer, and use the minimum value.
+ */
+ if (max_timeout && PJ_TIME_VAL_GT(timeout, *max_timeout)) {
+ timeout = *max_timeout;
+ }
+
+ /* Poll ioqueue.
+ * Repeat polling the ioqueue while we have immediate events, because
+ * timer heap may process more than one events, so if we only process
+ * one network events at a time (such as when IOCP backend is used),
+ * the ioqueue may have trouble keeping up with the request rate.
+ *
+ * For example, for each send() request, one network event will be
+ * reported by ioqueue for the send() completion. If we don't poll
+ * the ioqueue often enough, the send() completion will not be
+ * reported in timely manner.
+ */
+ do {
+ c = pj_ioqueue_poll( endpt->ioqueue, &timeout);
+ if (c < 0) {
+ pj_status_t err = pj_get_netos_error();
+ pj_thread_sleep(PJ_TIME_VAL_MSEC(timeout));
+ if (p_count)
+ *p_count = count;
+ return err;
+ } else if (c == 0) {
+ break;
+ } else {
+ net_event_count += c;
+ timeout.sec = timeout.msec = 0;
+ }
+ } while (c > 0 && net_event_count < PJSIP_MAX_NET_EVENTS);
+
+ count += net_event_count;
+ if (p_count)
+ *p_count = count;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Handle events.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_handle_events(pjsip_endpoint *endpt,
+ const pj_time_val *max_timeout)
+{
+ return pjsip_endpt_handle_events2(endpt, max_timeout, NULL);
+}
+
+/*
+ * Schedule timer.
+ */
+#if PJ_TIMER_DEBUG
+PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer_dbg(pjsip_endpoint *endpt,
+ pj_timer_entry *entry,
+ const pj_time_val *delay,
+ const char *src_file,
+ int src_line)
+{
+ PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer(entry=%p, delay=%u.%u)",
+ entry, delay->sec, delay->msec));
+ return pj_timer_heap_schedule_dbg(endpt->timer_heap, entry, delay,
+ src_file, src_line);
+}
+#else
+PJ_DEF(pj_status_t) pjsip_endpt_schedule_timer( pjsip_endpoint *endpt,
+ pj_timer_entry *entry,
+ const pj_time_val *delay )
+{
+ PJ_LOG(6, (THIS_FILE, "pjsip_endpt_schedule_timer(entry=%p, delay=%u.%u)",
+ entry, delay->sec, delay->msec));
+ return pj_timer_heap_schedule( endpt->timer_heap, entry, delay );
+}
+#endif
+
+/*
+ * Cancel the previously registered timer.
+ */
+PJ_DEF(void) pjsip_endpt_cancel_timer( pjsip_endpoint *endpt,
+ pj_timer_entry *entry )
+{
+ PJ_LOG(6, (THIS_FILE, "pjsip_endpt_cancel_timer(entry=%p)", entry));
+ pj_timer_heap_cancel( endpt->timer_heap, entry );
+}
+
+/*
+ * Get the timer heap instance of the SIP endpoint.
+ */
+PJ_DEF(pj_timer_heap_t*) pjsip_endpt_get_timer_heap(pjsip_endpoint *endpt)
+{
+ return endpt->timer_heap;
+}
+
+/*
+ * This is the callback that is called by the transport manager when it
+ * receives a message from the network.
+ */
+static void endpt_on_rx_msg( pjsip_endpoint *endpt,
+ pj_status_t status,
+ pjsip_rx_data *rdata )
+{
+ pjsip_msg *msg = rdata->msg_info.msg;
+
+ if (status != PJ_SUCCESS) {
+ char info[30];
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ info[0] = '\0';
+
+ if (status == PJSIP_EMISSINGHDR) {
+ pj_str_t p;
+
+ p.ptr = info; p.slen = 0;
+
+ if (rdata->msg_info.cid == NULL || rdata->msg_info.cid->id.slen)
+ pj_strcpy2(&p, "Call-ID");
+ if (rdata->msg_info.from == NULL)
+ pj_strcpy2(&p, " From");
+ if (rdata->msg_info.to == NULL)
+ pj_strcpy2(&p, " To");
+ if (rdata->msg_info.via == NULL)
+ pj_strcpy2(&p, " Via");
+ if (rdata->msg_info.cseq == NULL)
+ pj_strcpy2(&p, " CSeq");
+
+ p.ptr[p.slen] = '\0';
+ }
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(1, (THIS_FILE,
+ "Error processing packet from %s:%d: %s %s [code %d]:\n"
+ "%.*s\n"
+ "-- end of packet.",
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ errmsg,
+ info,
+ status,
+ (int)rdata->msg_info.len,
+ rdata->msg_info.msg_buf));
+ return;
+ }
+
+ PJ_LOG(5, (THIS_FILE, "Processing incoming message: %s",
+ pjsip_rx_data_get_info(rdata)));
+ pj_log_push_indent();
+
+#if defined(PJSIP_CHECK_VIA_SENT_BY) && PJSIP_CHECK_VIA_SENT_BY != 0
+ /* For response, check that the value in Via sent-by match the transport.
+ * If not matched, silently drop the response.
+ * Ref: RFC3261 Section 18.1.2 Receiving Response
+ */
+ if (msg->type == PJSIP_RESPONSE_MSG) {
+ const pj_str_t *local_addr;
+ int port = rdata->msg_info.via->sent_by.port;
+ pj_bool_t mismatch = PJ_FALSE;
+ if (port == 0) {
+ pjsip_transport_type_e type;
+ type = (pjsip_transport_type_e)rdata->tp_info.transport->key.type;
+ port = pjsip_transport_get_default_port_for_type(type);
+ }
+ local_addr = &rdata->tp_info.transport->local_name.host;
+
+ if (pj_strcmp(&rdata->msg_info.via->sent_by.host, local_addr) != 0) {
+
+ /* The RFC says that we should drop response when sent-by
+ * address mismatch. But it could happen (e.g. with SER) when
+ * endpoint with private IP is sending request to public
+ * server.
+
+ mismatch = PJ_TRUE;
+
+ */
+
+ } else if (port != rdata->tp_info.transport->local_name.port) {
+ /* Port or address mismatch, we should discard response */
+ /* But we saw one implementation (we don't want to name it to
+ * protect the innocence) which put wrong sent-by port although
+ * the "rport" parameter is correct.
+ * So we discard the response only if the port doesn't match
+ * both the port in sent-by and rport. We try to be lenient here!
+ */
+ if (rdata->msg_info.via->rport_param !=
+ rdata->tp_info.transport->local_name.port)
+ mismatch = PJ_TRUE;
+ else {
+ PJ_LOG(4,(THIS_FILE, "Message %s from %s has mismatch port in "
+ "sent-by but the rport parameter is "
+ "correct",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name));
+ }
+ }
+
+ if (mismatch) {
+ PJ_TODO(ENDPT_REPORT_WHEN_DROPPING_MESSAGE);
+ PJ_LOG(4,(THIS_FILE, "Dropping response %s from %s:%d because "
+ "sent-by is mismatch",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port));
+ pj_log_pop_indent();
+ return;
+ }
+ }
+#endif
+
+
+ /* Distribute to modules, starting from modules with highest priority */
+ LOCK_MODULE_ACCESS(endpt);
+
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ pjsip_module *mod;
+ pj_bool_t handled = PJ_FALSE;
+
+ mod = endpt->module_list.next;
+ while (mod != &endpt->module_list) {
+ if (mod->on_rx_request)
+ handled = (*mod->on_rx_request)(rdata);
+ if (handled)
+ break;
+ mod = mod->next;
+ }
+
+ /* No module is able to handle the request. */
+ if (!handled) {
+ PJ_TODO(ENDPT_RESPOND_UNHANDLED_REQUEST);
+ PJ_LOG(4,(THIS_FILE, "Message %s from %s:%d was dropped/unhandled by"
+ " any modules",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port));
+ }
+
+ } else {
+ pjsip_module *mod;
+ pj_bool_t handled = PJ_FALSE;
+
+ mod = endpt->module_list.next;
+ while (mod != &endpt->module_list) {
+ if (mod->on_rx_response)
+ handled = (*mod->on_rx_response)(rdata);
+ if (handled)
+ break;
+ mod = mod->next;
+ }
+
+ if (!handled) {
+ PJ_LOG(4,(THIS_FILE, "Message %s from %s:%d was dropped/unhandled"
+ " by any modules",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port));
+ }
+ }
+
+ UNLOCK_MODULE_ACCESS(endpt);
+
+ /* Must clear mod_data before returning rdata to transport, since
+ * rdata may be reused.
+ */
+ pj_bzero(&rdata->endpt_info, sizeof(rdata->endpt_info));
+
+ pj_log_pop_indent();
+}
+
+/*
+ * This callback is called by transport manager before message is sent.
+ * Modules may inspect the message before it's actually sent.
+ */
+static pj_status_t endpt_on_tx_msg( pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata )
+{
+ pj_status_t status = PJ_SUCCESS;
+ pjsip_module *mod;
+
+ /* Distribute to modules, starting from modules with LOWEST priority */
+ LOCK_MODULE_ACCESS(endpt);
+
+ mod = endpt->module_list.prev;
+ if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+ while (mod != &endpt->module_list) {
+ if (mod->on_tx_request)
+ status = (*mod->on_tx_request)(tdata);
+ if (status != PJ_SUCCESS)
+ break;
+ mod = mod->prev;
+ }
+
+ } else {
+ while (mod != &endpt->module_list) {
+ if (mod->on_tx_response)
+ status = (*mod->on_tx_response)(tdata);
+ if (status != PJ_SUCCESS)
+ break;
+ mod = mod->prev;
+ }
+ }
+
+ UNLOCK_MODULE_ACCESS(endpt);
+
+ return status;
+}
+
+
+/*
+ * Create transmit data buffer.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_tdata( pjsip_endpoint *endpt,
+ pjsip_tx_data **p_tdata)
+{
+ return pjsip_tx_data_create(endpt->transport_mgr, p_tdata);
+}
+
+/*
+ * Create the DNS resolver instance.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_resolver(pjsip_endpoint *endpt,
+ pj_dns_resolver **p_resv)
+{
+#if PJSIP_HAS_RESOLVER
+ PJ_ASSERT_RETURN(endpt && p_resv, PJ_EINVAL);
+ return pj_dns_resolver_create( endpt->pf, NULL, 0, endpt->timer_heap,
+ endpt->ioqueue, p_resv);
+#else
+ PJ_UNUSED_ARG(endpt);
+ PJ_UNUSED_ARG(p_resv);
+ pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)");
+ return PJ_EINVALIDOP;
+#endif
+}
+
+/*
+ * Set DNS resolver to be used by the SIP resolver.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_set_resolver( pjsip_endpoint *endpt,
+ pj_dns_resolver *resv)
+{
+ return pjsip_resolver_set_resolver(endpt->resolver, resv);
+}
+
+/*
+ * Get the DNS resolver being used by the SIP resolver.
+ */
+PJ_DEF(pj_dns_resolver*) pjsip_endpt_get_resolver(pjsip_endpoint *endpt)
+{
+ PJ_ASSERT_RETURN(endpt, NULL);
+ return pjsip_resolver_get_resolver(endpt->resolver);
+}
+
+/*
+ * Resolve
+ */
+PJ_DEF(void) pjsip_endpt_resolve( pjsip_endpoint *endpt,
+ pj_pool_t *pool,
+ pjsip_host_info *target,
+ void *token,
+ pjsip_resolver_callback *cb)
+{
+ pjsip_resolve( endpt->resolver, pool, target, token, cb);
+}
+
+/*
+ * Get transport manager.
+ */
+PJ_DEF(pjsip_tpmgr*) pjsip_endpt_get_tpmgr(pjsip_endpoint *endpt)
+{
+ return endpt->transport_mgr;
+}
+
+/*
+ * Get ioqueue instance.
+ */
+PJ_DEF(pj_ioqueue_t*) pjsip_endpt_get_ioqueue(pjsip_endpoint *endpt)
+{
+ return endpt->ioqueue;
+}
+
+/*
+ * Find/create transport.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport(pjsip_endpoint *endpt,
+ pjsip_transport_type_e type,
+ const pj_sockaddr_t *remote,
+ int addr_len,
+ const pjsip_tpselector *sel,
+ pjsip_transport **transport)
+{
+ return pjsip_tpmgr_acquire_transport(endpt->transport_mgr, type,
+ remote, addr_len, sel, transport);
+}
+
+
+/*
+ * Find/create transport.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_acquire_transport2(pjsip_endpoint *endpt,
+ pjsip_transport_type_e type,
+ const pj_sockaddr_t *remote,
+ int addr_len,
+ const pjsip_tpselector *sel,
+ pjsip_tx_data *tdata,
+ pjsip_transport **transport)
+{
+ return pjsip_tpmgr_acquire_transport2(endpt->transport_mgr, type, remote,
+ addr_len, sel, tdata, transport);
+}
+
+
+/*
+ * Report error.
+ */
+PJ_DEF(void) pjsip_endpt_log_error( pjsip_endpoint *endpt,
+ const char *sender,
+ pj_status_t error_code,
+ const char *format,
+ ... )
+{
+#if PJ_LOG_MAX_LEVEL > 0
+ char newformat[256];
+ int len;
+ va_list marker;
+
+ va_start(marker, format);
+
+ PJ_UNUSED_ARG(endpt);
+
+ len = pj_ansi_strlen(format);
+ if (len < (int)sizeof(newformat)-30) {
+ pj_str_t errstr;
+
+ pj_ansi_strcpy(newformat, format);
+ pj_ansi_snprintf(newformat+len, sizeof(newformat)-len-1,
+ ": [err %d] ", error_code);
+ len += pj_ansi_strlen(newformat+len);
+
+ errstr = pj_strerror( error_code, newformat+len,
+ sizeof(newformat)-len-1);
+
+ len += errstr.slen;
+ newformat[len] = '\0';
+
+ pj_log(sender, 1, newformat, marker);
+ } else {
+ pj_log(sender, 1, format, marker);
+ }
+
+ va_end(marker);
+#else
+ PJ_UNUSED_ARG(format);
+ PJ_UNUSED_ARG(error_code);
+ PJ_UNUSED_ARG(sender);
+ PJ_UNUSED_ARG(endpt);
+#endif
+}
+
+
+/*
+ * Dump endpoint.
+ */
+PJ_DEF(void) pjsip_endpt_dump( pjsip_endpoint *endpt, pj_bool_t detail )
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ PJ_LOG(5, (THIS_FILE, "pjsip_endpt_dump()"));
+
+ /* Lock mutex. */
+ pj_mutex_lock(endpt->mutex);
+
+ PJ_LOG(3, (THIS_FILE, "Dumping endpoint %p:", endpt));
+
+ /* Dumping pool factory. */
+ pj_pool_factory_dump(endpt->pf, detail);
+
+ /* Pool health. */
+ PJ_LOG(3, (THIS_FILE," Endpoint pool capacity=%u, used_size=%u",
+ pj_pool_get_capacity(endpt->pool),
+ pj_pool_get_used_size(endpt->pool)));
+
+ /* Resolver */
+#if PJSIP_HAS_RESOLVER
+ if (pjsip_endpt_get_resolver(endpt)) {
+ pj_dns_resolver_dump(pjsip_endpt_get_resolver(endpt), detail);
+ }
+#endif
+
+ /* Transports.
+ */
+ pjsip_tpmgr_dump_transports( endpt->transport_mgr );
+
+ /* Timer. */
+#if PJ_TIMER_DEBUG
+ pj_timer_heap_dump(endpt->timer_heap);
+#else
+ PJ_LOG(3,(THIS_FILE, " Timer heap has %u entries",
+ pj_timer_heap_count(endpt->timer_heap)));
+#endif
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(endpt->mutex);
+#else
+ PJ_UNUSED_ARG(endpt);
+ PJ_UNUSED_ARG(detail);
+ PJ_LOG(3,(THIS_FILE, "pjsip_end_dump: can't dump because it's disabled."));
+#endif
+}
+
+
+PJ_DEF(pj_status_t) pjsip_endpt_atexit( pjsip_endpoint *endpt,
+ pjsip_endpt_exit_callback func)
+{
+ exit_cb *new_cb;
+
+ PJ_ASSERT_RETURN(endpt && func, PJ_EINVAL);
+
+ new_cb = PJ_POOL_ZALLOC_T(endpt->pool, exit_cb);
+ new_cb->func = func;
+
+ pj_mutex_lock(endpt->mutex);
+ pj_list_push_back(&endpt->exit_cb_list, new_cb);
+ pj_mutex_unlock(endpt->mutex);
+
+ return PJ_SUCCESS;
+}
diff --git a/pjsip/src/pjsip/sip_endpoint_wrap.cpp b/pjsip/src/pjsip/sip_endpoint_wrap.cpp
new file mode 100644
index 0000000..1386023
--- /dev/null
+++ b/pjsip/src/pjsip/sip_endpoint_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_endpoint_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_endpoint.c"
diff --git a/pjsip/src/pjsip/sip_errno.c b/pjsip/src/pjsip/sip_errno.c
new file mode 100644
index 0000000..211db39
--- /dev/null
+++ b/pjsip/src/pjsip/sip_errno.c
@@ -0,0 +1,211 @@
+/* $Id: sip_errno.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_msg.h>
+#include <pj/string.h>
+#include <pj/errno.h>
+
+/* PJSIP's own error codes/messages
+ * MUST KEEP THIS ARRAY SORTED!!
+ */
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+static const struct
+{
+ int code;
+ const char *msg;
+} err_str[] =
+{
+ /* Generic SIP errors */
+ PJ_BUILD_ERR( PJSIP_EBUSY, "Object is busy" ),
+ PJ_BUILD_ERR( PJSIP_ETYPEEXISTS , "Object with the same type exists" ),
+ PJ_BUILD_ERR( PJSIP_ESHUTDOWN, "SIP stack shutting down" ),
+ PJ_BUILD_ERR( PJSIP_ENOTINITIALIZED,"SIP object is not initialized." ),
+ PJ_BUILD_ERR( PJSIP_ENOROUTESET, "Missing route set (for tel: URI)" ),
+
+ /* Messaging errors */
+ PJ_BUILD_ERR( PJSIP_EINVALIDMSG, "Invalid message/syntax error" ),
+ PJ_BUILD_ERR( PJSIP_ENOTREQUESTMSG, "Expecting request message"),
+ PJ_BUILD_ERR( PJSIP_ENOTRESPONSEMSG,"Expecting response message"),
+ PJ_BUILD_ERR( PJSIP_EMSGTOOLONG, "Message too long" ),
+ PJ_BUILD_ERR( PJSIP_EPARTIALMSG, "Partial message" ),
+
+ PJ_BUILD_ERR( PJSIP_EINVALIDSTATUS, "Invalid/unexpected SIP status code"),
+
+ PJ_BUILD_ERR( PJSIP_EINVALIDURI, "Invalid URI" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDSCHEME, "Invalid URI scheme" ),
+ PJ_BUILD_ERR( PJSIP_EMISSINGREQURI, "Missing Request-URI" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDREQURI, "Invalid Request URI" ),
+ PJ_BUILD_ERR( PJSIP_EURITOOLONG, "URI is too long" ),
+
+ PJ_BUILD_ERR( PJSIP_EMISSINGHDR, "Missing required header(s)" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDHDR, "Invalid header field"),
+ PJ_BUILD_ERR( PJSIP_EINVALIDVIA, "Invalid Via header" ),
+ PJ_BUILD_ERR( PJSIP_EMULTIPLEVIA, "Multiple Via headers in response" ),
+
+ PJ_BUILD_ERR( PJSIP_EMISSINGBODY, "Missing message body" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDMETHOD, "Invalid/unexpected method" ),
+
+ /* Transport errors */
+ PJ_BUILD_ERR( PJSIP_EUNSUPTRANSPORT,"Unsupported transport"),
+ PJ_BUILD_ERR( PJSIP_EPENDINGTX, "Transmit buffer already pending"),
+ PJ_BUILD_ERR( PJSIP_ERXOVERFLOW, "Rx buffer overflow"),
+ PJ_BUILD_ERR( PJSIP_EBUFDESTROYED, "Buffer destroyed"),
+ PJ_BUILD_ERR( PJSIP_ETPNOTSUITABLE, "Unsuitable transport selected"),
+ PJ_BUILD_ERR( PJSIP_ETPNOTAVAIL, "Transport not available for use"),
+
+ /* Transaction errors */
+ PJ_BUILD_ERR( PJSIP_ETSXDESTROYED, "Transaction has been destroyed"),
+ PJ_BUILD_ERR( PJSIP_ENOTSX, "No transaction is associated with the object "
+ "(expecting stateful processing)" ),
+
+ /* URI comparison status */
+ PJ_BUILD_ERR( PJSIP_ECMPSCHEME, "URI scheme mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPUSER, "URI user part mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPPASSWD, "URI password part mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPHOST, "URI host part mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPPORT, "URI port mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPTRANSPORTPRM,"URI transport param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPTTLPARAM, "URI ttl param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPUSERPARAM, "URI user param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPMETHODPARAM,"URI method param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPMADDRPARAM, "URI maddr param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPOTHERPARAM, "URI other param mismatch" ),
+ PJ_BUILD_ERR( PJSIP_ECMPHEADERPARAM,"URI header parameter mismatch" ),
+
+ /* Authentication. */
+ PJ_BUILD_ERR( PJSIP_EFAILEDCREDENTIAL, "Credential failed to authenticate"),
+ PJ_BUILD_ERR( PJSIP_ENOCREDENTIAL, "No suitable credential"),
+ PJ_BUILD_ERR( PJSIP_EINVALIDALGORITHM, "Invalid/unsupported digest algorithm" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDQOP, "Invalid/unsupported digest qop" ),
+ PJ_BUILD_ERR( PJSIP_EINVALIDAUTHSCHEME,"Unsupported authentication scheme" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHNOPREVCHAL, "No previous challenge" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHNOAUTH, "No suitable authorization header" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHACCNOTFOUND, "Account or credential not found" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHACCDISABLED, "Account or credential is disabled" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHINVALIDREALM, "Invalid authorization realm"),
+ PJ_BUILD_ERR( PJSIP_EAUTHINVALIDDIGEST,"Invalid authorization digest" ),
+ PJ_BUILD_ERR( PJSIP_EAUTHSTALECOUNT, "Maximum number of stale retries exceeded"),
+ PJ_BUILD_ERR( PJSIP_EAUTHINNONCE, "Invalid nonce value in authentication challenge"),
+ PJ_BUILD_ERR( PJSIP_EAUTHINAKACRED, "Invalid AKA credential"),
+ PJ_BUILD_ERR( PJSIP_EAUTHNOCHAL, "No challenge is found"),
+
+ /* UA/dialog layer. */
+ PJ_BUILD_ERR( PJSIP_EMISSINGTAG, "Missing From/To tag parameter" ),
+ PJ_BUILD_ERR( PJSIP_ENOTREFER, "Expecting REFER request") ,
+ PJ_BUILD_ERR( PJSIP_ENOREFERSESSION,"Not associated with REFER subscription"),
+
+ /* Invite session. */
+ PJ_BUILD_ERR( PJSIP_ESESSIONTERMINATED, "INVITE session already terminated" ),
+ PJ_BUILD_ERR( PJSIP_ESESSIONSTATE, "Invalid INVITE session state" ),
+ PJ_BUILD_ERR( PJSIP_ESESSIONINSECURE, "Require secure session/transport"),
+
+ /* SSL errors */
+ PJ_BUILD_ERR( PJSIP_TLS_EUNKNOWN, "Unknown TLS error" ),
+ PJ_BUILD_ERR( PJSIP_TLS_EINVMETHOD, "Invalid SSL protocol method" ),
+ PJ_BUILD_ERR( PJSIP_TLS_ECACERT, "Error loading/verifying SSL CA list file"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECERTFILE, "Error loading SSL certificate chain file"),
+ PJ_BUILD_ERR( PJSIP_TLS_EKEYFILE, "Error adding private key from SSL certificate file"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECIPHER, "Error setting SSL cipher list"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECTX, "Error creating SSL context"),
+ PJ_BUILD_ERR( PJSIP_TLS_ESSLCONN, "Error creating SSL connection object"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECONNECT, "Unknown error when performing SSL connect()"),
+ PJ_BUILD_ERR( PJSIP_TLS_EACCEPT, "Unknown error when performing SSL accept()"),
+ PJ_BUILD_ERR( PJSIP_TLS_ESEND, "Unknown error when sending SSL data"),
+ PJ_BUILD_ERR( PJSIP_TLS_EREAD, "Unknown error when reading SSL data"),
+ PJ_BUILD_ERR( PJSIP_TLS_ETIMEDOUT, "SSL negotiation has timed out"),
+ PJ_BUILD_ERR( PJSIP_TLS_ECERTVERIF, "SSL certificate verification error"),
+};
+
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+
+/*
+ * pjsip_strerror()
+ */
+PJ_DEF(pj_str_t) pjsip_strerror( pj_status_t statcode,
+ char *buf, pj_size_t bufsize )
+{
+ pj_str_t errstr;
+
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+
+ if (statcode >= PJSIP_ERRNO_START && statcode < PJSIP_ERRNO_START+800)
+ {
+ /* Status code. */
+ const pj_str_t *status_text =
+ pjsip_get_status_text(PJSIP_ERRNO_TO_SIP_STATUS(statcode));
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, status_text, bufsize);
+ return errstr;
+ }
+ else if (statcode >= PJSIP_ERRNO_START_PJSIP &&
+ statcode < PJSIP_ERRNO_START_PJSIP + 1000)
+ {
+ /* Find the error in the table.
+ * Use binary search!
+ */
+ int first = 0;
+ int n = PJ_ARRAY_SIZE(err_str);
+
+ while (n > 0) {
+ int half = n/2;
+ int mid = first + half;
+
+ if (err_str[mid].code < statcode) {
+ first = mid+1;
+ n -= (half+1);
+ } else if (err_str[mid].code > statcode) {
+ n = half;
+ } else {
+ first = mid;
+ break;
+ }
+ }
+
+
+ if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) {
+ pj_str_t msg;
+
+ msg.ptr = (char*)err_str[first].msg;
+ msg.slen = pj_ansi_strlen(err_str[first].msg);
+
+ errstr.ptr = buf;
+ pj_strncpy_with_null(&errstr, &msg, bufsize);
+ return errstr;
+
+ }
+ }
+
+#endif /* PJ_HAS_ERROR_STRING */
+
+ /* Error not found. */
+ errstr.ptr = buf;
+ errstr.slen = pj_ansi_snprintf(buf, bufsize,
+ "Unknown pjsip error %d",
+ statcode);
+
+ return errstr;
+
+}
+
diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c
new file mode 100644
index 0000000..edbf367
--- /dev/null
+++ b/pjsip/src/pjsip/sip_msg.c
@@ -0,0 +1,2218 @@
+/* $Id: sip_msg.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 <pjsip/sip_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_errno.h>
+#include <pj/ctype.h>
+#include <pj/guid.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pjlib-util/string.h>
+
+PJ_DEF_DATA(const pjsip_method) pjsip_invite_method =
+ { PJSIP_INVITE_METHOD, { "INVITE",6 }};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_cancel_method =
+ { PJSIP_CANCEL_METHOD, { "CANCEL",6 }};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_ack_method =
+ { PJSIP_ACK_METHOD, { "ACK",3}};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_bye_method =
+ { PJSIP_BYE_METHOD, { "BYE",3}};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_register_method =
+ { PJSIP_REGISTER_METHOD, { "REGISTER", 8}};
+
+PJ_DEF_DATA(const pjsip_method) pjsip_options_method =
+ { PJSIP_OPTIONS_METHOD, { "OPTIONS",7}};
+
+
+/** INVITE method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_invite_method(void)
+{
+ return &pjsip_invite_method;
+}
+
+/** CANCEL method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_cancel_method(void)
+{
+ return &pjsip_cancel_method;
+}
+
+/** ACK method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_ack_method(void)
+{
+ return &pjsip_ack_method;
+}
+
+/** BYE method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_bye_method(void)
+{
+ return &pjsip_bye_method;
+}
+
+/** REGISTER method constant.*/
+PJ_DEF(const pjsip_method*) pjsip_get_register_method(void)
+{
+ return &pjsip_register_method;
+}
+
+/** OPTIONS method constant. */
+PJ_DEF(const pjsip_method*) pjsip_get_options_method(void)
+{
+ return &pjsip_options_method;
+}
+
+
+static const pj_str_t *method_names[] =
+{
+ &pjsip_invite_method.name,
+ &pjsip_cancel_method.name,
+ &pjsip_ack_method.name,
+ &pjsip_bye_method.name,
+ &pjsip_register_method.name,
+ &pjsip_options_method.name
+};
+
+const pjsip_hdr_name_info_t pjsip_hdr_names[] =
+{
+ { "Accept", 6, NULL }, // PJSIP_H_ACCEPT,
+ { "Accept-Encoding", 15, NULL }, // PJSIP_H_ACCEPT_ENCODING,
+ { "Accept-Language", 15, NULL }, // PJSIP_H_ACCEPT_LANGUAGE,
+ { "Alert-Info", 10, NULL }, // PJSIP_H_ALERT_INFO,
+ { "Allow", 5, NULL }, // PJSIP_H_ALLOW,
+ { "Authentication-Info",19, NULL }, // PJSIP_H_AUTHENTICATION_INFO,
+ { "Authorization", 13, NULL }, // PJSIP_H_AUTHORIZATION,
+ { "Call-ID", 7, "i" }, // PJSIP_H_CALL_ID,
+ { "Call-Info", 9, NULL }, // PJSIP_H_CALL_INFO,
+ { "Contact", 7, "m" }, // PJSIP_H_CONTACT,
+ { "Content-Disposition",19, NULL }, // PJSIP_H_CONTENT_DISPOSITION,
+ { "Content-Encoding", 16, "e" }, // PJSIP_H_CONTENT_ENCODING,
+ { "Content-Language", 16, NULL }, // PJSIP_H_CONTENT_LANGUAGE,
+ { "Content-Length", 14, "l" }, // PJSIP_H_CONTENT_LENGTH,
+ { "Content-Type", 12, "c" }, // PJSIP_H_CONTENT_TYPE,
+ { "CSeq", 4, NULL }, // PJSIP_H_CSEQ,
+ { "Date", 4, NULL }, // PJSIP_H_DATE,
+ { "Error-Info", 10, NULL }, // PJSIP_H_ERROR_INFO,
+ { "Expires", 7, NULL }, // PJSIP_H_EXPIRES,
+ { "From", 4, "f" }, // PJSIP_H_FROM,
+ { "In-Reply-To", 11, NULL }, // PJSIP_H_IN_REPLY_TO,
+ { "Max-Forwards", 12, NULL }, // PJSIP_H_MAX_FORWARDS,
+ { "MIME-Version", 12, NULL }, // PJSIP_H_MIME_VERSION,
+ { "Min-Expires", 11, NULL }, // PJSIP_H_MIN_EXPIRES,
+ { "Organization", 12, NULL }, // PJSIP_H_ORGANIZATION,
+ { "Priority", 8, NULL }, // PJSIP_H_PRIORITY,
+ { "Proxy-Authenticate", 18, NULL }, // PJSIP_H_PROXY_AUTHENTICATE,
+ { "Proxy-Authorization",19, NULL }, // PJSIP_H_PROXY_AUTHORIZATION,
+ { "Proxy-Require", 13, NULL }, // PJSIP_H_PROXY_REQUIRE,
+ { "Record-Route", 12, NULL }, // PJSIP_H_RECORD_ROUTE,
+ { "Reply-To", 8, NULL }, // PJSIP_H_REPLY_TO,
+ { "Require", 7, NULL }, // PJSIP_H_REQUIRE,
+ { "Retry-After", 11, NULL }, // PJSIP_H_RETRY_AFTER,
+ { "Route", 5, NULL }, // PJSIP_H_ROUTE,
+ { "Server", 6, NULL }, // PJSIP_H_SERVER,
+ { "Subject", 7, "s" }, // PJSIP_H_SUBJECT,
+ { "Supported", 9, "k" }, // PJSIP_H_SUPPORTED,
+ { "Timestamp", 9, NULL }, // PJSIP_H_TIMESTAMP,
+ { "To", 2, "t" }, // PJSIP_H_TO,
+ { "Unsupported", 11, NULL }, // PJSIP_H_UNSUPPORTED,
+ { "User-Agent", 10, NULL }, // PJSIP_H_USER_AGENT,
+ { "Via", 3, "v" }, // PJSIP_H_VIA,
+ { "Warning", 7, NULL }, // PJSIP_H_WARNING,
+ { "WWW-Authenticate", 16, NULL }, // PJSIP_H_WWW_AUTHENTICATE,
+
+ { "_Unknown-Header", 15, NULL }, // PJSIP_H_OTHER,
+};
+
+pj_bool_t pjsip_use_compact_form = PJSIP_ENCODE_SHORT_HNAME;
+
+static pj_str_t status_phrase[710];
+static int print_media_type(char *buf, unsigned len,
+ const pjsip_media_type *media);
+
+static int init_status_phrase()
+{
+ unsigned i;
+ pj_str_t default_reason_phrase = { "Default status message", 22};
+
+ for (i=0; i<PJ_ARRAY_SIZE(status_phrase); ++i)
+ status_phrase[i] = default_reason_phrase;
+
+ pj_strset2( &status_phrase[100], "Trying");
+ pj_strset2( &status_phrase[180], "Ringing");
+ pj_strset2( &status_phrase[181], "Call Is Being Forwarded");
+ pj_strset2( &status_phrase[182], "Queued");
+ pj_strset2( &status_phrase[183], "Session Progress");
+
+ pj_strset2( &status_phrase[200], "OK");
+ pj_strset2( &status_phrase[202], "Accepted");
+
+ pj_strset2( &status_phrase[300], "Multiple Choices");
+ pj_strset2( &status_phrase[301], "Moved Permanently");
+ pj_strset2( &status_phrase[302], "Moved Temporarily");
+ pj_strset2( &status_phrase[305], "Use Proxy");
+ pj_strset2( &status_phrase[380], "Alternative Service");
+
+ pj_strset2( &status_phrase[400], "Bad Request");
+ pj_strset2( &status_phrase[401], "Unauthorized");
+ pj_strset2( &status_phrase[402], "Payment Required");
+ pj_strset2( &status_phrase[403], "Forbidden");
+ pj_strset2( &status_phrase[404], "Not Found");
+ pj_strset2( &status_phrase[405], "Method Not Allowed");
+ pj_strset2( &status_phrase[406], "Not Acceptable");
+ pj_strset2( &status_phrase[407], "Proxy Authentication Required");
+ pj_strset2( &status_phrase[408], "Request Timeout");
+ pj_strset2( &status_phrase[410], "Gone");
+ pj_strset2( &status_phrase[413], "Request Entity Too Large");
+ pj_strset2( &status_phrase[414], "Request URI Too Long");
+ pj_strset2( &status_phrase[415], "Unsupported Media Type");
+ pj_strset2( &status_phrase[416], "Unsupported URI Scheme");
+ pj_strset2( &status_phrase[420], "Bad Extension");
+ pj_strset2( &status_phrase[421], "Extension Required");
+ pj_strset2( &status_phrase[422], "Session Timer Too Small");
+ pj_strset2( &status_phrase[423], "Interval Too Brief");
+ pj_strset2( &status_phrase[480], "Temporarily Unavailable");
+ pj_strset2( &status_phrase[481], "Call/Transaction Does Not Exist");
+ pj_strset2( &status_phrase[482], "Loop Detected");
+ pj_strset2( &status_phrase[483], "Too Many Hops");
+ pj_strset2( &status_phrase[484], "Address Incompleted");
+ pj_strset2( &status_phrase[485], "Ambiguous");
+ pj_strset2( &status_phrase[486], "Busy Here");
+ pj_strset2( &status_phrase[487], "Request Terminated");
+ pj_strset2( &status_phrase[488], "Not Acceptable Here");
+ pj_strset2( &status_phrase[489], "Bad Event");
+ pj_strset2( &status_phrase[490], "Request Updated");
+ pj_strset2( &status_phrase[491], "Request Pending");
+ pj_strset2( &status_phrase[493], "Undecipherable");
+
+ pj_strset2( &status_phrase[500], "Internal Server Error");
+ pj_strset2( &status_phrase[501], "Not Implemented");
+ pj_strset2( &status_phrase[502], "Bad Gateway");
+ pj_strset2( &status_phrase[503], "Service Unavailable");
+ pj_strset2( &status_phrase[504], "Server Timeout");
+ pj_strset2( &status_phrase[505], "Version Not Supported");
+ pj_strset2( &status_phrase[513], "Message Too Large");
+ pj_strset2( &status_phrase[580], "Precondition Failure");
+
+ pj_strset2( &status_phrase[600], "Busy Everywhere");
+ pj_strset2( &status_phrase[603], "Decline");
+ pj_strset2( &status_phrase[604], "Does Not Exist Anywhere");
+ pj_strset2( &status_phrase[606], "Not Acceptable");
+
+ pj_strset2( &status_phrase[701], "No response from destination server");
+ pj_strset2( &status_phrase[702], "Unable to resolve destination server");
+ pj_strset2( &status_phrase[703], "Error sending message to destination server");
+
+ return 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Method.
+ */
+
+PJ_DEF(void) pjsip_method_init( pjsip_method *m,
+ pj_pool_t *pool,
+ const pj_str_t *str)
+{
+ pj_str_t dup;
+ pjsip_method_init_np(m, pj_strdup(pool, &dup, str));
+}
+
+PJ_DEF(void) pjsip_method_set( pjsip_method *m, pjsip_method_e me )
+{
+ pj_assert(me < PJSIP_OTHER_METHOD);
+ m->id = me;
+ m->name = *method_names[me];
+}
+
+PJ_DEF(void) pjsip_method_init_np(pjsip_method *m,
+ pj_str_t *str)
+{
+ unsigned i;
+ for (i=0; i<PJ_ARRAY_SIZE(method_names); ++i) {
+ if (pj_memcmp(str->ptr, method_names[i]->ptr, str->slen)==0 ||
+ pj_stricmp(str, method_names[i])==0)
+ {
+ m->id = (pjsip_method_e)i;
+ m->name = *method_names[i];
+ return;
+ }
+ }
+ m->id = PJSIP_OTHER_METHOD;
+ m->name = *str;
+}
+
+PJ_DEF(void) pjsip_method_copy( pj_pool_t *pool,
+ pjsip_method *method,
+ const pjsip_method *rhs )
+{
+ method->id = rhs->id;
+ if (rhs->id != PJSIP_OTHER_METHOD) {
+ method->name = rhs->name;
+ } else {
+ pj_strdup(pool, &method->name, &rhs->name);
+ }
+}
+
+
+PJ_DEF(int) pjsip_method_cmp( const pjsip_method *m1, const pjsip_method *m2)
+{
+ if (m1->id == m2->id) {
+ if (m1->id != PJSIP_OTHER_METHOD)
+ return 0;
+ /* Method comparison is case sensitive! */
+ return pj_strcmp(&m1->name, &m2->name);
+ }
+
+ return ( m1->id < m2->id ) ? -1 : 1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Message.
+ */
+
+PJ_DEF(pjsip_msg*) pjsip_msg_create( pj_pool_t *pool, pjsip_msg_type_e type)
+{
+ pjsip_msg *msg = PJ_POOL_ALLOC_T(pool, pjsip_msg);
+ pj_list_init(&msg->hdr);
+ msg->type = type;
+ msg->body = NULL;
+ return msg;
+}
+
+PJ_DEF(pjsip_msg*) pjsip_msg_clone( pj_pool_t *pool, const pjsip_msg *src)
+{
+ pjsip_msg *dst;
+ const pjsip_hdr *sh;
+
+ dst = pjsip_msg_create(pool, src->type);
+
+ /* Clone request/status line */
+ if (src->type == PJSIP_REQUEST_MSG) {
+ pjsip_method_copy(pool, &dst->line.req.method, &src->line.req.method);
+ dst->line.req.uri = (pjsip_uri*) pjsip_uri_clone(pool,
+ src->line.req.uri);
+ } else {
+ dst->line.status.code = src->line.status.code;
+ pj_strdup(pool, &dst->line.status.reason, &src->line.status.reason);
+ }
+
+ /* Clone headers */
+ sh = src->hdr.next;
+ while (sh != &src->hdr) {
+ pjsip_hdr *dh = (pjsip_hdr*) pjsip_hdr_clone(pool, sh);
+ pjsip_msg_add_hdr(dst, dh);
+ sh = sh->next;
+ }
+
+ /* Clone message body */
+ if (src->body) {
+ dst->body = pjsip_msg_body_clone(pool, src->body);
+ }
+
+ return dst;
+}
+
+PJ_DEF(void*) pjsip_msg_find_hdr( const pjsip_msg *msg,
+ pjsip_hdr_e hdr_type, const void *start)
+{
+ const pjsip_hdr *hdr=(const pjsip_hdr*) start, *end=&msg->hdr;
+
+ if (hdr == NULL) {
+ hdr = msg->hdr.next;
+ }
+ for (; hdr!=end; hdr = hdr->next) {
+ if (hdr->type == hdr_type)
+ return (void*)hdr;
+ }
+ return NULL;
+}
+
+PJ_DEF(void*) pjsip_msg_find_hdr_by_name( const pjsip_msg *msg,
+ const pj_str_t *name,
+ const void *start)
+{
+ const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
+
+ if (hdr == NULL) {
+ hdr = msg->hdr.next;
+ }
+ for (; hdr!=end; hdr = hdr->next) {
+ if (pj_stricmp(&hdr->name, name) == 0)
+ return (void*)hdr;
+ }
+ return NULL;
+}
+
+PJ_DEF(void*) pjsip_msg_find_hdr_by_names( const pjsip_msg *msg,
+ const pj_str_t *name,
+ const pj_str_t *sname,
+ const void *start)
+{
+ const pjsip_hdr *hdr=(const pjsip_hdr*)start, *end=&msg->hdr;
+
+ if (hdr == NULL) {
+ hdr = msg->hdr.next;
+ }
+ for (; hdr!=end; hdr = hdr->next) {
+ if (pj_stricmp(&hdr->name, name) == 0)
+ return (void*)hdr;
+ if (pj_stricmp(&hdr->name, sname) == 0)
+ return (void*)hdr;
+ }
+ return NULL;
+}
+
+PJ_DEF(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg,
+ pjsip_hdr_e hdr_type, void *start)
+{
+ pjsip_hdr *hdr = (pjsip_hdr*) pjsip_msg_find_hdr(msg, hdr_type, start);
+ if (hdr) {
+ pj_list_erase(hdr);
+ }
+ return hdr;
+}
+
+PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg,
+ char *buf, pj_size_t size)
+{
+ char *p=buf, *end=buf+size;
+ int len;
+ pjsip_hdr *hdr;
+ pj_str_t clen_hdr = { "Content-Length: ", 16};
+
+ if (pjsip_use_compact_form) {
+ clen_hdr.ptr = "l: ";
+ clen_hdr.slen = 3;
+ }
+
+ /* Get a wild guess on how many bytes are typically needed.
+ * We'll check this later in detail, but this serves as a quick check.
+ */
+ if (size < 256)
+ return -1;
+
+ /* Print request line or status line depending on message type */
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ pjsip_uri *uri;
+
+ /* Add method. */
+ len = msg->line.req.method.name.slen;
+ pj_memcpy(p, msg->line.req.method.name.ptr, len);
+ p += len;
+ *p++ = ' ';
+
+ /* Add URI */
+ uri = (pjsip_uri*) pjsip_uri_get_uri(msg->line.req.uri);
+ len = pjsip_uri_print( PJSIP_URI_IN_REQ_URI, uri, p, end-p);
+ if (len < 1)
+ return -1;
+ p += len;
+
+ /* Add ' SIP/2.0' */
+ if (end-p < 16)
+ return -1;
+ pj_memcpy(p, " SIP/2.0\r\n", 10);
+ p += 10;
+
+ } else {
+
+ /* Add 'SIP/2.0 ' */
+ pj_memcpy(p, "SIP/2.0 ", 8);
+ p += 8;
+
+ /* Add status code. */
+ len = pj_utoa(msg->line.status.code, p);
+ p += len;
+ *p++ = ' ';
+
+ /* Add reason text. */
+ len = msg->line.status.reason.slen;
+ pj_memcpy(p, msg->line.status.reason.ptr, len );
+ p += len;
+
+ /* Add newline. */
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+
+ /* Print each of the headers. */
+ for (hdr=msg->hdr.next; hdr!=&msg->hdr; hdr=hdr->next) {
+ len = pjsip_hdr_print_on(hdr, p, end-p);
+ if (len < 0)
+ return -1;
+
+ if (len > 0) {
+ p += len;
+ if (p+3 >= end)
+ return -1;
+
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+ }
+
+ /* Process message body. */
+ if (msg->body) {
+ enum { CLEN_SPACE = 5 };
+ char *clen_pos = NULL;
+
+ /* Automaticly adds Content-Type and Content-Length headers, only
+ * if content_type is set in the message body.
+ */
+ if (msg->body->content_type.type.slen) {
+ pj_str_t ctype_hdr = { "Content-Type: ", 14};
+ const pjsip_media_type *media = &msg->body->content_type;
+
+ if (pjsip_use_compact_form) {
+ ctype_hdr.ptr = "c: ";
+ ctype_hdr.slen = 3;
+ }
+
+ /* Add Content-Type header. */
+ if ( (end-p) < 24 + media->type.slen + media->subtype.slen) {
+ return -1;
+ }
+ pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen);
+ p += ctype_hdr.slen;
+ p += print_media_type(p, end-p, media);
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Add Content-Length header. */
+ if ((end-p) < clen_hdr.slen + 12 + 2) {
+ return -1;
+ }
+ pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen);
+ p += clen_hdr.slen;
+
+ /* Print blanks after "Content-Length:", this is where we'll put
+ * the content length value after we know the length of the
+ * body.
+ */
+ pj_memset(p, ' ', CLEN_SPACE);
+ clen_pos = p;
+ p += CLEN_SPACE;
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+
+ /* Add blank newline. */
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Print the message body itself. */
+ len = (*msg->body->print_body)(msg->body, p, end-p);
+ if (len < 0) {
+ return -1;
+ }
+ p += len;
+
+ /* Now that we have the length of the body, print this to the
+ * Content-Length header.
+ */
+ if (clen_pos) {
+ char tmp[16];
+ len = pj_utoa(len, tmp);
+ if (len > CLEN_SPACE) len = CLEN_SPACE;
+ pj_memcpy(clen_pos+CLEN_SPACE-len, tmp, len);
+ }
+
+ } else {
+ /* There's no message body.
+ * Add Content-Length with zero value.
+ */
+ if ((end-p) < clen_hdr.slen+8) {
+ return -1;
+ }
+ pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen);
+ p += clen_hdr.slen;
+ *p++ = ' ';
+ *p++ = '0';
+ *p++ = '\r';
+ *p++ = '\n';
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+
+ *p = '\0';
+ return p-buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+PJ_DEF(void*) pjsip_hdr_clone( pj_pool_t *pool, const void *hdr_ptr )
+{
+ const pjsip_hdr *hdr = (const pjsip_hdr*) hdr_ptr;
+ return (*hdr->vptr->clone)(pool, hdr_ptr);
+}
+
+
+PJ_DEF(void*) pjsip_hdr_shallow_clone( pj_pool_t *pool, const void *hdr_ptr )
+{
+ const pjsip_hdr *hdr = (const pjsip_hdr*) hdr_ptr;
+ return (*hdr->vptr->shallow_clone)(pool, hdr_ptr);
+}
+
+PJ_DEF(int) pjsip_hdr_print_on( void *hdr_ptr, char *buf, pj_size_t len)
+{
+ pjsip_hdr *hdr = (pjsip_hdr*) hdr_ptr;
+ return (*hdr->vptr->print_on)(hdr_ptr, buf, len);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Status/Reason Phrase
+ */
+
+PJ_DEF(const pj_str_t*) pjsip_get_status_text(int code)
+{
+ static int is_initialized;
+ if (is_initialized == 0) {
+ is_initialized = 1;
+ init_status_phrase();
+ }
+
+ return (code>=100 &&
+ code<(int)(sizeof(status_phrase)/sizeof(status_phrase[0]))) ?
+ &status_phrase[code] : &status_phrase[0];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Media type
+ */
+/*
+ * Init media type.
+ */
+PJ_DEF(void) pjsip_media_type_init( pjsip_media_type *mt,
+ pj_str_t *type,
+ pj_str_t *subtype)
+{
+ pj_bzero(mt, sizeof(*mt));
+ pj_list_init(&mt->param);
+ if (type)
+ mt->type = *type;
+ if (subtype)
+ mt->subtype = *subtype;
+}
+
+PJ_DEF(void) pjsip_media_type_init2( pjsip_media_type *mt,
+ char *type,
+ char *subtype)
+{
+ pj_str_t s_type, s_subtype;
+
+ if (type) {
+ s_type = pj_str(type);
+ } else {
+ s_type.ptr = NULL;
+ s_type.slen = 0;
+ }
+
+ if (subtype) {
+ s_subtype = pj_str(subtype);
+ } else {
+ s_subtype.ptr = NULL;
+ s_subtype.slen = 0;
+ }
+
+ pjsip_media_type_init(mt, &s_type, &s_subtype);
+}
+
+/*
+ * Compare two media types.
+ */
+PJ_DEF(int) pjsip_media_type_cmp( const pjsip_media_type *mt1,
+ const pjsip_media_type *mt2,
+ pj_bool_t cmp_param)
+{
+ int rc;
+
+ PJ_ASSERT_RETURN(mt1 && mt2, 1);
+
+ rc = pj_stricmp(&mt1->type, &mt2->type);
+ if (rc) return rc;
+
+ rc = pj_stricmp(&mt1->subtype, &mt2->subtype);
+ if (rc) return rc;
+
+ if (cmp_param) {
+ rc = pjsip_param_cmp(&mt1->param, &mt2->param, (cmp_param==1));
+ }
+
+ return rc;
+}
+
+PJ_DEF(void) pjsip_media_type_cp( pj_pool_t *pool,
+ pjsip_media_type *dst,
+ const pjsip_media_type *src)
+{
+ PJ_ASSERT_ON_FAIL(pool && dst && src, return);
+ pj_strdup(pool, &dst->type, &src->type);
+ pj_strdup(pool, &dst->subtype, &src->subtype);
+ pjsip_param_clone(pool, &dst->param, &src->param);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Generic pjsip_hdr_names/hvalue header.
+ */
+
+static int pjsip_generic_string_hdr_print( pjsip_generic_string_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_generic_string_hdr* pjsip_generic_string_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_string_hdr *hdr);
+static pjsip_generic_string_hdr* pjsip_generic_string_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_string_hdr *hdr );
+
+static pjsip_hdr_vptr generic_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_generic_string_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_generic_string_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_generic_string_hdr_print,
+};
+
+
+PJ_DEF(void) pjsip_generic_string_hdr_init2(pjsip_generic_string_hdr *hdr,
+ pj_str_t *hname,
+ pj_str_t *hvalue)
+{
+ init_hdr(hdr, PJSIP_H_OTHER, &generic_hdr_vptr);
+ if (hname) {
+ hdr->name = *hname;
+ hdr->sname = *hname;
+ }
+ if (hvalue) {
+ hdr->hvalue = *hvalue;
+ } else {
+ hdr->hvalue.ptr = NULL;
+ hdr->hvalue.slen = 0;
+ }
+}
+
+
+PJ_DEF(pjsip_generic_string_hdr*) pjsip_generic_string_hdr_init(pj_pool_t *pool,
+ void *mem,
+ const pj_str_t *hnames,
+ const pj_str_t *hvalue)
+{
+ pjsip_generic_string_hdr *hdr = (pjsip_generic_string_hdr*) mem;
+ pj_str_t dup_hname, dup_hval;
+
+ if (hnames) {
+ pj_strdup(pool, &dup_hname, hnames);
+ } else {
+ dup_hname.slen = 0;
+ }
+
+ if (hvalue) {
+ pj_strdup(pool, &dup_hval, hvalue);
+ } else {
+ dup_hval.slen = 0;
+ }
+
+ pjsip_generic_string_hdr_init2(hdr, &dup_hname, &dup_hval);
+ return hdr;
+}
+
+PJ_DEF(pjsip_generic_string_hdr*) pjsip_generic_string_hdr_create(pj_pool_t *pool,
+ const pj_str_t *hnames,
+ const pj_str_t *hvalue)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_string_hdr));
+ return pjsip_generic_string_hdr_init(pool, mem, hnames, hvalue);
+}
+
+static int pjsip_generic_string_hdr_print( pjsip_generic_string_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ if ((pj_ssize_t)size < hname->slen + hdr->hvalue.slen + 5)
+ return -1;
+
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+ pj_memcpy(p, hdr->hvalue.ptr, hdr->hvalue.slen);
+ p += hdr->hvalue.slen;
+ *p = '\0';
+
+ return p - buf;
+}
+
+static pjsip_generic_string_hdr* pjsip_generic_string_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_string_hdr *rhs)
+{
+ pjsip_generic_string_hdr *hdr;
+
+ hdr = pjsip_generic_string_hdr_create(pool, &rhs->name, &rhs->hvalue);
+
+ hdr->type = rhs->type;
+ pj_strdup(pool, &hdr->sname, &rhs->sname);
+ return hdr;
+}
+
+static pjsip_generic_string_hdr* pjsip_generic_string_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_string_hdr *rhs )
+{
+ pjsip_generic_string_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_string_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Generic pjsip_hdr_names/integer value header.
+ */
+
+static int pjsip_generic_int_hdr_print( pjsip_generic_int_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_generic_int_hdr* pjsip_generic_int_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_int_hdr *hdr);
+static pjsip_generic_int_hdr* pjsip_generic_int_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_int_hdr *hdr );
+
+static pjsip_hdr_vptr generic_int_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_generic_int_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_generic_int_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_generic_int_hdr_print,
+};
+
+PJ_DEF(pjsip_generic_int_hdr*) pjsip_generic_int_hdr_init( pj_pool_t *pool,
+ void *mem,
+ const pj_str_t *hnames,
+ int value)
+{
+ pjsip_generic_int_hdr *hdr = (pjsip_generic_int_hdr*) mem;
+
+ init_hdr(hdr, PJSIP_H_OTHER, &generic_int_hdr_vptr);
+ if (hnames) {
+ pj_strdup(pool, &hdr->name, hnames);
+ hdr->sname = hdr->name;
+ }
+ hdr->ivalue = value;
+ return hdr;
+}
+
+PJ_DEF(pjsip_generic_int_hdr*) pjsip_generic_int_hdr_create( pj_pool_t *pool,
+ const pj_str_t *hnames,
+ int value)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_int_hdr));
+ return pjsip_generic_int_hdr_init(pool, mem, hnames, value);
+}
+
+static int pjsip_generic_int_hdr_print( pjsip_generic_int_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ if ((pj_ssize_t)size < hname->slen + 15)
+ return -1;
+
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ p += pj_utoa(hdr->ivalue, p);
+
+ return p - buf;
+}
+
+static pjsip_generic_int_hdr* pjsip_generic_int_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_int_hdr *rhs)
+{
+ pjsip_generic_int_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_int_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+static pjsip_generic_int_hdr* pjsip_generic_int_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_int_hdr *rhs )
+{
+ pjsip_generic_int_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_int_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Generic array header.
+ */
+static int pjsip_generic_array_hdr_print( pjsip_generic_array_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_generic_array_hdr* pjsip_generic_array_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_array_hdr *hdr);
+static pjsip_generic_array_hdr* pjsip_generic_array_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_array_hdr *hdr);
+
+static pjsip_hdr_vptr generic_array_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_generic_array_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_generic_array_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_generic_array_hdr_print,
+};
+
+
+PJ_DEF(pjsip_generic_array_hdr*) pjsip_generic_array_hdr_init( pj_pool_t *pool,
+ void *mem,
+ const pj_str_t *hnames)
+{
+ pjsip_generic_array_hdr *hdr = (pjsip_generic_array_hdr*) mem;
+
+ init_hdr(hdr, PJSIP_H_OTHER, &generic_array_hdr_vptr);
+ if (hnames) {
+ pj_strdup(pool, &hdr->name, hnames);
+ hdr->sname = hdr->name;
+ }
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_generic_array_hdr*) pjsip_generic_array_hdr_create( pj_pool_t *pool,
+ const pj_str_t *hnames)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_generic_array_hdr));
+ return pjsip_generic_array_hdr_init(pool, mem, hnames);
+
+}
+
+static int pjsip_generic_array_hdr_print( pjsip_generic_array_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf, *endbuf = buf+size;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ copy_advance(p, (*hname));
+ *p++ = ':';
+ *p++ = ' ';
+
+ if (hdr->count > 0) {
+ unsigned i;
+ int printed;
+ copy_advance(p, hdr->values[0]);
+ for (i=1; i<hdr->count; ++i) {
+ copy_advance_pair(p, ", ", 2, hdr->values[i]);
+ }
+ }
+
+ return p - buf;
+}
+
+static pjsip_generic_array_hdr* pjsip_generic_array_hdr_clone( pj_pool_t *pool,
+ const pjsip_generic_array_hdr *rhs)
+{
+ unsigned i;
+ pjsip_generic_array_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_array_hdr);
+
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ for (i=0; i<rhs->count; ++i) {
+ pj_strdup(pool, &hdr->values[i], &rhs->values[i]);
+ }
+
+ return hdr;
+}
+
+
+static pjsip_generic_array_hdr* pjsip_generic_array_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_generic_array_hdr *rhs)
+{
+ pjsip_generic_array_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_generic_array_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Accept header.
+ */
+PJ_DEF(pjsip_accept_hdr*) pjsip_accept_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_accept_hdr *hdr = (pjsip_accept_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_ACCEPT, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_accept_hdr*) pjsip_accept_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_accept_hdr));
+ return pjsip_accept_hdr_init(pool, mem);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Allow header.
+ */
+
+PJ_DEF(pjsip_allow_hdr*) pjsip_allow_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_allow_hdr *hdr = (pjsip_allow_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_ALLOW, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_allow_hdr*) pjsip_allow_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_allow_hdr));
+ return pjsip_allow_hdr_init(pool, mem);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Call-ID header.
+ */
+
+PJ_DEF(pjsip_cid_hdr*) pjsip_cid_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_cid_hdr *hdr = (pjsip_cid_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_CALL_ID, &generic_hdr_vptr);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_cid_hdr*) pjsip_cid_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_cid_hdr));
+ return pjsip_cid_hdr_init(pool, mem);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Content-Length header.
+ */
+static int pjsip_clen_hdr_print( pjsip_clen_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_clen_hdr* pjsip_clen_hdr_clone( pj_pool_t *pool, const pjsip_clen_hdr *hdr);
+#define pjsip_clen_hdr_shallow_clone pjsip_clen_hdr_clone
+
+static pjsip_hdr_vptr clen_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_clen_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_clen_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_clen_hdr_print,
+};
+
+PJ_DEF(pjsip_clen_hdr*) pjsip_clen_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_clen_hdr *hdr = (pjsip_clen_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_CONTENT_LENGTH, &clen_hdr_vptr);
+ hdr->len = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_clen_hdr*) pjsip_clen_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_clen_hdr));
+ return pjsip_clen_hdr_init(pool, mem);
+}
+
+static int pjsip_clen_hdr_print( pjsip_clen_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ int len;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ if ((pj_ssize_t)size < hname->slen + 14)
+ return -1;
+
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ len = pj_utoa(hdr->len, p);
+ p += len;
+ *p = '\0';
+
+ return p-buf;
+}
+
+static pjsip_clen_hdr* pjsip_clen_hdr_clone( pj_pool_t *pool, const pjsip_clen_hdr *rhs)
+{
+ pjsip_clen_hdr *hdr = pjsip_clen_hdr_create(pool);
+ hdr->len = rhs->len;
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * CSeq header.
+ */
+static int pjsip_cseq_hdr_print( pjsip_cseq_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_cseq_hdr* pjsip_cseq_hdr_clone( pj_pool_t *pool, const pjsip_cseq_hdr *hdr);
+static pjsip_cseq_hdr* pjsip_cseq_hdr_shallow_clone( pj_pool_t *pool, const pjsip_cseq_hdr *hdr );
+
+static pjsip_hdr_vptr cseq_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_cseq_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_cseq_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_cseq_hdr_print,
+};
+
+PJ_DEF(pjsip_cseq_hdr*) pjsip_cseq_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_cseq_hdr *hdr = (pjsip_cseq_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_CSEQ, &cseq_hdr_vptr);
+ hdr->cseq = 0;
+ hdr->method.id = PJSIP_OTHER_METHOD;
+ hdr->method.name.ptr = NULL;
+ hdr->method.name.slen = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_cseq_hdr*) pjsip_cseq_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_cseq_hdr));
+ return pjsip_cseq_hdr_init(pool, mem);
+}
+
+static int pjsip_cseq_hdr_print( pjsip_cseq_hdr *hdr, char *buf, pj_size_t size)
+{
+ char *p = buf;
+ int len;
+ /* CSeq doesn't have compact form */
+
+ if ((pj_ssize_t)size < hdr->name.slen + hdr->method.name.slen + 15)
+ return -1;
+
+ pj_memcpy(p, hdr->name.ptr, hdr->name.slen);
+ p += hdr->name.slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ len = pj_utoa(hdr->cseq, p);
+ p += len;
+ *p++ = ' ';
+
+ pj_memcpy(p, hdr->method.name.ptr, hdr->method.name.slen);
+ p += hdr->method.name.slen;
+
+ *p = '\0';
+
+ return p-buf;
+}
+
+static pjsip_cseq_hdr* pjsip_cseq_hdr_clone( pj_pool_t *pool,
+ const pjsip_cseq_hdr *rhs)
+{
+ pjsip_cseq_hdr *hdr = pjsip_cseq_hdr_create(pool);
+ hdr->cseq = rhs->cseq;
+ pjsip_method_copy(pool, &hdr->method, &rhs->method);
+ return hdr;
+}
+
+static pjsip_cseq_hdr* pjsip_cseq_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_cseq_hdr *rhs )
+{
+ pjsip_cseq_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_cseq_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Contact header.
+ */
+static int pjsip_contact_hdr_print( pjsip_contact_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_contact_hdr* pjsip_contact_hdr_clone( pj_pool_t *pool, const pjsip_contact_hdr *hdr);
+static pjsip_contact_hdr* pjsip_contact_hdr_shallow_clone( pj_pool_t *pool, const pjsip_contact_hdr *);
+
+static pjsip_hdr_vptr contact_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_contact_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_contact_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_contact_hdr_print,
+};
+
+PJ_DEF(pjsip_contact_hdr*) pjsip_contact_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_contact_hdr *hdr = (pjsip_contact_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_contact_hdr));
+ init_hdr(hdr, PJSIP_H_CONTACT, &contact_hdr_vptr);
+ hdr->expires = -1;
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_contact_hdr*) pjsip_contact_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_contact_hdr));
+ return pjsip_contact_hdr_init(pool, mem);
+}
+
+static int pjsip_contact_hdr_print( pjsip_contact_hdr *hdr, char *buf,
+ pj_size_t size)
+{
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ if (hdr->star) {
+ char *p = buf;
+ if ((pj_ssize_t)size < hname->slen + 6)
+ return -1;
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+ *p++ = '*';
+ return p - buf;
+
+ } else {
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+
+ copy_advance(buf, (*hname));
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ printed = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, hdr->uri,
+ buf, endbuf-buf);
+ if (printed < 1)
+ return -1;
+
+ buf += printed;
+
+ if (hdr->q1000) {
+ unsigned frac;
+
+ if (buf+19 >= endbuf)
+ return -1;
+
+ /*
+ printed = sprintf(buf, ";q=%u.%03u",
+ hdr->q1000/1000, hdr->q1000 % 1000);
+ */
+ pj_memcpy(buf, ";q=", 3);
+ printed = pj_utoa(hdr->q1000/1000, buf+3);
+ buf += printed + 3;
+ frac = hdr->q1000 % 1000;
+ if (frac != 0) {
+ *buf++ = '.';
+ if ((frac % 100)==0) frac /= 100;
+ if ((frac % 10)==0) frac /= 10;
+ printed = pj_utoa(frac, buf);
+ buf += printed;
+ }
+ }
+
+ if (hdr->expires >= 0) {
+ if (buf+23 >= endbuf)
+ return -1;
+
+ pj_memcpy(buf, ";expires=", 9);
+ printed = pj_utoa(hdr->expires, buf+9);
+ buf += printed + 9;
+ }
+
+ printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC,
+ ';');
+ if (printed < 0)
+ return printed;
+ buf += printed;
+
+ return buf-startbuf;
+ }
+}
+
+static pjsip_contact_hdr* pjsip_contact_hdr_clone(pj_pool_t *pool,
+ const pjsip_contact_hdr *rhs)
+{
+ pjsip_contact_hdr *hdr = pjsip_contact_hdr_create(pool);
+
+ hdr->star = rhs->star;
+ if (hdr->star)
+ return hdr;
+
+ hdr->uri = (pjsip_uri*) pjsip_uri_clone(pool, rhs->uri);
+ hdr->q1000 = rhs->q1000;
+ hdr->expires = rhs->expires;
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_contact_hdr*
+pjsip_contact_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_contact_hdr *rhs)
+{
+ pjsip_contact_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_contact_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Content-Type header..
+ */
+static int pjsip_ctype_hdr_print( pjsip_ctype_hdr *hdr, char *buf,
+ pj_size_t size);
+static pjsip_ctype_hdr* pjsip_ctype_hdr_clone(pj_pool_t *pool,
+ const pjsip_ctype_hdr *hdr);
+#define pjsip_ctype_hdr_shallow_clone pjsip_ctype_hdr_clone
+
+static pjsip_hdr_vptr ctype_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_ctype_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_ctype_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_ctype_hdr_print,
+};
+
+PJ_DEF(pjsip_ctype_hdr*) pjsip_ctype_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_ctype_hdr *hdr = (pjsip_ctype_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_ctype_hdr));
+ init_hdr(hdr, PJSIP_H_CONTENT_TYPE, &ctype_hdr_vptr);
+ pj_list_init(&hdr->media.param);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_ctype_hdr*) pjsip_ctype_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_ctype_hdr));
+ return pjsip_ctype_hdr_init(pool, mem);
+}
+
+static int print_media_type(char *buf, unsigned len,
+ const pjsip_media_type *media)
+{
+ char *p = buf;
+ pj_ssize_t printed;
+ const pjsip_parser_const_t *pc;
+
+ pj_memcpy(p, media->type.ptr, media->type.slen);
+ p += media->type.slen;
+ *p++ = '/';
+ pj_memcpy(p, media->subtype.ptr, media->subtype.slen);
+ p += media->subtype.slen;
+
+ pc = pjsip_parser_const();
+ printed = pjsip_param_print_on(&media->param, p, buf+len-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return -1;
+
+ p += printed;
+
+ return p-buf;
+}
+
+
+PJ_DEF(int) pjsip_media_type_print(char *buf, unsigned len,
+ const pjsip_media_type *media)
+{
+ return print_media_type(buf, len, media);
+}
+
+static int pjsip_ctype_hdr_print( pjsip_ctype_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ int len;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+
+ if ((pj_ssize_t)size < hname->slen +
+ hdr->media.type.slen + hdr->media.subtype.slen + 8)
+ {
+ return -1;
+ }
+
+ pj_memcpy(p, hname->ptr, hname->slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ len = print_media_type(p, buf+size-p, &hdr->media);
+ p += len;
+
+ *p = '\0';
+ return p-buf;
+}
+
+static pjsip_ctype_hdr* pjsip_ctype_hdr_clone( pj_pool_t *pool,
+ const pjsip_ctype_hdr *rhs)
+{
+ pjsip_ctype_hdr *hdr = pjsip_ctype_hdr_create(pool);
+ pj_strdup(pool, &hdr->media.type, &rhs->media.type);
+ pj_strdup(pool, &hdr->media.subtype, &rhs->media.subtype);
+ pjsip_param_clone(pool, &hdr->media.param, &rhs->media.param);
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Expires header.
+ */
+PJ_DEF(pjsip_expires_hdr*) pjsip_expires_hdr_init( pj_pool_t *pool,
+ void *mem,
+ int value)
+{
+ pjsip_expires_hdr *hdr = (pjsip_expires_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_EXPIRES, &generic_int_hdr_vptr);
+ hdr->ivalue = value;
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_expires_hdr*) pjsip_expires_hdr_create( pj_pool_t *pool,
+ int value )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_expires_hdr));
+ return pjsip_expires_hdr_init(pool, mem, value);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * To or From header.
+ */
+static int pjsip_fromto_hdr_print( pjsip_fromto_hdr *hdr,
+ char *buf, pj_size_t size);
+static pjsip_fromto_hdr* pjsip_fromto_hdr_clone( pj_pool_t *pool,
+ const pjsip_fromto_hdr *hdr);
+static pjsip_fromto_hdr* pjsip_fromto_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_fromto_hdr *hdr);
+
+
+static pjsip_hdr_vptr fromto_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_fromto_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_fromto_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_fromto_hdr_print,
+};
+
+PJ_DEF(pjsip_from_hdr*) pjsip_from_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_from_hdr *hdr = (pjsip_from_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_from_hdr));
+ init_hdr(hdr, PJSIP_H_FROM, &fromto_hdr_vptr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_from_hdr*) pjsip_from_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_from_hdr));
+ return pjsip_from_hdr_init(pool, mem);
+}
+
+PJ_DEF(pjsip_to_hdr*) pjsip_to_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_to_hdr *hdr = (pjsip_to_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_to_hdr));
+ init_hdr(hdr, PJSIP_H_TO, &fromto_hdr_vptr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_to_hdr*) pjsip_to_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_to_hdr));
+ return pjsip_to_hdr_init(pool, mem);
+}
+
+PJ_DEF(pjsip_from_hdr*) pjsip_fromto_hdr_set_from( pjsip_fromto_hdr *hdr )
+{
+ hdr->type = PJSIP_H_FROM;
+ hdr->name.ptr = pjsip_hdr_names[PJSIP_H_FROM].name;
+ hdr->name.slen = pjsip_hdr_names[PJSIP_H_FROM].name_len;
+ hdr->sname.ptr = pjsip_hdr_names[PJSIP_H_FROM].sname;
+ hdr->sname.slen = 1;
+ return hdr;
+}
+
+PJ_DEF(pjsip_to_hdr*) pjsip_fromto_hdr_set_to( pjsip_fromto_hdr *hdr )
+{
+ hdr->type = PJSIP_H_TO;
+ hdr->name.ptr = pjsip_hdr_names[PJSIP_H_TO].name;
+ hdr->name.slen = pjsip_hdr_names[PJSIP_H_TO].name_len;
+ hdr->sname.ptr = pjsip_hdr_names[PJSIP_H_TO].sname;
+ hdr->sname.slen = 1;
+ return hdr;
+}
+
+static int pjsip_fromto_hdr_print( pjsip_fromto_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ copy_advance(buf, (*hname));
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ printed = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, hdr->uri,
+ buf, endbuf-buf);
+ if (printed < 1)
+ return -1;
+
+ buf += printed;
+
+ copy_advance_pair_escape(buf, ";tag=", 5, hdr->tag,
+ pc->pjsip_TOKEN_SPEC);
+
+ printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return buf-startbuf;
+}
+
+static pjsip_fromto_hdr* pjsip_fromto_hdr_clone( pj_pool_t *pool,
+ const pjsip_fromto_hdr *rhs)
+{
+ pjsip_fromto_hdr *hdr = pjsip_from_hdr_create(pool);
+
+ hdr->type = rhs->type;
+ hdr->name = rhs->name;
+ hdr->sname = rhs->sname;
+ hdr->uri = (pjsip_uri*) pjsip_uri_clone(pool, rhs->uri);
+ pj_strdup( pool, &hdr->tag, &rhs->tag);
+ pjsip_param_clone( pool, &hdr->other_param, &rhs->other_param);
+
+ return hdr;
+}
+
+static pjsip_fromto_hdr*
+pjsip_fromto_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_fromto_hdr *rhs)
+{
+ pjsip_fromto_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_fromto_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone( pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Max-Forwards header.
+ */
+PJ_DEF(pjsip_max_fwd_hdr*) pjsip_max_fwd_hdr_init( pj_pool_t *pool,
+ void *mem,
+ int value)
+{
+ pjsip_max_fwd_hdr *hdr = (pjsip_max_fwd_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_MAX_FORWARDS, &generic_int_hdr_vptr);
+ hdr->ivalue = value;
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_max_fwd_hdr*) pjsip_max_fwd_hdr_create(pj_pool_t *pool,
+ int value)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_max_fwd_hdr));
+ return pjsip_max_fwd_hdr_init(pool, mem, value);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Min-Expires header.
+ */
+PJ_DEF(pjsip_min_expires_hdr*) pjsip_min_expires_hdr_init( pj_pool_t *pool,
+ void *mem,
+ int value )
+{
+ pjsip_min_expires_hdr *hdr = (pjsip_min_expires_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_MIN_EXPIRES, &generic_int_hdr_vptr);
+ hdr->ivalue = value;
+ return hdr;
+}
+
+PJ_DEF(pjsip_min_expires_hdr*) pjsip_min_expires_hdr_create(pj_pool_t *pool,
+ int value )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_min_expires_hdr));
+ return pjsip_min_expires_hdr_init(pool, mem, value );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Record-Route and Route header.
+ */
+static int pjsip_routing_hdr_print( pjsip_routing_hdr *r, char *buf, pj_size_t size );
+static pjsip_routing_hdr* pjsip_routing_hdr_clone( pj_pool_t *pool, const pjsip_routing_hdr *r );
+static pjsip_routing_hdr* pjsip_routing_hdr_shallow_clone( pj_pool_t *pool, const pjsip_routing_hdr *r );
+
+static pjsip_hdr_vptr routing_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_routing_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_routing_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_routing_hdr_print,
+};
+
+PJ_DEF(pjsip_rr_hdr*) pjsip_rr_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_rr_hdr *hdr = (pjsip_rr_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_RECORD_ROUTE, &routing_hdr_vptr);
+ pjsip_name_addr_init(&hdr->name_addr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_rr_hdr*) pjsip_rr_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_rr_hdr));
+ return pjsip_rr_hdr_init(pool, mem);
+}
+
+PJ_DEF(pjsip_route_hdr*) pjsip_route_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_route_hdr *hdr = (pjsip_route_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_ROUTE, &routing_hdr_vptr);
+ pjsip_name_addr_init(&hdr->name_addr);
+ pj_list_init(&hdr->other_param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_route_hdr*) pjsip_route_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_route_hdr));
+ return pjsip_route_hdr_init(pool, mem);
+}
+
+PJ_DEF(pjsip_rr_hdr*) pjsip_routing_hdr_set_rr( pjsip_routing_hdr *hdr )
+{
+ hdr->type = PJSIP_H_RECORD_ROUTE;
+ hdr->name.ptr = pjsip_hdr_names[PJSIP_H_RECORD_ROUTE].name;
+ hdr->name.slen = pjsip_hdr_names[PJSIP_H_RECORD_ROUTE].name_len;
+ hdr->sname = hdr->name;
+ return hdr;
+}
+
+PJ_DEF(pjsip_route_hdr*) pjsip_routing_hdr_set_route( pjsip_routing_hdr *hdr )
+{
+ hdr->type = PJSIP_H_ROUTE;
+ hdr->name.ptr = pjsip_hdr_names[PJSIP_H_ROUTE].name;
+ hdr->name.slen = pjsip_hdr_names[PJSIP_H_ROUTE].name_len;
+ hdr->sname = hdr->name;
+ return hdr;
+}
+
+static int pjsip_routing_hdr_print( pjsip_routing_hdr *hdr,
+ char *buf, pj_size_t size )
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ pjsip_sip_uri *sip_uri;
+ pjsip_param *p;
+
+ /* Check the proprietary param 'hide', don't print this header
+ * if it exists in the route URI.
+ */
+ sip_uri = (pjsip_sip_uri*) pjsip_uri_get_uri(hdr->name_addr.uri);
+ p = sip_uri->other_param.next;
+ while (p != &sip_uri->other_param) {
+ const pj_str_t st_hide = {"hide", 4};
+
+ if (pj_stricmp(&p->name, &st_hide) == 0) {
+ /* Check if param 'hide' is specified without 'lr'. */
+ pj_assert(sip_uri->lr_param != 0);
+ return 0;
+ }
+ p = p->next;
+ }
+
+ /* Route and Record-Route don't compact forms */
+
+ copy_advance(buf, hdr->name);
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ printed = pjsip_uri_print(PJSIP_URI_IN_ROUTING_HDR, &hdr->name_addr, buf,
+ endbuf-buf);
+ if (printed < 1)
+ return -1;
+ buf += printed;
+
+ printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return buf-startbuf;
+}
+
+static pjsip_routing_hdr* pjsip_routing_hdr_clone( pj_pool_t *pool,
+ const pjsip_routing_hdr *rhs )
+{
+ pjsip_routing_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_routing_hdr);
+
+ init_hdr(hdr, rhs->type, rhs->vptr);
+ pjsip_name_addr_init(&hdr->name_addr);
+ pjsip_name_addr_assign(pool, &hdr->name_addr, &rhs->name_addr);
+ pjsip_param_clone( pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_routing_hdr* pjsip_routing_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_routing_hdr *rhs )
+{
+ pjsip_routing_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_routing_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone( pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Require header.
+ */
+PJ_DEF(pjsip_require_hdr*) pjsip_require_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_require_hdr *hdr = (pjsip_require_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_REQUIRE, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_require_hdr*) pjsip_require_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_require_hdr));
+ return pjsip_require_hdr_init(pool, mem);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Retry-After header.
+ */
+static int pjsip_retry_after_hdr_print(pjsip_retry_after_hdr *r,
+ char *buf, pj_size_t size );
+static pjsip_retry_after_hdr* pjsip_retry_after_hdr_clone(pj_pool_t *pool,
+ const pjsip_retry_after_hdr *r);
+static pjsip_retry_after_hdr*
+pjsip_retry_after_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_retry_after_hdr *r );
+
+static pjsip_hdr_vptr retry_after_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_retry_after_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_retry_after_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_retry_after_hdr_print,
+};
+
+
+PJ_DEF(pjsip_retry_after_hdr*) pjsip_retry_after_hdr_init( pj_pool_t *pool,
+ void *mem,
+ int value )
+{
+ pjsip_retry_after_hdr *hdr = (pjsip_retry_after_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_RETRY_AFTER, &retry_after_hdr_vptr);
+ hdr->ivalue = value;
+ hdr->comment.slen = 0;
+ pj_list_init(&hdr->param);
+ return hdr;
+}
+
+PJ_DEF(pjsip_retry_after_hdr*) pjsip_retry_after_hdr_create(pj_pool_t *pool,
+ int value )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_retry_after_hdr));
+ return pjsip_retry_after_hdr_init(pool, mem, value );
+}
+
+
+static int pjsip_retry_after_hdr_print(pjsip_retry_after_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ char *p = buf;
+ char *endbuf = buf + size;
+ const pj_str_t *hname = &hdr->name;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ int printed;
+
+ if ((pj_ssize_t)size < hdr->name.slen + 2+11)
+ return -1;
+
+ pj_memcpy(p, hdr->name.ptr, hdr->name.slen);
+ p += hname->slen;
+ *p++ = ':';
+ *p++ = ' ';
+
+ p += pj_utoa(hdr->ivalue, p);
+
+ if (hdr->comment.slen) {
+ pj_bool_t enclosed;
+
+ if (endbuf-p < hdr->comment.slen + 3)
+ return -1;
+
+ enclosed = (*hdr->comment.ptr == '(');
+ if (!enclosed)
+ *p++ = '(';
+ pj_memcpy(p, hdr->comment.ptr, hdr->comment.slen);
+ p += hdr->comment.slen;
+ if (!enclosed)
+ *p++ = ')';
+
+ if (!pj_list_empty(&hdr->param))
+ *p++ = ' ';
+ }
+
+ printed = pjsip_param_print_on(&hdr->param, p, endbuf-p,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC,
+ ';');
+ if (printed < 0)
+ return printed;
+
+ p += printed;
+
+ return p - buf;
+}
+
+static pjsip_retry_after_hdr* pjsip_retry_after_hdr_clone(pj_pool_t *pool,
+ const pjsip_retry_after_hdr *rhs)
+{
+ pjsip_retry_after_hdr *hdr = pjsip_retry_after_hdr_create(pool, rhs->ivalue);
+ pj_strdup(pool, &hdr->comment, &rhs->comment);
+ pjsip_param_clone(pool, &hdr->param, &rhs->param);
+ return hdr;
+}
+
+static pjsip_retry_after_hdr*
+pjsip_retry_after_hdr_shallow_clone(pj_pool_t *pool,
+ const pjsip_retry_after_hdr *rhs)
+{
+ pjsip_retry_after_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_retry_after_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->param, &rhs->param);
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Supported header.
+ */
+PJ_DEF(pjsip_supported_hdr*) pjsip_supported_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_supported_hdr *hdr = (pjsip_supported_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+ init_hdr(hdr, PJSIP_H_SUPPORTED, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_supported_hdr*) pjsip_supported_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_supported_hdr));
+ return pjsip_supported_hdr_init(pool, mem);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Unsupported header.
+ */
+PJ_DEF(pjsip_unsupported_hdr*) pjsip_unsupported_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_unsupported_hdr *hdr = (pjsip_unsupported_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ init_hdr(hdr, PJSIP_H_UNSUPPORTED, &generic_array_hdr_vptr);
+ hdr->count = 0;
+ return hdr;
+}
+
+PJ_DEF(pjsip_unsupported_hdr*) pjsip_unsupported_hdr_create(pj_pool_t *pool)
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_unsupported_hdr));
+ return pjsip_unsupported_hdr_init(pool, mem);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Via header.
+ */
+static int pjsip_via_hdr_print( pjsip_via_hdr *hdr, char *buf, pj_size_t size);
+static pjsip_via_hdr* pjsip_via_hdr_clone( pj_pool_t *pool, const pjsip_via_hdr *hdr);
+static pjsip_via_hdr* pjsip_via_hdr_shallow_clone( pj_pool_t *pool, const pjsip_via_hdr *hdr );
+
+static pjsip_hdr_vptr via_hdr_vptr =
+{
+ (pjsip_hdr_clone_fptr) &pjsip_via_hdr_clone,
+ (pjsip_hdr_clone_fptr) &pjsip_via_hdr_shallow_clone,
+ (pjsip_hdr_print_fptr) &pjsip_via_hdr_print,
+};
+
+PJ_DEF(pjsip_via_hdr*) pjsip_via_hdr_init( pj_pool_t *pool,
+ void *mem )
+{
+ pjsip_via_hdr *hdr = (pjsip_via_hdr*) mem;
+
+ PJ_UNUSED_ARG(pool);
+
+ pj_bzero(mem, sizeof(pjsip_via_hdr));
+ init_hdr(hdr, PJSIP_H_VIA, &via_hdr_vptr);
+ hdr->ttl_param = -1;
+ hdr->rport_param = -1;
+ pj_list_init(&hdr->other_param);
+ return hdr;
+
+}
+
+PJ_DEF(pjsip_via_hdr*) pjsip_via_hdr_create( pj_pool_t *pool )
+{
+ void *mem = pj_pool_alloc(pool, sizeof(pjsip_via_hdr));
+ return pjsip_via_hdr_init(pool, mem);
+}
+
+static int pjsip_via_hdr_print( pjsip_via_hdr *hdr,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ pj_str_t sip_ver = { "SIP/2.0/", 8 };
+ const pj_str_t *hname = pjsip_use_compact_form? &hdr->sname : &hdr->name;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ if ((pj_ssize_t)size < hname->slen + sip_ver.slen +
+ hdr->transport.slen + hdr->sent_by.host.slen + 12)
+ {
+ return -1;
+ }
+
+ /* pjsip_hdr_names */
+ copy_advance(buf, (*hname));
+ *buf++ = ':';
+ *buf++ = ' ';
+
+ /* SIP/2.0/transport host:port */
+ pj_memcpy(buf, sip_ver.ptr, sip_ver.slen);
+ buf += sip_ver.slen;
+ //pj_memcpy(buf, hdr->transport.ptr, hdr->transport.slen);
+ /* Convert transport type to UPPERCASE (some endpoints want that) */
+ {
+ int i;
+ for (i=0; i<hdr->transport.slen; ++i) {
+ buf[i] = (char)pj_toupper(hdr->transport.ptr[i]);
+ }
+ }
+ buf += hdr->transport.slen;
+ *buf++ = ' ';
+
+ /* Check if host contains IPv6 */
+ if (pj_memchr(hdr->sent_by.host.ptr, ':', hdr->sent_by.host.slen)) {
+ copy_advance_pair_quote_cond(buf, "", 0, hdr->sent_by.host, '[', ']');
+ } else {
+ copy_advance_check(buf, hdr->sent_by.host);
+ }
+
+ if (hdr->sent_by.port != 0) {
+ *buf++ = ':';
+ printed = pj_utoa(hdr->sent_by.port, buf);
+ buf += printed;
+ }
+
+ if (hdr->ttl_param >= 0) {
+ size = endbuf-buf;
+ if (size < 14)
+ return -1;
+ pj_memcpy(buf, ";ttl=", 5);
+ printed = pj_utoa(hdr->ttl_param, buf+5);
+ buf += printed + 5;
+ }
+
+ if (hdr->rport_param >= 0) {
+ size = endbuf-buf;
+ if (size < 14)
+ return -1;
+ pj_memcpy(buf, ";rport", 6);
+ buf += 6;
+ if (hdr->rport_param > 0) {
+ *buf++ = '=';
+ buf += pj_utoa(hdr->rport_param, buf);
+ }
+ }
+
+
+ if (hdr->maddr_param.slen) {
+ /* Detect IPv6 IP address */
+ if (pj_memchr(hdr->maddr_param.ptr, ':', hdr->maddr_param.slen)) {
+ copy_advance_pair_quote_cond(buf, ";maddr=", 7, hdr->maddr_param,
+ '[', ']');
+ } else {
+ copy_advance_pair(buf, ";maddr=", 7, hdr->maddr_param);
+ }
+ }
+
+ copy_advance_pair(buf, ";received=", 10, hdr->recvd_param);
+ copy_advance_pair_escape(buf, ";branch=", 8, hdr->branch_param,
+ pc->pjsip_TOKEN_SPEC);
+
+ printed = pjsip_param_print_on(&hdr->other_param, buf, endbuf-buf,
+ &pc->pjsip_TOKEN_SPEC,
+ &pc->pjsip_TOKEN_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return buf-startbuf;
+}
+
+static pjsip_via_hdr* pjsip_via_hdr_clone( pj_pool_t *pool,
+ const pjsip_via_hdr *rhs)
+{
+ pjsip_via_hdr *hdr = pjsip_via_hdr_create(pool);
+ pj_strdup(pool, &hdr->transport, &rhs->transport);
+ pj_strdup(pool, &hdr->sent_by.host, &rhs->sent_by.host);
+ hdr->sent_by.port = rhs->sent_by.port;
+ hdr->ttl_param = rhs->ttl_param;
+ hdr->rport_param = rhs->rport_param;
+ pj_strdup(pool, &hdr->maddr_param, &rhs->maddr_param);
+ pj_strdup(pool, &hdr->recvd_param, &rhs->recvd_param);
+ pj_strdup(pool, &hdr->branch_param, &rhs->branch_param);
+ pjsip_param_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+static pjsip_via_hdr* pjsip_via_hdr_shallow_clone( pj_pool_t *pool,
+ const pjsip_via_hdr *rhs )
+{
+ pjsip_via_hdr *hdr = PJ_POOL_ALLOC_T(pool, pjsip_via_hdr);
+ pj_memcpy(hdr, rhs, sizeof(*hdr));
+ pjsip_param_shallow_clone(pool, &hdr->other_param, &rhs->other_param);
+ return hdr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Warning header.
+ */
+PJ_DEF(pjsip_warning_hdr*) pjsip_warning_hdr_create( pj_pool_t *pool,
+ int code,
+ const pj_str_t *host,
+ const pj_str_t *text)
+{
+ const pj_str_t str_warning = { "Warning", 7 };
+ pj_str_t hvalue;
+
+ hvalue.ptr = (char*) pj_pool_alloc(pool, 10 + /* code */
+ host->slen + 2 + /* host */
+ text->slen + 2); /* text */
+ hvalue.slen = pj_ansi_sprintf(hvalue.ptr, "%u %.*s \"%.*s\"",
+ code, (int)host->slen, host->ptr,
+ (int)text->slen, text->ptr);
+
+ return pjsip_generic_string_hdr_create(pool, &str_warning, &hvalue);
+}
+
+PJ_DEF(pjsip_warning_hdr*) pjsip_warning_hdr_create_from_status(pj_pool_t *pool,
+ const pj_str_t *host,
+ pj_status_t status)
+{
+ char errbuf[PJ_ERR_MSG_SIZE];
+ pj_str_t text;
+
+ text = pj_strerror(status, errbuf, sizeof(errbuf));
+ return pjsip_warning_hdr_create(pool, 399, host, &text);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/*
+ * Message body manipulations.
+ */
+PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size)
+{
+ if (size < msg_body->len)
+ return -1;
+ pj_memcpy(buf, msg_body->data, msg_body->len);
+ return msg_body->len;
+}
+
+PJ_DEF(void*) pjsip_clone_text_data( pj_pool_t *pool, const void *data,
+ unsigned len)
+{
+ char *newdata = "";
+
+ if (len) {
+ newdata = (char*) pj_pool_alloc(pool, len);
+ pj_memcpy(newdata, data, len);
+ }
+ return newdata;
+}
+
+PJ_DEF(pj_status_t) pjsip_msg_body_copy( pj_pool_t *pool,
+ pjsip_msg_body *dst_body,
+ const pjsip_msg_body *src_body )
+{
+ /* First check if clone_data field is initialized. */
+ PJ_ASSERT_RETURN( src_body->clone_data!=NULL, PJ_EINVAL );
+
+ /* Duplicate content-type */
+ pjsip_media_type_cp(pool, &dst_body->content_type,
+ &src_body->content_type);
+
+ /* Duplicate data. */
+ dst_body->data = (*src_body->clone_data)(pool, src_body->data,
+ src_body->len );
+
+ /* Length. */
+ dst_body->len = src_body->len;
+
+ /* Function pointers. */
+ dst_body->print_body = src_body->print_body;
+ dst_body->clone_data = src_body->clone_data;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pjsip_msg_body*) pjsip_msg_body_clone( pj_pool_t *pool,
+ const pjsip_msg_body *body )
+{
+ pjsip_msg_body *new_body;
+ pj_status_t status;
+
+ new_body = PJ_POOL_ALLOC_T(pool, pjsip_msg_body);
+ PJ_ASSERT_RETURN(new_body, NULL);
+
+ status = pjsip_msg_body_copy(pool, new_body, body);
+
+ return (status==PJ_SUCCESS) ? new_body : NULL;
+}
+
+
+PJ_DEF(pjsip_msg_body*) pjsip_msg_body_create( pj_pool_t *pool,
+ const pj_str_t *type,
+ const pj_str_t *subtype,
+ const pj_str_t *text )
+{
+ pjsip_msg_body *body;
+
+ PJ_ASSERT_RETURN(pool && type && subtype && text, NULL);
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ PJ_ASSERT_RETURN(body != NULL, NULL);
+
+ pj_strdup(pool, &body->content_type.type, type);
+ pj_strdup(pool, &body->content_type.subtype, subtype);
+ pj_list_init(&body->content_type.param);
+
+ body->data = pj_pool_alloc(pool, text->slen);
+ pj_memcpy(body->data, text->ptr, text->slen);
+ body->len = text->slen;
+
+ body->clone_data = &pjsip_clone_text_data;
+ body->print_body = &pjsip_print_text_body;
+
+ return body;
+}
+
diff --git a/pjsip/src/pjsip/sip_multipart.c b/pjsip/src/pjsip/sip_multipart.c
new file mode 100644
index 0000000..8fa9d75
--- /dev/null
+++ b/pjsip/src/pjsip/sip_multipart.c
@@ -0,0 +1,658 @@
+/* $Id: sip_multipart.c 3841 2011-10-24 09:28:13Z ming $ */
+/*
+ * 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 <pjsip/sip_multipart.h>
+#include <pjsip/sip_parser.h>
+#include <pjlib-util/scanner.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/errno.h>
+#include <pj/except.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "sip_multipart.c"
+
+#define IS_SPACE(c) ((c)==' ' || (c)=='\t')
+
+#if 0
+# define TRACE_(x) PJ_LOG(4,x)
+#else
+# define TRACE_(x)
+#endif
+
+extern pj_bool_t pjsip_use_compact_form;
+
+/* Type of "data" in multipart pjsip_msg_body */
+struct multipart_data
+{
+ pj_str_t boundary;
+ pjsip_multipart_part part_head;
+};
+
+
+static int multipart_print_body(struct pjsip_msg_body *msg_body,
+ char *buf, pj_size_t size)
+{
+ const struct multipart_data *m_data;
+ pj_str_t clen_hdr = { "Content-Length: ", 16};
+ pjsip_multipart_part *part;
+ char *p = buf, *end = buf+size;
+
+#define SIZE_LEFT() (end-p)
+
+ m_data = (const struct multipart_data*)msg_body->data;
+
+ PJ_ASSERT_RETURN(m_data && !pj_list_empty(&m_data->part_head), PJ_EINVAL);
+
+ part = m_data->part_head.next;
+ while (part != &m_data->part_head) {
+ enum { CLEN_SPACE = 5 };
+ char *clen_pos;
+ const pjsip_hdr *hdr;
+
+ clen_pos = NULL;
+
+ /* Print delimiter */
+ if (SIZE_LEFT() <= (m_data->boundary.slen+8) << 1)
+ return -1;
+ *p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-';
+ pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen);
+ p += m_data->boundary.slen;
+ *p++ = 13; *p++ = 10;
+
+ /* Print optional headers */
+ hdr = part->hdr.next;
+ while (hdr != &part->hdr) {
+ int printed = pjsip_hdr_print_on((pjsip_hdr*)hdr, p,
+ SIZE_LEFT()-2);
+ if (printed < 0)
+ return -1;
+ p += printed;
+ *p++ = '\r';
+ *p++ = '\n';
+ hdr = hdr->next;
+ }
+
+ /* Automaticly adds Content-Type and Content-Length headers, only
+ * if content_type is set in the message body.
+ */
+ if (part->body && part->body->content_type.type.slen) {
+ pj_str_t ctype_hdr = { "Content-Type: ", 14};
+ const pjsip_media_type *media = &part->body->content_type;
+
+ if (pjsip_use_compact_form) {
+ ctype_hdr.ptr = "c: ";
+ ctype_hdr.slen = 3;
+ }
+
+ /* Add Content-Type header. */
+ if ( (end-p) < 24 + media->type.slen + media->subtype.slen) {
+ return -1;
+ }
+ pj_memcpy(p, ctype_hdr.ptr, ctype_hdr.slen);
+ p += ctype_hdr.slen;
+ p += pjsip_media_type_print(p, end-p, media);
+ *p++ = '\r';
+ *p++ = '\n';
+
+ /* Add Content-Length header. */
+ if ((end-p) < clen_hdr.slen + 12 + 2) {
+ return -1;
+ }
+ pj_memcpy(p, clen_hdr.ptr, clen_hdr.slen);
+ p += clen_hdr.slen;
+
+ /* Print blanks after "Content-Length:", this is where we'll put
+ * the content length value after we know the length of the
+ * body.
+ */
+ pj_memset(p, ' ', CLEN_SPACE);
+ clen_pos = p;
+ p += CLEN_SPACE;
+ *p++ = '\r';
+ *p++ = '\n';
+ }
+
+ /* Empty newline */
+ *p++ = 13; *p++ = 10;
+
+ /* Print the body */
+ pj_assert(part->body != NULL);
+ if (part->body) {
+ int printed = part->body->print_body(part->body, p, SIZE_LEFT());
+ if (printed < 0)
+ return -1;
+ p += printed;
+
+ /* Now that we have the length of the body, print this to the
+ * Content-Length header.
+ */
+ if (clen_pos) {
+ char tmp[16];
+ int len;
+
+ len = pj_utoa(printed, tmp);
+ if (len > CLEN_SPACE) len = CLEN_SPACE;
+ pj_memcpy(clen_pos+CLEN_SPACE-len, tmp, len);
+ }
+ }
+
+ part = part->next;
+ }
+
+ /* Print closing delimiter */
+ if (SIZE_LEFT() < m_data->boundary.slen+8)
+ return -1;
+ *p++ = 13; *p++ = 10; *p++ = '-'; *p++ = '-';
+ pj_memcpy(p, m_data->boundary.ptr, m_data->boundary.slen);
+ p += m_data->boundary.slen;
+ *p++ = '-'; *p++ = '-'; *p++ = 13; *p++ = 10;
+
+#undef SIZE_LEFT
+
+ return p - buf;
+}
+
+static void* multipart_clone_data(pj_pool_t *pool, const void *data,
+ unsigned len)
+{
+ const struct multipart_data *src;
+ struct multipart_data *dst;
+ const pjsip_multipart_part *src_part;
+
+ PJ_UNUSED_ARG(len);
+
+ src = (const struct multipart_data*) data;
+ dst = PJ_POOL_ALLOC_T(pool, struct multipart_data);
+
+ pj_strdup(pool, &dst->boundary, &src->boundary);
+
+ src_part = src->part_head.next;
+ while (src_part != &src->part_head) {
+ pjsip_multipart_part *dst_part;
+ const pjsip_hdr *src_hdr;
+
+ dst_part = pjsip_multipart_create_part(pool);
+
+ src_hdr = src_part->hdr.next;
+ while (src_hdr != &src_part->hdr) {
+ pjsip_hdr *dst_hdr = (pjsip_hdr*)pjsip_hdr_clone(pool, src_hdr);
+ pj_list_push_back(&dst_part->hdr, dst_hdr);
+ src_hdr = src_hdr->next;
+ }
+
+ dst_part->body = pjsip_msg_body_clone(pool, src_part->body);
+
+ pj_list_push_back(&dst->part_head, dst_part);
+
+ src_part = src_part->next;
+ }
+
+ return (void*)dst;
+}
+
+/*
+ * Create an empty multipart body.
+ */
+PJ_DEF(pjsip_msg_body*) pjsip_multipart_create( pj_pool_t *pool,
+ const pjsip_media_type *ctype,
+ const pj_str_t *boundary)
+{
+ pjsip_msg_body *body;
+ pjsip_param *ctype_param;
+ struct multipart_data *mp_data;
+ pj_str_t STR_BOUNDARY = { "boundary", 8 };
+
+ PJ_ASSERT_RETURN(pool, NULL);
+
+ body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+
+ /* content-type */
+ if (ctype && ctype->type.slen) {
+ pjsip_media_type_cp(pool, &body->content_type, ctype);
+ } else {
+ pj_str_t STR_MULTIPART = {"multipart", 9};
+ pj_str_t STR_MIXED = { "mixed", 5 };
+
+ pjsip_media_type_init(&body->content_type,
+ &STR_MULTIPART, &STR_MIXED);
+ }
+
+ /* multipart data */
+ mp_data = PJ_POOL_ZALLOC_T(pool, struct multipart_data);
+ pj_list_init(&mp_data->part_head);
+ if (boundary) {
+ pj_strdup(pool, &mp_data->boundary, boundary);
+ } else {
+ pj_create_unique_string(pool, &mp_data->boundary);
+ }
+ body->data = mp_data;
+
+ /* Add ";boundary" parameter to content_type parameter. */
+ ctype_param = pjsip_param_find(&body->content_type.param, &STR_BOUNDARY);
+ if (!ctype_param) {
+ ctype_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ ctype_param->name = STR_BOUNDARY;
+ pj_list_push_back(&body->content_type.param, ctype_param);
+ }
+ ctype_param->value = mp_data->boundary;
+
+ /* function pointers */
+ body->print_body = &multipart_print_body;
+ body->clone_data = &multipart_clone_data;
+
+ return body;
+}
+
+/*
+ * Create an empty multipart part.
+ */
+PJ_DEF(pjsip_multipart_part*) pjsip_multipart_create_part(pj_pool_t *pool)
+{
+ pjsip_multipart_part *mp;
+
+ mp = PJ_POOL_ZALLOC_T(pool, pjsip_multipart_part);
+ pj_list_init(&mp->hdr);
+
+ return mp;
+}
+
+
+/*
+ * Deep clone.
+ */
+PJ_DEF(pjsip_multipart_part*)
+pjsip_multipart_clone_part(pj_pool_t *pool,
+ const pjsip_multipart_part *src)
+{
+ pjsip_multipart_part *dst;
+ const pjsip_hdr *hdr;
+
+ dst = pjsip_multipart_create_part(pool);
+
+ hdr = src->hdr.next;
+ while (hdr != &src->hdr) {
+ pj_list_push_back(&dst->hdr, pjsip_hdr_clone(pool, hdr));
+ hdr = hdr->next;
+ }
+
+ dst->body = pjsip_msg_body_clone(pool, src->body);
+
+ return dst;
+}
+
+
+/*
+ * Add a part into multipart bodies.
+ */
+PJ_DEF(pj_status_t) pjsip_multipart_add_part( pj_pool_t *pool,
+ pjsip_msg_body *mp,
+ pjsip_multipart_part *part)
+{
+ struct multipart_data *m_data;
+
+ /* All params must be specified */
+ PJ_ASSERT_RETURN(pool && mp && part, PJ_EINVAL);
+
+ /* mp must really point to an actual multipart msg body */
+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, PJ_EINVAL);
+
+ /* The multipart part must contain a valid message body */
+ PJ_ASSERT_RETURN(part->body && part->body->print_body, PJ_EINVAL);
+
+ m_data = (struct multipart_data*)mp->data;
+ pj_list_push_back(&m_data->part_head, part);
+
+ PJ_UNUSED_ARG(pool);
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Get the first part of multipart bodies.
+ */
+PJ_DEF(pjsip_multipart_part*)
+pjsip_multipart_get_first_part(const pjsip_msg_body *mp)
+{
+ struct multipart_data *m_data;
+
+ /* Must specify mandatory params */
+ PJ_ASSERT_RETURN(mp, NULL);
+
+ /* mp must really point to an actual multipart msg body */
+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
+
+ m_data = (struct multipart_data*)mp->data;
+ if (pj_list_empty(&m_data->part_head))
+ return NULL;
+
+ return m_data->part_head.next;
+}
+
+/*
+ * Get the next part after the specified part.
+ */
+PJ_DEF(pjsip_multipart_part*)
+pjsip_multipart_get_next_part(const pjsip_msg_body *mp,
+ pjsip_multipart_part *part)
+{
+ struct multipart_data *m_data;
+
+ /* Must specify mandatory params */
+ PJ_ASSERT_RETURN(mp && part, NULL);
+
+ /* mp must really point to an actual multipart msg body */
+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
+
+ m_data = (struct multipart_data*)mp->data;
+
+ /* the part parameter must be really member of the list */
+ PJ_ASSERT_RETURN(pj_list_find_node(&m_data->part_head, part) != NULL,
+ NULL);
+
+ if (part->next == &m_data->part_head)
+ return NULL;
+
+ return part->next;
+}
+
+/*
+ * Find a body inside multipart bodies which has the specified content type.
+ */
+PJ_DEF(pjsip_multipart_part*)
+pjsip_multipart_find_part( const pjsip_msg_body *mp,
+ const pjsip_media_type *content_type,
+ const pjsip_multipart_part *start)
+{
+ struct multipart_data *m_data;
+ pjsip_multipart_part *part;
+
+ /* Must specify mandatory params */
+ PJ_ASSERT_RETURN(mp && content_type, NULL);
+
+ /* mp must really point to an actual multipart msg body */
+ PJ_ASSERT_RETURN(mp->print_body==&multipart_print_body, NULL);
+
+ m_data = (struct multipart_data*)mp->data;
+
+ if (start)
+ part = start->next;
+ else
+ part = m_data->part_head.next;
+
+ while (part != &m_data->part_head) {
+ if (pjsip_media_type_cmp(&part->body->content_type,
+ content_type, 0)==0)
+ {
+ return part;
+ }
+ part = part->next;
+ }
+
+ return NULL;
+}
+
+/* Parse a multipart part. "pct" is parent content-type */
+static pjsip_multipart_part *parse_multipart_part(pj_pool_t *pool,
+ char *start,
+ pj_size_t len,
+ const pjsip_media_type *pct)
+{
+ pjsip_multipart_part *part = pjsip_multipart_create_part(pool);
+ char *p = start, *end = start+len, *end_hdr = NULL, *start_body = NULL;
+ pjsip_ctype_hdr *ctype_hdr = NULL;
+
+ TRACE_((THIS_FILE, "Parsing part: begin--\n%.*s\n--end",
+ (int)len, start));
+
+ /* Find the end of header area, by looking at an empty line */
+ for (;;) {
+ while (p!=end && *p!='\n') ++p;
+ if (p==end) {
+ start_body = end;
+ break;
+ }
+ if ((p==start) || (p==start+1 && *(p-1)=='\r')) {
+ /* Empty header section */
+ end_hdr = start;
+ start_body = ++p;
+ break;
+ } else if (p==end-1) {
+ /* Empty body section */
+ end_hdr = end;
+ start_body = ++p;
+ } else if ((p>=start+1 && *(p-1)=='\n') ||
+ (p>=start+2 && *(p-1)=='\r' && *(p-2)=='\n'))
+ {
+ /* Found it */
+ end_hdr = (*(p-1)=='\r') ? (p-1) : p;
+ start_body = ++p;
+ break;
+ } else {
+ ++p;
+ }
+ }
+
+ /* Parse the headers */
+ if (end_hdr-start > 0) {
+ pjsip_hdr *hdr;
+ pj_status_t status;
+
+ status = pjsip_parse_headers(pool, start, end_hdr-start,
+ &part->hdr, 0);
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(2,(THIS_FILE, status, "Warning: error parsing multipart"
+ " header"));
+ }
+
+ /* Find Content-Type header */
+ hdr = part->hdr.next;
+ while (hdr != &part->hdr) {
+ TRACE_((THIS_FILE, "Header parsed: %.*s", (int)hdr->name.slen,
+ hdr->name.ptr));
+ if (hdr->type == PJSIP_H_CONTENT_TYPE) {
+ ctype_hdr = (pjsip_ctype_hdr*)hdr;
+ }
+ hdr = hdr->next;
+ }
+ }
+
+ /* Assign the body */
+ part->body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
+ if (ctype_hdr) {
+ pjsip_media_type_cp(pool, &part->body->content_type, &ctype_hdr->media);
+ } else if (pct && pj_stricmp2(&pct->subtype, "digest")==0) {
+ part->body->content_type.type = pj_str("message");
+ part->body->content_type.subtype = pj_str("rfc822");
+ } else {
+ part->body->content_type.type = pj_str("text");
+ part->body->content_type.subtype = pj_str("plain");
+ }
+
+ if (start_body < end) {
+ part->body->data = start_body;
+ part->body->len = end - start_body;
+ } else {
+ part->body->data = (void*)"";
+ part->body->len = 0;
+ }
+ TRACE_((THIS_FILE, "Body parsed: \"%.*s\"", (int)part->body->len,
+ part->body->data));
+ part->body->print_body = &pjsip_print_text_body;
+ part->body->clone_data = &pjsip_clone_text_data;
+
+ return part;
+}
+
+/* Public function to parse multipart message bodies into its parts */
+PJ_DEF(pjsip_msg_body*) pjsip_multipart_parse(pj_pool_t *pool,
+ char *buf, pj_size_t len,
+ const pjsip_media_type *ctype,
+ unsigned options)
+{
+ pj_str_t boundary, delim;
+ char *curptr, *endptr;
+ const pjsip_param *ctype_param;
+ const pj_str_t STR_BOUNDARY = { "boundary", 8 };
+ pjsip_msg_body *body = NULL;
+
+ PJ_ASSERT_RETURN(pool && buf && len && ctype && !options, NULL);
+
+ TRACE_((THIS_FILE, "Started parsing multipart body"));
+
+ /* Get the boundary value in the ctype */
+ boundary.ptr = NULL;
+ boundary.slen = 0;
+ ctype_param = pjsip_param_find(&ctype->param, &STR_BOUNDARY);
+ if (ctype_param) {
+ boundary = ctype_param->value;
+ if (boundary.slen>2 && *boundary.ptr=='"') {
+ /* Remove quote */
+ boundary.ptr++;
+ boundary.slen -= 2;
+ }
+ TRACE_((THIS_FILE, "Boundary is specified: '%.*s'", (int)boundary.slen,
+ boundary.ptr));
+ }
+
+ if (!boundary.slen) {
+ /* Boundary not found or not specified. Try to be clever, get
+ * the boundary from the body.
+ */
+ char *p=buf, *end=buf+len;
+
+ PJ_LOG(4,(THIS_FILE, "Warning: boundary parameter not found or "
+ "not specified when parsing multipart body"));
+
+ /* Find the first "--". This "--" must be right after a CRLF, unless
+ * it really appears at the start of the buffer.
+ */
+ for (;;) {
+ while (p!=end && *p!='-') ++p;
+ if (p!=end && *(p+1)=='-' &&
+ ((p>buf && *(p-1)=='\n') || (p==buf)))
+ {
+ p+=2;
+ break;
+ } else {
+ ++p;
+ }
+ }
+
+ if (p==end) {
+ /* Unable to determine boundary. Maybe this is not a multipart
+ * message?
+ */
+ PJ_LOG(4,(THIS_FILE, "Error: multipart boundary not specified and"
+ " unable to calculate from the body"));
+ return NULL;
+ }
+
+ boundary.ptr = p;
+ while (p!=end && !pj_isspace(*p)) ++p;
+ boundary.slen = p - boundary.ptr;
+
+ TRACE_((THIS_FILE, "Boundary is calculated: '%.*s'",
+ (int)boundary.slen, boundary.ptr));
+ }
+
+ /* Build the delimiter:
+ * delimiter = "--" boundary
+ */
+ delim.slen = boundary.slen+2;
+ delim.ptr = (char*)pj_pool_alloc(pool, (int)delim.slen);
+ delim.ptr[0] = '-';
+ delim.ptr[1] = '-';
+ pj_memcpy(delim.ptr+2, boundary.ptr, boundary.slen);
+
+ /* Start parsing the body, skip until the first delimiter. */
+ curptr = buf;
+ endptr = buf + len;
+ {
+ pj_str_t body;
+
+ body.ptr = buf; body.slen = len;
+ curptr = pj_strstr(&body, &delim);
+ if (!curptr)
+ return NULL;
+ }
+
+ body = pjsip_multipart_create(pool, ctype, &boundary);
+
+ for (;;) {
+ char *start_body, *end_body;
+ pjsip_multipart_part *part;
+
+ /* Eat the boundary */
+ curptr += delim.slen;
+ if (*curptr=='-' && curptr<endptr-1 && *(curptr+1)=='-') {
+ /* Found the closing delimiter */
+ curptr += 2;
+ break;
+ }
+ /* Optional whitespace after delimiter */
+ while (curptr!=endptr && IS_SPACE(*curptr)) ++curptr;
+ /* Mandatory CRLF */
+ if (*curptr=='\r') ++curptr;
+ if (*curptr!='\n') {
+ /* Expecting a newline here */
+ return NULL;
+ }
+ ++curptr;
+
+ /* We now in the start of the body */
+ start_body = curptr;
+
+ /* Find the next delimiter */
+ {
+ pj_str_t subbody;
+
+ subbody.ptr = curptr; subbody.slen = endptr - curptr;
+ curptr = pj_strstr(&subbody, &delim);
+ if (!curptr) {
+ /* We're really expecting end delimiter to be found. */
+ return NULL;
+ }
+ }
+
+ end_body = curptr;
+
+ /* The newline preceeding the delimiter is conceptually part of
+ * the delimiter, so trim it from the body.
+ */
+ if (*(end_body-1) == '\n')
+ --end_body;
+ if (*(end_body-1) == '\r')
+ --end_body;
+
+ /* Now that we have determined the part's boundary, parse it
+ * to get the header and body part of the part.
+ */
+ part = parse_multipart_part(pool, start_body, end_body - start_body,
+ ctype);
+ if (part) {
+ pjsip_multipart_add_part(pool, body, part);
+ }
+ }
+
+ return body;
+}
+
diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c
new file mode 100644
index 0000000..2d46fe9
--- /dev/null
+++ b/pjsip/src/pjsip/sip_parser.c
@@ -0,0 +1,2379 @@
+/* $Id: sip_parser.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 <pjsip/sip_parser.h>
+#include <pjsip/sip_uri.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_multipart.h>
+#include <pjsip/sip_auth_parser.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_transport.h> /* rdata structure */
+#include <pjlib-util/scanner.h>
+#include <pjlib-util/string.h>
+#include <pj/except.h>
+#include <pj/log.h>
+#include <pj/hash.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/ctype.h>
+#include <pj/assert.h>
+
+#define THIS_FILE "sip_parser.c"
+
+#define ALNUM
+#define RESERVED ";/?:@&=+$,"
+#define MARK "-_.!~*'()"
+#define UNRESERVED ALNUM MARK
+#define ESCAPED "%"
+#define USER_UNRESERVED "&=+$,;?/"
+#define PASS "&=+$,"
+#define TOKEN "-.!%*_`'~+" /* '=' was removed for parsing
+ * param */
+#define HOST "_-."
+#define HEX_DIGIT "abcdefABCDEF"
+#define PARAM_CHAR "[]/:&+$" UNRESERVED ESCAPED
+#define HNV_UNRESERVED "[]/?:+$"
+#define HDR_CHAR HNV_UNRESERVED UNRESERVED ESCAPED
+
+/* A generic URI can consist of (For a complete BNF see RFC 2396):
+ #?;:@&=+-_.!~*'()%$,/
+ */
+#define GENERIC_URI_CHARS "#?;:@&=+-_.!~*'()%$,/" "%"
+
+#define UNREACHED(expr)
+
+#define IS_NEWLINE(c) ((c)=='\r' || (c)=='\n')
+#define IS_SPACE(c) ((c)==' ' || (c)=='\t')
+
+/*
+ * Header parser records.
+ */
+typedef struct handler_rec
+{
+ char hname[PJSIP_MAX_HNAME_LEN+1];
+ pj_size_t hname_len;
+ pj_uint32_t hname_hash;
+ pjsip_parse_hdr_func *handler;
+} handler_rec;
+
+static handler_rec handler[PJSIP_MAX_HEADER_TYPES];
+static unsigned handler_count;
+static int parser_is_initialized;
+
+/*
+ * URI parser records.
+ */
+typedef struct uri_parser_rec
+{
+ pj_str_t scheme;
+ pjsip_parse_uri_func *parse;
+} uri_parser_rec;
+
+static uri_parser_rec uri_handler[PJSIP_MAX_URI_TYPES];
+static unsigned uri_handler_count;
+
+/*
+ * Global vars (also extern).
+ */
+int PJSIP_SYN_ERR_EXCEPTION = -1;
+
+/* Parser constants */
+static pjsip_parser_const_t pconst =
+{
+ { "user", 4}, /* pjsip_USER_STR */
+ { "method", 6}, /* pjsip_METHOD_STR */
+ { "transport", 9}, /* pjsip_TRANSPORT_STR */
+ { "maddr", 5 }, /* pjsip_MADDR_STR */
+ { "lr", 2 }, /* pjsip_LR_STR */
+ { "sip", 3 }, /* pjsip_SIP_STR */
+ { "sips", 4 }, /* pjsip_SIPS_STR */
+ { "tel", 3 }, /* pjsip_TEL_STR */
+ { "branch", 6 }, /* pjsip_BRANCH_STR */
+ { "ttl", 3 }, /* pjsip_TTL_STR */
+ { "received", 8 }, /* pjsip_RECEIVED_STR */
+ { "q", 1 }, /* pjsip_Q_STR */
+ { "expires", 7 }, /* pjsip_EXPIRES_STR */
+ { "tag", 3 }, /* pjsip_TAG_STR */
+ { "rport", 5} /* pjsip_RPORT_STR */
+};
+
+/* Character Input Specification buffer. */
+static pj_cis_buf_t cis_buf;
+
+
+/*
+ * Forward decl.
+ */
+static pjsip_msg * int_parse_msg( pjsip_parse_ctx *ctx,
+ pjsip_parser_err_report *err_list);
+static void int_parse_param( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_str_t *pname,
+ pj_str_t *pvalue,
+ unsigned option);
+static void int_parse_uri_param( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_str_t *pname,
+ pj_str_t *pvalue,
+ unsigned option);
+static void int_parse_hparam( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_str_t *hname,
+ pj_str_t *hvalue );
+static void int_parse_req_line( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pjsip_request_line *req_line);
+static int int_is_next_user( pj_scanner *scanner);
+static void int_parse_status_line( pj_scanner *scanner,
+ pjsip_status_line *line);
+static void int_parse_user_pass( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_str_t *user,
+ pj_str_t *pass);
+static void int_parse_uri_host_port( pj_scanner *scanner,
+ pj_str_t *p_host,
+ int *p_port);
+static pjsip_uri * int_parse_uri_or_name_addr( pj_scanner *scanner,
+ pj_pool_t *pool,
+ unsigned option);
+static void* int_parse_sip_url( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_bool_t parse_params);
+static pjsip_name_addr *
+ int_parse_name_addr( pj_scanner *scanner,
+ pj_pool_t *pool );
+static void* int_parse_other_uri(pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_bool_t parse_params);
+static void parse_hdr_end( pj_scanner *scanner );
+
+static pjsip_hdr* parse_hdr_accept( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_allow( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_call_id( pjsip_parse_ctx *ctx);
+static pjsip_hdr* parse_hdr_contact( pjsip_parse_ctx *ctx);
+static pjsip_hdr* parse_hdr_content_len( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_content_type( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_cseq( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_expires( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_from( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_max_forwards( pjsip_parse_ctx *ctx);
+static pjsip_hdr* parse_hdr_min_expires( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_rr( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_route( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_require( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_retry_after( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_supported( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_to( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_unsupported( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_via( pjsip_parse_ctx *ctx );
+static pjsip_hdr* parse_hdr_generic_string( pjsip_parse_ctx *ctx);
+
+/* Convert non NULL terminated string to integer. */
+static unsigned long pj_strtoul_mindigit(const pj_str_t *str,
+ unsigned mindig)
+{
+ unsigned long value;
+ unsigned i;
+
+ value = 0;
+ for (i=0; i<(unsigned)str->slen; ++i) {
+ value = value * 10 + (str->ptr[i] - '0');
+ }
+ for (; i<mindig; ++i) {
+ value = value * 10;
+ }
+ return value;
+}
+
+/* Case insensitive comparison */
+#define parser_stricmp(s1, s2) (s1.slen!=s2.slen || pj_stricmp_alnum(&s1, &s2))
+
+
+/* Get a token and unescape */
+PJ_INLINE(void) parser_get_and_unescape(pj_scanner *scanner, pj_pool_t *pool,
+ const pj_cis_t *spec,
+ const pj_cis_t *unesc_spec,
+ pj_str_t *token)
+{
+#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(spec);
+ pj_scan_get_unescape(scanner, unesc_spec, token);
+#else
+ PJ_UNUSED_ARG(unesc_spec);
+ pj_scan_get(scanner, spec, token);
+ *token = pj_str_unescape(pool, token);
+#endif
+}
+
+
+
+/* Syntax error handler for parser. */
+static void on_syntax_error(pj_scanner *scanner)
+{
+ PJ_UNUSED_ARG(scanner);
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+}
+
+/* Get parser constants. */
+PJ_DEF(const pjsip_parser_const_t*) pjsip_parser_const(void)
+{
+ return &pconst;
+}
+
+/* Concatenate unrecognized params into single string. */
+PJ_DEF(void) pjsip_concat_param_imp(pj_str_t *param, pj_pool_t *pool,
+ const pj_str_t *pname,
+ const pj_str_t *pvalue,
+ int sepchar)
+{
+ char *new_param, *p;
+ int len;
+
+ len = param->slen + pname->slen + pvalue->slen + 3;
+ p = new_param = (char*) pj_pool_alloc(pool, len);
+
+ if (param->slen) {
+ int old_len = param->slen;
+ pj_memcpy(p, param->ptr, old_len);
+ p += old_len;
+ }
+ *p++ = (char)sepchar;
+ pj_memcpy(p, pname->ptr, pname->slen);
+ p += pname->slen;
+
+ if (pvalue->slen) {
+ *p++ = '=';
+ pj_memcpy(p, pvalue->ptr, pvalue->slen);
+ p += pvalue->slen;
+ }
+
+ *p = '\0';
+
+ param->ptr = new_param;
+ param->slen = p - new_param;
+}
+
+/* Initialize static properties of the parser. */
+static pj_status_t init_parser()
+{
+ pj_status_t status;
+
+ /*
+ * Syntax error exception number.
+ */
+ pj_assert (PJSIP_SYN_ERR_EXCEPTION == -1);
+ status = pj_exception_id_alloc("PJSIP syntax error",
+ &PJSIP_SYN_ERR_EXCEPTION);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /*
+ * Init character input spec (cis)
+ */
+
+ pj_cis_buf_init(&cis_buf);
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_DIGIT_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_num(&pconst.pjsip_DIGIT_SPEC);
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_ALPHA_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_alpha( &pconst.pjsip_ALPHA_SPEC );
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_alpha( &pconst.pjsip_ALNUM_SPEC );
+ pj_cis_add_num( &pconst.pjsip_ALNUM_SPEC );
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_NOT_NEWLINE);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_NOT_NEWLINE, "\r\n");
+ pj_cis_invert(&pconst.pjsip_NOT_NEWLINE);
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_NOT_COMMA_OR_NEWLINE);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_NOT_COMMA_OR_NEWLINE, ",\r\n");
+ pj_cis_invert(&pconst.pjsip_NOT_COMMA_OR_NEWLINE);
+
+ status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_TOKEN_SPEC, TOKEN);
+
+ status = pj_cis_dup(&pconst.pjsip_TOKEN_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str(&pconst.pjsip_TOKEN_SPEC_ESC, "%");
+
+ status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC, &pconst.pjsip_TOKEN_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, ":");
+
+ status = pj_cis_dup(&pconst.pjsip_VIA_PARAM_SPEC_ESC, &pconst.pjsip_TOKEN_SPEC_ESC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_VIA_PARAM_SPEC, ":");
+
+ status = pj_cis_dup(&pconst.pjsip_HOST_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_HOST_SPEC, HOST);
+
+ status = pj_cis_dup(&pconst.pjsip_HEX_SPEC, &pconst.pjsip_DIGIT_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_HEX_SPEC, HEX_DIGIT);
+
+ status = pj_cis_dup(&pconst.pjsip_PARAM_CHAR_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_PARAM_CHAR_SPEC, PARAM_CHAR);
+
+ status = pj_cis_dup(&pconst.pjsip_PARAM_CHAR_SPEC_ESC, &pconst.pjsip_PARAM_CHAR_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str(&pconst.pjsip_PARAM_CHAR_SPEC_ESC, ESCAPED);
+
+ status = pj_cis_dup(&pconst.pjsip_HDR_CHAR_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_HDR_CHAR_SPEC, HDR_CHAR);
+
+ status = pj_cis_dup(&pconst.pjsip_HDR_CHAR_SPEC_ESC, &pconst.pjsip_HDR_CHAR_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str(&pconst.pjsip_HDR_CHAR_SPEC_ESC, ESCAPED);
+
+ status = pj_cis_dup(&pconst.pjsip_USER_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_USER_SPEC, UNRESERVED ESCAPED USER_UNRESERVED );
+
+ status = pj_cis_dup(&pconst.pjsip_USER_SPEC_ESC, &pconst.pjsip_USER_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str( &pconst.pjsip_USER_SPEC_ESC, ESCAPED);
+
+ status = pj_cis_dup(&pconst.pjsip_USER_SPEC_LENIENT, &pconst.pjsip_USER_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_USER_SPEC_LENIENT, "#");
+
+ status = pj_cis_dup(&pconst.pjsip_USER_SPEC_LENIENT_ESC, &pconst.pjsip_USER_SPEC_ESC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str(&pconst.pjsip_USER_SPEC_LENIENT_ESC, "#");
+
+ status = pj_cis_dup(&pconst.pjsip_PASSWD_SPEC, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_PASSWD_SPEC, UNRESERVED ESCAPED PASS);
+
+ status = pj_cis_dup(&pconst.pjsip_PASSWD_SPEC_ESC, &pconst.pjsip_PASSWD_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_del_str( &pconst.pjsip_PASSWD_SPEC_ESC, ESCAPED);
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_PROBE_USER_HOST_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_PROBE_USER_HOST_SPEC, "@ \n>");
+ pj_cis_invert( &pconst.pjsip_PROBE_USER_HOST_SPEC );
+
+ status = pj_cis_init(&cis_buf, &pconst.pjsip_DISPLAY_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_DISPLAY_SPEC, ":\r\n<");
+ pj_cis_invert(&pconst.pjsip_DISPLAY_SPEC);
+
+ status = pj_cis_dup(&pconst.pjsip_OTHER_URI_CONTENT, &pconst.pjsip_ALNUM_SPEC);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+ pj_cis_add_str( &pconst.pjsip_OTHER_URI_CONTENT, GENERIC_URI_CHARS);
+
+ /*
+ * Register URI parsers.
+ */
+
+ status = pjsip_register_uri_parser("sip", &int_parse_sip_url);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_uri_parser("sips", &int_parse_sip_url);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /*
+ * Register header parsers.
+ */
+
+ status = pjsip_register_hdr_parser( "Accept", NULL, &parse_hdr_accept);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Allow", NULL, &parse_hdr_allow);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Call-ID", "i", &parse_hdr_call_id);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Contact", "m", &parse_hdr_contact);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Content-Length", "l",
+ &parse_hdr_content_len);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Content-Type", "c",
+ &parse_hdr_content_type);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "CSeq", NULL, &parse_hdr_cseq);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Expires", NULL, &parse_hdr_expires);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "From", "f", &parse_hdr_from);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Max-Forwards", NULL,
+ &parse_hdr_max_forwards);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Min-Expires", NULL,
+ &parse_hdr_min_expires);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Record-Route", NULL, &parse_hdr_rr);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Route", NULL, &parse_hdr_route);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Require", NULL, &parse_hdr_require);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Retry-After", NULL,
+ &parse_hdr_retry_after);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Supported", "k",
+ &parse_hdr_supported);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "To", "t", &parse_hdr_to);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Unsupported", NULL,
+ &parse_hdr_unsupported);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ status = pjsip_register_hdr_parser( "Via", "v", &parse_hdr_via);
+ PJ_ASSERT_RETURN(status == PJ_SUCCESS, status);
+
+ /*
+ * Register auth parser.
+ */
+
+ status = pjsip_auth_init_parser();
+
+ return status;
+}
+
+void init_sip_parser(void)
+{
+ pj_enter_critical_section();
+ if (++parser_is_initialized == 1) {
+ init_parser();
+ }
+ pj_leave_critical_section();
+}
+
+void deinit_sip_parser(void)
+{
+ pj_enter_critical_section();
+ if (--parser_is_initialized == 0) {
+ /* Clear header handlers */
+ pj_bzero(handler, sizeof(handler));
+ handler_count = 0;
+
+ /* Clear URI handlers */
+ pj_bzero(uri_handler, sizeof(uri_handler));
+ uri_handler_count = 0;
+
+ /* Deregister exception ID */
+ pj_exception_id_free(PJSIP_SYN_ERR_EXCEPTION);
+ PJSIP_SYN_ERR_EXCEPTION = -1;
+ }
+ pj_leave_critical_section();
+}
+
+/* Compare the handler record with header name, and return:
+ * - 0 if handler match.
+ * - <0 if handler is 'less' than the header name.
+ * - >0 if handler is 'greater' than header name.
+ */
+PJ_INLINE(int) compare_handler( const handler_rec *r1,
+ const char *name,
+ pj_size_t name_len,
+ pj_uint32_t hash )
+{
+ PJ_UNUSED_ARG(name_len);
+
+ /* Compare hashed value. */
+ if (r1->hname_hash < hash)
+ return -1;
+ if (r1->hname_hash > hash)
+ return 1;
+
+ /* Compare length. */
+ /*
+ if (r1->hname_len < name_len)
+ return -1;
+ if (r1->hname_len > name_len)
+ return 1;
+ */
+
+ /* Equal length and equal hash. compare the strings. */
+ return pj_memcmp(r1->hname, name, name_len);
+}
+
+/* Register one handler for one header name. */
+static pj_status_t int_register_parser( const char *name,
+ pjsip_parse_hdr_func *fptr )
+{
+ unsigned pos;
+ handler_rec rec;
+
+ if (handler_count >= PJ_ARRAY_SIZE(handler)) {
+ pj_assert(!"Too many handlers!");
+ return PJ_ETOOMANY;
+ }
+
+ /* Initialize temporary handler. */
+ rec.handler = fptr;
+ rec.hname_len = strlen(name);
+ if (rec.hname_len >= sizeof(rec.hname)) {
+ pj_assert(!"Header name is too long!");
+ return PJ_ENAMETOOLONG;
+ }
+ /* Copy name. */
+ pj_memcpy(rec.hname, name, rec.hname_len);
+ rec.hname[rec.hname_len] = '\0';
+
+ /* Calculate hash value. */
+ rec.hname_hash = pj_hash_calc(0, rec.hname, rec.hname_len);
+
+ /* Get the pos to insert the new handler. */
+ for (pos=0; pos < handler_count; ++pos) {
+ int d;
+ d = compare_handler(&handler[pos], rec.hname, rec.hname_len,
+ rec.hname_hash);
+ if (d == 0) {
+ pj_assert(0);
+ return PJ_EEXISTS;
+ }
+ if (d > 0) {
+ break;
+ }
+ }
+
+ /* Shift handlers. */
+ if (pos != handler_count) {
+ pj_memmove( &handler[pos+1], &handler[pos],
+ (handler_count-pos)*sizeof(handler_rec));
+ }
+ /* Add new handler. */
+ pj_memcpy( &handler[pos], &rec, sizeof(handler_rec));
+ ++handler_count;
+
+ return PJ_SUCCESS;
+}
+
+/* Register parser handler. If both header name and short name are valid,
+ * then two instances of handler will be registered.
+ */
+PJ_DEF(pj_status_t) pjsip_register_hdr_parser( const char *hname,
+ const char *hshortname,
+ pjsip_parse_hdr_func *fptr)
+{
+ unsigned i, len;
+ char hname_lcase[PJSIP_MAX_HNAME_LEN+1];
+ pj_status_t status;
+
+ /* Check that name is not too long */
+ len = pj_ansi_strlen(hname);
+ if (len > PJSIP_MAX_HNAME_LEN) {
+ pj_assert(!"Header name is too long!");
+ return PJ_ENAMETOOLONG;
+ }
+
+ /* Register the normal Mixed-Case name */
+ status = int_register_parser(hname, fptr);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Get the lower-case name */
+ for (i=0; i<len; ++i) {
+ hname_lcase[i] = (char)pj_tolower(hname[i]);
+ }
+ hname_lcase[len] = '\0';
+
+ /* Register the lower-case version of the name */
+ status = int_register_parser(hname_lcase, fptr);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+
+ /* Register the shortname version of the name */
+ if (hshortname) {
+ status = int_register_parser(hshortname, fptr);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+ return PJ_SUCCESS;
+}
+
+
+/* Find handler to parse the header name. */
+static pjsip_parse_hdr_func * find_handler_imp(pj_uint32_t hash,
+ const pj_str_t *hname)
+{
+ handler_rec *first;
+ int comp;
+ unsigned n;
+
+ /* Binary search for the handler. */
+ comp = -1;
+ first = &handler[0];
+ n = handler_count;
+ for (; n > 0; ) {
+ unsigned half = n / 2;
+ handler_rec *mid = first + half;
+
+ comp = compare_handler(mid, hname->ptr, hname->slen, hash);
+ if (comp < 0) {
+ first = ++mid;
+ n -= half + 1;
+ } else if (comp==0) {
+ first = mid;
+ break;
+ } else {
+ n = half;
+ }
+ }
+
+ return comp==0 ? first->handler : NULL;
+}
+
+
+/* Find handler to parse the header name. */
+static pjsip_parse_hdr_func* find_handler(const pj_str_t *hname)
+{
+ pj_uint32_t hash;
+ char hname_copy[PJSIP_MAX_HNAME_LEN];
+ pj_str_t tmp;
+ pjsip_parse_hdr_func *handler;
+
+ if (hname->slen >= PJSIP_MAX_HNAME_LEN) {
+ /* Guaranteed not to be able to find handler. */
+ return NULL;
+ }
+
+ /* First, common case, try to find handler with exact name */
+ hash = pj_hash_calc(0, hname->ptr, hname->slen);
+ handler = find_handler_imp(hash, hname);
+ if (handler)
+ return handler;
+
+
+ /* If not found, try converting the header name to lowercase and
+ * search again.
+ */
+ hash = pj_hash_calc_tolower(0, hname_copy, hname);
+ tmp.ptr = hname_copy;
+ tmp.slen = hname->slen;
+ return find_handler_imp(hash, &tmp);
+}
+
+
+/* Find URI handler. */
+static pjsip_parse_uri_func* find_uri_handler(const pj_str_t *scheme)
+{
+ unsigned i;
+ for (i=0; i<uri_handler_count; ++i) {
+ if (parser_stricmp(uri_handler[i].scheme, (*scheme))==0)
+ return uri_handler[i].parse;
+ }
+ return &int_parse_other_uri;
+}
+
+/* Register URI parser. */
+PJ_DEF(pj_status_t) pjsip_register_uri_parser( char *scheme,
+ pjsip_parse_uri_func *func)
+{
+ if (uri_handler_count >= PJ_ARRAY_SIZE(uri_handler))
+ return PJ_ETOOMANY;
+
+ uri_handler[uri_handler_count].scheme = pj_str((char*)scheme);
+ uri_handler[uri_handler_count].parse = func;
+ ++uri_handler_count;
+
+ return PJ_SUCCESS;
+}
+
+/* Public function to parse SIP message. */
+PJ_DEF(pjsip_msg*) pjsip_parse_msg( pj_pool_t *pool,
+ char *buf, pj_size_t size,
+ pjsip_parser_err_report *err_list)
+{
+ pjsip_msg *msg = NULL;
+ pj_scanner scanner;
+ pjsip_parse_ctx context;
+
+ pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ context.scanner = &scanner;
+ context.pool = pool;
+ context.rdata = NULL;
+
+ msg = int_parse_msg(&context, err_list);
+
+ pj_scan_fini(&scanner);
+ return msg;
+}
+
+/* Public function to parse as rdata.*/
+PJ_DEF(pjsip_msg *) pjsip_parse_rdata( char *buf, pj_size_t size,
+ pjsip_rx_data *rdata )
+{
+ pj_scanner scanner;
+ pjsip_parse_ctx context;
+
+ pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ context.scanner = &scanner;
+ context.pool = rdata->tp_info.pool;
+ context.rdata = rdata;
+
+ rdata->msg_info.msg = int_parse_msg(&context, &rdata->msg_info.parse_err);
+
+ pj_scan_fini(&scanner);
+ return rdata->msg_info.msg;
+}
+
+/* Determine if a message has been received. */
+PJ_DEF(pj_bool_t) pjsip_find_msg( const char *buf, pj_size_t size,
+ pj_bool_t is_datagram, pj_size_t *msg_size)
+{
+#if PJ_HAS_TCP
+ const char *hdr_end;
+ const char *body_start;
+ const char *pos;
+ const char *line;
+ int content_length = -1;
+ pj_str_t cur_msg;
+ const pj_str_t end_hdr = { "\n\r\n", 3};
+
+ *msg_size = size;
+
+ /* For datagram, the whole datagram IS the message. */
+ if (is_datagram) {
+ return PJ_SUCCESS;
+ }
+
+
+ /* Find the end of header area by finding an empty line.
+ * Don't use plain strstr() since we want to be able to handle
+ * NULL character in the message
+ */
+ cur_msg.ptr = (char*)buf; cur_msg.slen = size;
+ pos = pj_strstr(&cur_msg, &end_hdr);
+ if (pos == NULL) {
+ return PJSIP_EPARTIALMSG;
+ }
+
+ hdr_end = pos+1;
+ body_start = pos+3;
+
+ /* Find "Content-Length" header the hard way. */
+ line = pj_strchr(&cur_msg, '\n');
+ while (line && line < hdr_end) {
+ ++line;
+ if ( ((*line=='C' || *line=='c') &&
+ strnicmp_alnum(line, "Content-Length", 14) == 0) ||
+ ((*line=='l' || *line=='L') &&
+ (*(line+1)==' ' || *(line+1)=='\t' || *(line+1)==':')))
+ {
+ /* Try to parse the header. */
+ pj_scanner scanner;
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, (char*)line, hdr_end-line,
+ PJ_SCAN_AUTOSKIP_WS_HEADER, &on_syntax_error);
+
+ PJ_TRY {
+ pj_str_t str_clen;
+
+ /* Get "Content-Length" or "L" name */
+ if (*line=='C' || *line=='c')
+ pj_scan_advance_n(&scanner, 14, PJ_TRUE);
+ else if (*line=='l' || *line=='L')
+ pj_scan_advance_n(&scanner, 1, PJ_TRUE);
+
+ /* Get colon */
+ if (pj_scan_get_char(&scanner) != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ /* Get number */
+ pj_scan_get(&scanner, &pconst.pjsip_DIGIT_SPEC, &str_clen);
+
+ /* Get newline. */
+ pj_scan_get_newline(&scanner);
+
+ /* Found a valid Content-Length header. */
+ content_length = pj_strtoul(&str_clen);
+ }
+ PJ_CATCH_ANY {
+ content_length = -1;
+ }
+ PJ_END
+
+ pj_scan_fini(&scanner);
+ }
+
+ /* Found valid Content-Length? */
+ if (content_length != -1)
+ break;
+
+ /* Go to next line. */
+ cur_msg.slen -= (line - cur_msg.ptr);
+ cur_msg.ptr = (char*)line;
+ line = pj_strchr(&cur_msg, '\n');
+ }
+
+ /* Found Content-Length? */
+ if (content_length == -1) {
+ return PJSIP_EMISSINGHDR;
+ }
+
+ /* Enough packet received? */
+ *msg_size = (body_start - buf) + content_length;
+ return (*msg_size) <= size ? PJ_SUCCESS : PJSIP_EPARTIALMSG;
+#else
+ PJ_UNUSED_ARG(buf);
+ PJ_UNUSED_ARG(is_datagram);
+ *msg_size = size;
+ return PJ_SUCCESS;
+#endif
+}
+
+/* Public function to parse URI */
+PJ_DEF(pjsip_uri*) pjsip_parse_uri( pj_pool_t *pool,
+ char *buf, pj_size_t size,
+ unsigned option)
+{
+ pj_scanner scanner;
+ pjsip_uri *uri = NULL;
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, buf, size, 0, &on_syntax_error);
+
+
+ PJ_TRY {
+ uri = int_parse_uri_or_name_addr(&scanner, pool, option);
+ }
+ PJ_CATCH_ANY {
+ uri = NULL;
+ }
+ PJ_END;
+
+ /* Must have exhausted all inputs. */
+ if (pj_scan_is_eof(&scanner) || IS_NEWLINE(*scanner.curptr)) {
+ /* Success. */
+ pj_scan_fini(&scanner);
+ return uri;
+ }
+
+ /* Still have some characters unparsed. */
+ pj_scan_fini(&scanner);
+ return NULL;
+}
+
+/* SIP version */
+static void parse_sip_version(pj_scanner *scanner)
+{
+ pj_str_t SIP = { "SIP", 3 };
+ pj_str_t V2 = { "2.0", 3 };
+ pj_str_t sip, version;
+
+ pj_scan_get( scanner, &pconst.pjsip_ALPHA_SPEC, &sip);
+ if (pj_scan_get_char(scanner) != '/')
+ on_syntax_error(scanner);
+ pj_scan_get_n( scanner, 3, &version);
+ if (pj_stricmp(&sip, &SIP) || pj_stricmp(&version, &V2))
+ on_syntax_error(scanner);
+}
+
+static pj_bool_t is_next_sip_version(pj_scanner *scanner)
+{
+ pj_str_t SIP = { "SIP", 3 };
+ pj_str_t sip;
+ int c;
+
+ c = pj_scan_peek(scanner, &pconst.pjsip_ALPHA_SPEC, &sip);
+ /* return TRUE if it is "SIP" followed by "/" or space.
+ * we include space since the "/" may be separated by space,
+ * although this would mean it would return TRUE if it is a
+ * request and the method is "SIP"!
+ */
+ return c && (c=='/' || c==' ' || c=='\t') && pj_stricmp(&sip, &SIP)==0;
+}
+
+/* Internal function to parse SIP message */
+static pjsip_msg *int_parse_msg( pjsip_parse_ctx *ctx,
+ pjsip_parser_err_report *err_list)
+{
+ pj_bool_t parsing_headers;
+ pjsip_msg *msg = NULL;
+ pj_str_t hname;
+ pjsip_ctype_hdr *ctype_hdr = NULL;
+ pj_scanner *scanner = ctx->scanner;
+ pj_pool_t *pool = ctx->pool;
+ PJ_USE_EXCEPTION;
+
+ parsing_headers = PJ_FALSE;
+
+retry_parse:
+ PJ_TRY
+ {
+ if (parsing_headers)
+ goto parse_headers;
+
+ /* Skip leading newlines. */
+ while (IS_NEWLINE(*scanner->curptr)) {
+ pj_scan_get_newline(scanner);
+ }
+
+ /* Check if we still have valid packet.
+ * Sometimes endpoints just send blank (CRLF) packets just to keep
+ * NAT bindings open.
+ */
+ if (pj_scan_is_eof(scanner))
+ return NULL;
+
+ /* Parse request or status line */
+ if (is_next_sip_version(scanner)) {
+ msg = pjsip_msg_create(pool, PJSIP_RESPONSE_MSG);
+ int_parse_status_line( scanner, &msg->line.status );
+ } else {
+ msg = pjsip_msg_create(pool, PJSIP_REQUEST_MSG);
+ int_parse_req_line(scanner, pool, &msg->line.req );
+ }
+
+ parsing_headers = PJ_TRUE;
+
+parse_headers:
+ /* Parse headers. */
+ do {
+ pjsip_parse_hdr_func * handler;
+ pjsip_hdr *hdr = NULL;
+
+ /* Init hname just in case parsing fails.
+ * Ref: PROTOS #2412
+ */
+ hname.slen = 0;
+
+ /* Get hname. */
+ pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &hname);
+ if (pj_scan_get_char( scanner ) != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ /* Find handler. */
+ handler = find_handler(&hname);
+
+ /* Call the handler if found.
+ * If no handler is found, then treat the header as generic
+ * hname/hvalue pair.
+ */
+ if (handler) {
+ hdr = (*handler)(ctx);
+
+ /* Note:
+ * hdr MAY BE NULL, if parsing does not yield a new header
+ * instance, e.g. the values have been added to existing
+ * header. See http://trac.pjsip.org/repos/ticket/940
+ */
+
+ /* Check if we've just parsed a Content-Type header.
+ * We will check for a message body if we've got Content-Type
+ * header.
+ */
+ if (hdr && hdr->type == PJSIP_H_CONTENT_TYPE) {
+ ctype_hdr = (pjsip_ctype_hdr*)hdr;
+ }
+
+ } else {
+ hdr = parse_hdr_generic_string(ctx);
+ hdr->name = hdr->sname = hname;
+ }
+
+
+ /* Single parse of header line can produce multiple headers.
+ * For example, if one Contact: header contains Contact list
+ * separated by comma, then these Contacts will be split into
+ * different Contact headers.
+ * So here we must insert list instead of just insert one header.
+ */
+ if (hdr)
+ pj_list_insert_nodes_before(&msg->hdr, hdr);
+
+ /* Parse until EOF or an empty line is found. */
+ } while (!pj_scan_is_eof(scanner) && !IS_NEWLINE(*scanner->curptr));
+
+ parsing_headers = PJ_FALSE;
+
+ /* If empty line is found, eat it. */
+ if (!pj_scan_is_eof(scanner)) {
+ if (IS_NEWLINE(*scanner->curptr)) {
+ pj_scan_get_newline(scanner);
+ }
+ }
+
+ /* If we have Content-Type header, treat the rest of the message
+ * as body.
+ */
+ if (ctype_hdr && scanner->curptr!=scanner->end) {
+ /* New: if Content-Type indicates that this is a multipart
+ * message body, parse it.
+ */
+ const pj_str_t STR_MULTIPART = { "multipart", 9 };
+ pjsip_msg_body *body;
+
+ if (pj_stricmp(&ctype_hdr->media.type, &STR_MULTIPART)==0) {
+ body = pjsip_multipart_parse(pool, scanner->curptr,
+ scanner->end - scanner->curptr,
+ &ctype_hdr->media, 0);
+ } else {
+ body = PJ_POOL_ALLOC_T(pool, pjsip_msg_body);
+ pjsip_media_type_cp(pool, &body->content_type,
+ &ctype_hdr->media);
+
+ body->data = scanner->curptr;
+ body->len = scanner->end - scanner->curptr;
+ body->print_body = &pjsip_print_text_body;
+ body->clone_data = &pjsip_clone_text_data;
+ }
+
+ msg->body = body;
+ }
+ }
+ PJ_CATCH_ANY
+ {
+ /* Exception was thrown during parsing.
+ * Skip until newline, and parse next header.
+ */
+ if (err_list) {
+ pjsip_parser_err_report *err_info;
+
+ err_info = PJ_POOL_ALLOC_T(pool, pjsip_parser_err_report);
+ err_info->except_code = PJ_GET_EXCEPTION();
+ err_info->line = scanner->line;
+ /* Scanner's column is zero based, so add 1 */
+ err_info->col = pj_scan_get_col(scanner) + 1;
+ if (parsing_headers)
+ err_info->hname = hname;
+ else if (msg && msg->type == PJSIP_REQUEST_MSG)
+ err_info->hname = pj_str("Request Line");
+ else if (msg && msg->type == PJSIP_RESPONSE_MSG)
+ err_info->hname = pj_str("Status Line");
+ else
+ err_info->hname.slen = 0;
+
+ pj_list_insert_before(err_list, err_info);
+ }
+
+ if (parsing_headers) {
+ if (!pj_scan_is_eof(scanner)) {
+ /* Skip until next line.
+ * Watch for header continuation.
+ */
+ do {
+ pj_scan_skip_line(scanner);
+ } while (IS_SPACE(*scanner->curptr));
+ }
+
+ /* Restore flag. Flag may be set in int_parse_sip_url() */
+ scanner->skip_ws = PJ_SCAN_AUTOSKIP_WS_HEADER;
+
+ /* Continue parse next header, if any. */
+ if (!pj_scan_is_eof(scanner) && !IS_NEWLINE(*scanner->curptr)) {
+ goto retry_parse;
+ }
+ }
+
+ msg = NULL;
+ }
+ PJ_END;
+
+ return msg;
+}
+
+
+/* Parse parameter (pname ["=" pvalue]). */
+static void parse_param_imp( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ const pj_cis_t *spec, const pj_cis_t *esc_spec,
+ unsigned option)
+{
+ /* pname */
+ parser_get_and_unescape(scanner, pool, spec, esc_spec, pname);
+
+ /* init pvalue */
+ pvalue->ptr = NULL;
+ pvalue->slen = 0;
+
+ /* pvalue, if any */
+ if (*scanner->curptr == '=') {
+ pj_scan_get_char(scanner);
+ if (!pj_scan_is_eof(scanner)) {
+ /* pvalue can be a quoted string. */
+ if (*scanner->curptr == '"') {
+ pj_scan_get_quote( scanner, '"', '"', pvalue);
+ if (option & PJSIP_PARSE_REMOVE_QUOTE) {
+ pvalue->ptr++;
+ pvalue->slen -= 2;
+ }
+ } else if (*scanner->curptr == '[') {
+ /* pvalue can be a quoted IPv6; in this case, the
+ * '[' and ']' quote characters are to be removed
+ * from the pvalue.
+ */
+ pj_scan_get_char(scanner);
+ pj_scan_get_until_ch(scanner, ']', pvalue);
+ pj_scan_get_char(scanner);
+ } else if(pj_cis_match(spec, *scanner->curptr)) {
+ parser_get_and_unescape(scanner, pool, spec, esc_spec, pvalue);
+ }
+ }
+ }
+}
+
+/* Parse parameter (pname ["=" pvalue]) using token. */
+PJ_DEF(void) pjsip_parse_param_imp(pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ unsigned option)
+{
+ parse_param_imp(scanner, pool, pname, pvalue, &pconst.pjsip_TOKEN_SPEC,
+ &pconst.pjsip_TOKEN_SPEC_ESC, option);
+}
+
+
+/* Parse parameter (pname ["=" pvalue]) using paramchar. */
+PJ_DEF(void) pjsip_parse_uri_param_imp( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ unsigned option)
+{
+ parse_param_imp(scanner,pool, pname, pvalue, &pconst.pjsip_PARAM_CHAR_SPEC,
+ &pconst.pjsip_PARAM_CHAR_SPEC_ESC, option);
+}
+
+
+/* Parse parameter (";" pname ["=" pvalue]) in SIP header. */
+static void int_parse_param( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ unsigned option)
+{
+ /* Get ';' character */
+ pj_scan_get_char(scanner);
+
+ /* Get pname and optionally pvalue */
+ pjsip_parse_param_imp(scanner, pool, pname, pvalue, option);
+}
+
+/* Parse parameter (";" pname ["=" pvalue]) in URI. */
+static void int_parse_uri_param( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *pname, pj_str_t *pvalue,
+ unsigned option)
+{
+ /* Get ';' character */
+ pj_scan_get_char(scanner);
+
+ /* Get pname and optionally pvalue */
+ pjsip_parse_uri_param_imp(scanner, pool, pname, pvalue,
+ option);
+}
+
+
+/* Parse header parameter. */
+static void int_parse_hparam( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *hname, pj_str_t *hvalue )
+{
+ /* Get '?' or '&' character. */
+ pj_scan_get_char(scanner);
+
+ /* hname */
+ parser_get_and_unescape(scanner, pool, &pconst.pjsip_HDR_CHAR_SPEC,
+ &pconst.pjsip_HDR_CHAR_SPEC_ESC, hname);
+
+ /* Init hvalue */
+ hvalue->ptr = NULL;
+ hvalue->slen = 0;
+
+ /* pvalue, if any */
+ if (*scanner->curptr == '=') {
+ pj_scan_get_char(scanner);
+ if (!pj_scan_is_eof(scanner) &&
+ pj_cis_match(&pconst.pjsip_HDR_CHAR_SPEC, *scanner->curptr))
+ {
+ parser_get_and_unescape(scanner, pool, &pconst.pjsip_HDR_CHAR_SPEC,
+ &pconst.pjsip_HDR_CHAR_SPEC_ESC, hvalue);
+ }
+ }
+}
+
+/* Parse host part:
+ * host = hostname / IPv4address / IPv6reference
+ */
+static void int_parse_host(pj_scanner *scanner, pj_str_t *host)
+{
+ if (*scanner->curptr == '[') {
+ /* Note: the '[' and ']' characters are removed from the host */
+ pj_scan_get_char(scanner);
+ pj_scan_get_until_ch(scanner, ']', host);
+ pj_scan_get_char(scanner);
+ } else {
+ pj_scan_get( scanner, &pconst.pjsip_HOST_SPEC, host);
+ }
+}
+
+/* Parse host:port in URI. */
+static void int_parse_uri_host_port( pj_scanner *scanner,
+ pj_str_t *host, int *p_port)
+{
+ int_parse_host(scanner, host);
+
+ /* RFC3261 section 19.1.2: host don't need to be unescaped */
+ if (*scanner->curptr == ':') {
+ pj_str_t port;
+ pj_scan_get_char(scanner);
+ pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &port);
+ *p_port = pj_strtoul(&port);
+ } else {
+ *p_port = 0;
+ }
+}
+
+/* Determine if the next token in an URI is a user specification. */
+static int int_is_next_user(pj_scanner *scanner)
+{
+ pj_str_t dummy;
+ int is_user;
+
+ /* Find character '@'. If this character exist, then the token
+ * must be a username.
+ */
+ if (pj_scan_peek( scanner, &pconst.pjsip_PROBE_USER_HOST_SPEC, &dummy) == '@')
+ is_user = 1;
+ else
+ is_user = 0;
+
+ return is_user;
+}
+
+/* Parse user:pass tokens in an URI. */
+static void int_parse_user_pass( pj_scanner *scanner, pj_pool_t *pool,
+ pj_str_t *user, pj_str_t *pass)
+{
+ parser_get_and_unescape(scanner, pool, &pconst.pjsip_USER_SPEC_LENIENT,
+ &pconst.pjsip_USER_SPEC_LENIENT_ESC, user);
+
+ if ( *scanner->curptr == ':') {
+ pj_scan_get_char( scanner );
+ parser_get_and_unescape(scanner, pool, &pconst.pjsip_PASSWD_SPEC,
+ &pconst.pjsip_PASSWD_SPEC_ESC, pass);
+ } else {
+ pass->ptr = NULL;
+ pass->slen = 0;
+ }
+
+ /* Get the '@' */
+ pj_scan_get_char( scanner );
+}
+
+/* Parse all types of URI. */
+static pjsip_uri *int_parse_uri_or_name_addr( pj_scanner *scanner, pj_pool_t *pool,
+ unsigned opt)
+{
+ pjsip_uri *uri;
+ int is_name_addr = 0;
+
+ /* Exhaust any whitespaces. */
+ pj_scan_skip_whitespace(scanner);
+
+ if (*scanner->curptr=='"' || *scanner->curptr=='<') {
+ uri = (pjsip_uri*)int_parse_name_addr( scanner, pool );
+ is_name_addr = 1;
+ } else {
+ pj_str_t scheme;
+ int next_ch;
+
+ next_ch = pj_scan_peek( scanner, &pconst.pjsip_DISPLAY_SPEC, &scheme);
+
+ if (next_ch==':') {
+ pjsip_parse_uri_func *func = find_uri_handler(&scheme);
+
+ if (func == NULL) {
+ /* Unsupported URI scheme */
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ uri = (pjsip_uri*)
+ (*func)(scanner, pool,
+ (opt & PJSIP_PARSE_URI_IN_FROM_TO_HDR)==0);
+
+
+ } else {
+ uri = (pjsip_uri*)int_parse_name_addr( scanner, pool );
+ is_name_addr = 1;
+ }
+ }
+
+ /* Should we return the URI object as name address? */
+ if (opt & PJSIP_PARSE_URI_AS_NAMEADDR) {
+ if (is_name_addr == 0) {
+ pjsip_name_addr *name_addr;
+
+ name_addr = pjsip_name_addr_create(pool);
+ name_addr->uri = uri;
+
+ uri = (pjsip_uri*)name_addr;
+ }
+ }
+
+ return uri;
+}
+
+/* Parse URI. */
+static pjsip_uri *int_parse_uri(pj_scanner *scanner, pj_pool_t *pool,
+ pj_bool_t parse_params)
+{
+ /* Bug:
+ * This function should not call back int_parse_name_addr() because
+ * it is called by that function. This would cause stack overflow
+ * with PROTOS test #1223.
+ if (*scanner->curptr=='"' || *scanner->curptr=='<') {
+ return (pjsip_uri*)int_parse_name_addr( scanner, pool );
+ } else {
+ */
+ pj_str_t scheme;
+ int colon;
+ pjsip_parse_uri_func *func;
+
+ /* Get scheme. */
+ colon = pj_scan_peek(scanner, &pconst.pjsip_TOKEN_SPEC, &scheme);
+ if (colon != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ func = find_uri_handler(&scheme);
+ if (func) {
+ return (pjsip_uri*)(*func)(scanner, pool, parse_params);
+
+ } else {
+ /* Unsupported URI scheme */
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ UNREACHED({ return NULL; /* Not reached. */ })
+ }
+
+ /*
+ }
+ */
+}
+
+/* Parse "sip:" and "sips:" URI.
+ * This actually returns (pjsip_sip_uri*) type,
+ */
+static void* int_parse_sip_url( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_bool_t parse_params)
+{
+ pj_str_t scheme;
+ pjsip_sip_uri *url = NULL;
+ int colon;
+ int skip_ws = scanner->skip_ws;
+ scanner->skip_ws = 0;
+
+ pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &scheme);
+ colon = pj_scan_get_char(scanner);
+ if (colon != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ if (parser_stricmp(scheme, pconst.pjsip_SIP_STR)==0) {
+ url = pjsip_sip_uri_create(pool, 0);
+
+ } else if (parser_stricmp(scheme, pconst.pjsip_SIPS_STR)==0) {
+ url = pjsip_sip_uri_create(pool, 1);
+
+ } else {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ /* should not reach here */
+ UNREACHED({
+ pj_assert(0);
+ return 0;
+ })
+ }
+
+ if (int_is_next_user(scanner)) {
+ int_parse_user_pass(scanner, pool, &url->user, &url->passwd);
+ }
+
+ /* Get host:port */
+ int_parse_uri_host_port(scanner, &url->host, &url->port);
+
+ /* Get URL parameters. */
+ if (parse_params) {
+ while (*scanner->curptr == ';' ) {
+ pj_str_t pname, pvalue;
+
+ int_parse_uri_param( scanner, pool, &pname, &pvalue, 0);
+
+ if (!parser_stricmp(pname, pconst.pjsip_USER_STR) && pvalue.slen) {
+ url->user_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_METHOD_STR) && pvalue.slen) {
+ url->method_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_TRANSPORT_STR) && pvalue.slen) {
+ url->transport_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_TTL_STR) && pvalue.slen) {
+ url->ttl_param = pj_strtoul(&pvalue);
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_MADDR_STR) && pvalue.slen) {
+ url->maddr_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_LR_STR)) {
+ url->lr_param = 1;
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = pname;
+ p->value = pvalue;
+ pj_list_insert_before(&url->other_param, p);
+ }
+ }
+ }
+
+ /* Get header params. */
+ if (parse_params && *scanner->curptr == '?') {
+ do {
+ pjsip_param *param;
+ param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ int_parse_hparam(scanner, pool, &param->name, &param->value);
+ pj_list_insert_before(&url->header_param, param);
+ } while (*scanner->curptr == '&');
+ }
+
+ scanner->skip_ws = skip_ws;
+ pj_scan_skip_whitespace(scanner);
+ return url;
+}
+
+/* Parse nameaddr. */
+static pjsip_name_addr *int_parse_name_addr( pj_scanner *scanner,
+ pj_pool_t *pool )
+{
+ int has_bracket;
+ pjsip_name_addr *name_addr;
+
+ name_addr = pjsip_name_addr_create(pool);
+
+ if (*scanner->curptr == '"') {
+ pj_scan_get_quote( scanner, '"', '"', &name_addr->display);
+ /* Trim the leading and ending quote */
+ name_addr->display.ptr++;
+ name_addr->display.slen -= 2;
+
+ } else if (*scanner->curptr != '<') {
+ int next;
+ pj_str_t dummy;
+
+ /* This can be either the start of display name,
+ * the start of URL ("sip:", "sips:", "tel:", etc.), or '<' char.
+ * We're only interested in display name, because SIP URL
+ * will be parser later.
+ */
+ next = pj_scan_peek(scanner, &pconst.pjsip_DISPLAY_SPEC, &dummy);
+ if (next == '<') {
+ /* Ok, this is what we're looking for, a display name. */
+ pj_scan_get_until_ch( scanner, '<', &name_addr->display);
+ pj_strtrim(&name_addr->display);
+ }
+ }
+
+ /* Manually skip whitespace. */
+ pj_scan_skip_whitespace(scanner);
+
+ /* Get the SIP-URL */
+ has_bracket = (*scanner->curptr == '<');
+ if (has_bracket)
+ pj_scan_get_char(scanner);
+ name_addr->uri = int_parse_uri( scanner, pool, PJ_TRUE );
+ if (has_bracket) {
+ if (pj_scan_get_char(scanner) != '>')
+ PJ_THROW( PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ return name_addr;
+}
+
+
+/* Parse other URI */
+static void* int_parse_other_uri(pj_scanner *scanner,
+ pj_pool_t *pool,
+ pj_bool_t parse_params)
+{
+ pjsip_other_uri *uri = 0;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+ int skip_ws = scanner->skip_ws;
+
+ PJ_UNUSED_ARG(parse_params);
+
+ scanner->skip_ws = 0;
+
+ uri = pjsip_other_uri_create(pool);
+
+ pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &uri->scheme);
+ if (pj_scan_get_char(scanner) != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ pj_scan_get(scanner, &pc->pjsip_OTHER_URI_CONTENT, &uri->content);
+ scanner->skip_ws = skip_ws;
+
+ return uri;
+}
+
+
+/* Parse SIP request line. */
+static void int_parse_req_line( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_request_line *req_line)
+{
+ pj_str_t token;
+
+ pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &token);
+ pjsip_method_init_np( &req_line->method, &token);
+
+ req_line->uri = int_parse_uri(scanner, pool, PJ_TRUE);
+ parse_sip_version(scanner);
+ pj_scan_get_newline( scanner );
+}
+
+/* Parse status line. */
+static void int_parse_status_line( pj_scanner *scanner,
+ pjsip_status_line *status_line)
+{
+ pj_str_t token;
+
+ parse_sip_version(scanner);
+ pj_scan_get( scanner, &pconst.pjsip_DIGIT_SPEC, &token);
+ status_line->code = pj_strtoul(&token);
+ if (*scanner->curptr != '\r' && *scanner->curptr != '\n')
+ pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &status_line->reason);
+ else
+ status_line->reason.slen=0, status_line->reason.ptr=NULL;
+ pj_scan_get_newline( scanner );
+}
+
+
+/*
+ * Public API to parse SIP status line.
+ */
+PJ_DEF(pj_status_t) pjsip_parse_status_line( char *buf, pj_size_t size,
+ pjsip_status_line *status_line)
+{
+ pj_scanner scanner;
+ PJ_USE_EXCEPTION;
+
+ pj_bzero(status_line, sizeof(*status_line));
+ pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ PJ_TRY {
+ int_parse_status_line(&scanner, status_line);
+ }
+ PJ_CATCH_ANY {
+ /* Tolerate the error if it is caused only by missing newline */
+ if (status_line->code == 0 && status_line->reason.slen == 0) {
+ pj_scan_fini(&scanner);
+ return PJSIP_EINVALIDMSG;
+ }
+ }
+ PJ_END;
+
+ pj_scan_fini(&scanner);
+ return PJ_SUCCESS;
+}
+
+
+/* Parse ending of header. */
+static void parse_hdr_end( pj_scanner *scanner )
+{
+ if (pj_scan_is_eof(scanner)) {
+ ; /* Do nothing. */
+ } else if (*scanner->curptr == '&') {
+ pj_scan_get_char(scanner);
+ } else {
+ pj_scan_get_newline(scanner);
+ }
+}
+
+/* Parse ending of header. */
+PJ_DEF(void) pjsip_parse_end_hdr_imp( pj_scanner *scanner )
+{
+ parse_hdr_end(scanner);
+}
+
+/* Parse generic array header. */
+static void parse_generic_array_hdr( pjsip_generic_array_hdr *hdr,
+ pj_scanner *scanner)
+{
+ /* Some header fields allow empty elements in the value:
+ * Accept, Allow, Supported
+ */
+ if (pj_scan_is_eof(scanner) ||
+ *scanner->curptr == '\r' || *scanner->curptr == '\n')
+ {
+ goto end;
+ }
+
+ if (hdr->count >= PJ_ARRAY_SIZE(hdr->values)) {
+ /* Too many elements */
+ on_syntax_error(scanner);
+ return;
+ }
+
+ pj_scan_get( scanner, &pconst.pjsip_NOT_COMMA_OR_NEWLINE,
+ &hdr->values[hdr->count]);
+ hdr->count++;
+
+ while (*scanner->curptr == ',') {
+ pj_scan_get_char(scanner);
+ pj_scan_get( scanner, &pconst.pjsip_NOT_COMMA_OR_NEWLINE,
+ &hdr->values[hdr->count]);
+ hdr->count++;
+
+ if (hdr->count >= PJSIP_GENERIC_ARRAY_MAX_COUNT)
+ break;
+ }
+
+end:
+ parse_hdr_end(scanner);
+}
+
+/* Parse generic string header. */
+static void parse_generic_string_hdr( pjsip_generic_string_hdr *hdr,
+ pjsip_parse_ctx *ctx)
+{
+ pj_scanner *scanner = ctx->scanner;
+
+ hdr->hvalue.slen = 0;
+
+ /* header may be mangled hence the loop */
+ while (pj_cis_match(&pconst.pjsip_NOT_NEWLINE, *scanner->curptr)) {
+ pj_str_t next, tmp;
+
+ pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &hdr->hvalue);
+ if (pj_scan_is_eof(scanner) || IS_NEWLINE(*scanner->curptr))
+ break;
+ /* mangled, get next fraction */
+ pj_scan_get( scanner, &pconst.pjsip_NOT_NEWLINE, &next);
+ /* concatenate */
+ tmp.ptr = (char*)pj_pool_alloc(ctx->pool,
+ hdr->hvalue.slen + next.slen + 2);
+ tmp.slen = 0;
+ pj_strcpy(&tmp, &hdr->hvalue);
+ pj_strcat2(&tmp, " ");
+ pj_strcat(&tmp, &next);
+ tmp.ptr[tmp.slen] = '\0';
+
+ hdr->hvalue = tmp;
+ }
+
+ parse_hdr_end(scanner);
+}
+
+/* Parse generic integer header. */
+static void parse_generic_int_hdr( pjsip_generic_int_hdr *hdr,
+ pj_scanner *scanner )
+{
+ pj_str_t tmp;
+ pj_scan_get( scanner, &pconst.pjsip_DIGIT_SPEC, &tmp);
+ hdr->ivalue = pj_strtoul(&tmp);
+ parse_hdr_end(scanner);
+}
+
+
+/* Parse Accept header. */
+static pjsip_hdr* parse_hdr_accept(pjsip_parse_ctx *ctx)
+{
+ pjsip_accept_hdr *accept = pjsip_accept_hdr_create(ctx->pool);
+ parse_generic_array_hdr(accept, ctx->scanner);
+ return (pjsip_hdr*)accept;
+}
+
+/* Parse Allow header. */
+static pjsip_hdr* parse_hdr_allow(pjsip_parse_ctx *ctx)
+{
+ pjsip_allow_hdr *allow = pjsip_allow_hdr_create(ctx->pool);
+ parse_generic_array_hdr(allow, ctx->scanner);
+ return (pjsip_hdr*)allow;
+}
+
+/* Parse Call-ID header. */
+static pjsip_hdr* parse_hdr_call_id(pjsip_parse_ctx *ctx)
+{
+ pjsip_cid_hdr *hdr = pjsip_cid_hdr_create(ctx->pool);
+ pj_scan_get( ctx->scanner, &pconst.pjsip_NOT_NEWLINE, &hdr->id);
+ parse_hdr_end(ctx->scanner);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.cid = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse and interpret Contact param. */
+static void int_parse_contact_param( pjsip_contact_hdr *hdr,
+ pj_scanner *scanner,
+ pj_pool_t *pool)
+{
+ while ( *scanner->curptr == ';' ) {
+ pj_str_t pname, pvalue;
+
+ int_parse_param( scanner, pool, &pname, &pvalue, 0);
+ if (!parser_stricmp(pname, pconst.pjsip_Q_STR) && pvalue.slen) {
+ char *dot_pos = (char*) pj_memchr(pvalue.ptr, '.', pvalue.slen);
+ if (!dot_pos) {
+ hdr->q1000 = pj_strtoul(&pvalue) * 1000;
+ } else {
+ pj_str_t tmp = pvalue;
+
+ tmp.slen = dot_pos - pvalue.ptr;
+ hdr->q1000 = pj_strtoul(&tmp) * 1000;
+
+ pvalue.slen = (pvalue.ptr+pvalue.slen) - (dot_pos+1);
+ pvalue.ptr = dot_pos + 1;
+ hdr->q1000 += pj_strtoul_mindigit(&pvalue, 3);
+ }
+ } else if (!parser_stricmp(pname, pconst.pjsip_EXPIRES_STR) && pvalue.slen) {
+ hdr->expires = pj_strtoul(&pvalue);
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = pname;
+ p->value = pvalue;
+ pj_list_insert_before(&hdr->other_param, p);
+ }
+ }
+}
+
+/* Parse Contact header. */
+static pjsip_hdr* parse_hdr_contact( pjsip_parse_ctx *ctx )
+{
+ pjsip_contact_hdr *first = NULL;
+ pj_scanner *scanner = ctx->scanner;
+
+ do {
+ pjsip_contact_hdr *hdr = pjsip_contact_hdr_create(ctx->pool);
+ if (first == NULL)
+ first = hdr;
+ else
+ pj_list_insert_before(first, hdr);
+
+ if (*scanner->curptr == '*') {
+ pj_scan_get_char(scanner);
+ hdr->star = 1;
+
+ } else {
+ hdr->star = 0;
+ hdr->uri = int_parse_uri_or_name_addr(scanner, ctx->pool,
+ PJSIP_PARSE_URI_AS_NAMEADDR |
+ PJSIP_PARSE_URI_IN_FROM_TO_HDR);
+
+ int_parse_contact_param(hdr, scanner, ctx->pool);
+ }
+
+ if (*scanner->curptr != ',')
+ break;
+
+ pj_scan_get_char(scanner);
+
+ } while (1);
+
+ parse_hdr_end(scanner);
+
+ return (pjsip_hdr*)first;
+}
+
+/* Parse Content-Length header. */
+static pjsip_hdr* parse_hdr_content_len( pjsip_parse_ctx *ctx )
+{
+ pj_str_t digit;
+ pjsip_clen_hdr *hdr;
+
+ hdr = pjsip_clen_hdr_create(ctx->pool);
+ pj_scan_get(ctx->scanner, &pconst.pjsip_DIGIT_SPEC, &digit);
+ hdr->len = pj_strtoul(&digit);
+ parse_hdr_end(ctx->scanner);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.clen = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Content-Type header. */
+static pjsip_hdr* parse_hdr_content_type( pjsip_parse_ctx *ctx )
+{
+ pjsip_ctype_hdr *hdr;
+ pj_scanner *scanner = ctx->scanner;
+
+ hdr = pjsip_ctype_hdr_create(ctx->pool);
+
+ /* Parse media type and subtype. */
+ pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->media.type);
+ pj_scan_get_char(scanner);
+ pj_scan_get(scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->media.subtype);
+
+ /* Parse media parameters */
+ while (*scanner->curptr == ';') {
+ pjsip_param *param = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ int_parse_param(scanner, ctx->pool, &param->name, &param->value, 0);
+ pj_list_push_back(&hdr->media.param, param);
+ }
+
+ parse_hdr_end(ctx->scanner);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.ctype = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse CSeq header. */
+static pjsip_hdr* parse_hdr_cseq( pjsip_parse_ctx *ctx )
+{
+ pj_str_t cseq, method;
+ pjsip_cseq_hdr *hdr;
+
+ hdr = pjsip_cseq_hdr_create(ctx->pool);
+ pj_scan_get( ctx->scanner, &pconst.pjsip_DIGIT_SPEC, &cseq);
+ hdr->cseq = pj_strtoul(&cseq);
+
+ pj_scan_get( ctx->scanner, &pconst.pjsip_TOKEN_SPEC, &method);
+ pjsip_method_init_np(&hdr->method, &method);
+
+ parse_hdr_end( ctx->scanner );
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.cseq = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Expires header. */
+static pjsip_hdr* parse_hdr_expires(pjsip_parse_ctx *ctx)
+{
+ pjsip_expires_hdr *hdr = pjsip_expires_hdr_create(ctx->pool, 0);
+ parse_generic_int_hdr(hdr, ctx->scanner);
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse From: or To: header. */
+static void parse_hdr_fromto( pj_scanner *scanner,
+ pj_pool_t *pool,
+ pjsip_from_hdr *hdr)
+{
+ hdr->uri = int_parse_uri_or_name_addr(scanner, pool,
+ PJSIP_PARSE_URI_AS_NAMEADDR |
+ PJSIP_PARSE_URI_IN_FROM_TO_HDR);
+
+ while ( *scanner->curptr == ';' ) {
+ pj_str_t pname, pvalue;
+
+ int_parse_param( scanner, pool, &pname, &pvalue, 0);
+
+ if (!parser_stricmp(pname, pconst.pjsip_TAG_STR)) {
+ hdr->tag = pvalue;
+
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = pname;
+ p->value = pvalue;
+ pj_list_insert_before(&hdr->other_param, p);
+ }
+ }
+
+ parse_hdr_end(scanner);
+}
+
+/* Parse From: header. */
+static pjsip_hdr* parse_hdr_from( pjsip_parse_ctx *ctx )
+{
+ pjsip_from_hdr *hdr = pjsip_from_hdr_create(ctx->pool);
+ parse_hdr_fromto(ctx->scanner, ctx->pool, hdr);
+ if (ctx->rdata)
+ ctx->rdata->msg_info.from = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Require: header. */
+static pjsip_hdr* parse_hdr_require( pjsip_parse_ctx *ctx )
+{
+ pjsip_require_hdr *hdr;
+ pj_bool_t new_hdr = (ctx->rdata==NULL ||
+ ctx->rdata->msg_info.require == NULL);
+
+ if (ctx->rdata && ctx->rdata->msg_info.require) {
+ hdr = ctx->rdata->msg_info.require;
+ } else {
+ hdr = pjsip_require_hdr_create(ctx->pool);
+ if (ctx->rdata)
+ ctx->rdata->msg_info.require = hdr;
+ }
+
+ parse_generic_array_hdr(hdr, ctx->scanner);
+
+ return new_hdr ? (pjsip_hdr*)hdr : NULL;
+}
+
+/* Parse Retry-After: header. */
+static pjsip_hdr* parse_hdr_retry_after(pjsip_parse_ctx *ctx)
+{
+ pjsip_retry_after_hdr *hdr;
+ pj_scanner *scanner = ctx->scanner;
+ pj_str_t tmp;
+
+ hdr = pjsip_retry_after_hdr_create(ctx->pool, 0);
+
+ pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &tmp);
+ hdr->ivalue = pj_strtoul(&tmp);
+
+ while (!pj_scan_is_eof(scanner) && *scanner->curptr!='\r' &&
+ *scanner->curptr!='\n')
+ {
+ if (*scanner->curptr=='(') {
+ pj_scan_get_quote(scanner, '(', ')', &hdr->comment);
+ /* Trim the leading and ending parens */
+ hdr->comment.ptr++;
+ hdr->comment.slen -= 2;
+ } else if (*scanner->curptr==';') {
+ pjsip_param *prm = PJ_POOL_ALLOC_T(ctx->pool, pjsip_param);
+ int_parse_param(scanner, ctx->pool, &prm->name, &prm->value, 0);
+ pj_list_push_back(&hdr->param, prm);
+ }
+ }
+
+ parse_hdr_end(scanner);
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Supported: header. */
+static pjsip_hdr* parse_hdr_supported(pjsip_parse_ctx *ctx)
+{
+ pjsip_supported_hdr *hdr;
+ pj_bool_t new_hdr = (ctx->rdata==NULL ||
+ ctx->rdata->msg_info.supported == NULL);
+
+ if (ctx->rdata && ctx->rdata->msg_info.supported) {
+ hdr = ctx->rdata->msg_info.supported;
+ } else {
+ hdr = pjsip_supported_hdr_create(ctx->pool);
+ if (ctx->rdata)
+ ctx->rdata->msg_info.supported = hdr;
+ }
+
+ parse_generic_array_hdr(hdr, ctx->scanner);
+ return new_hdr ? (pjsip_hdr*)hdr : NULL;
+}
+
+/* Parse To: header. */
+static pjsip_hdr* parse_hdr_to( pjsip_parse_ctx *ctx )
+{
+ pjsip_to_hdr *hdr = pjsip_to_hdr_create(ctx->pool);
+ parse_hdr_fromto(ctx->scanner, ctx->pool, hdr);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.to = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Unsupported: header. */
+static pjsip_hdr* parse_hdr_unsupported(pjsip_parse_ctx *ctx)
+{
+ pjsip_unsupported_hdr *hdr = pjsip_unsupported_hdr_create(ctx->pool);
+ parse_generic_array_hdr(hdr, ctx->scanner);
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse and interpret Via parameters. */
+static void int_parse_via_param( pjsip_via_hdr *hdr, pj_scanner *scanner,
+ pj_pool_t *pool)
+{
+ while ( *scanner->curptr == ';' ) {
+ pj_str_t pname, pvalue;
+
+ //Parse with PARAM_CHAR instead, to allow IPv6
+ //No, back to using int_parse_param() for the "`" character!
+ //int_parse_param( scanner, pool, &pname, &pvalue, 0);
+ //parse_param_imp(scanner, pool, &pname, &pvalue,
+ // &pconst.pjsip_TOKEN_SPEC,
+ // &pconst.pjsip_TOKEN_SPEC_ESC, 0);
+ //int_parse_param(scanner, pool, &pname, &pvalue, 0);
+ // This should be the correct one:
+ // added special spec for Via parameter, basically token plus
+ // ":" to allow IPv6 address in the received param.
+ pj_scan_get_char(scanner);
+ parse_param_imp(scanner, pool, &pname, &pvalue,
+ &pconst.pjsip_VIA_PARAM_SPEC,
+ &pconst.pjsip_VIA_PARAM_SPEC_ESC,
+ 0);
+
+ if (!parser_stricmp(pname, pconst.pjsip_BRANCH_STR) && pvalue.slen) {
+ hdr->branch_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_TTL_STR) && pvalue.slen) {
+ hdr->ttl_param = pj_strtoul(&pvalue);
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_MADDR_STR) && pvalue.slen) {
+ hdr->maddr_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_RECEIVED_STR) && pvalue.slen) {
+ hdr->recvd_param = pvalue;
+
+ } else if (!parser_stricmp(pname, pconst.pjsip_RPORT_STR)) {
+ if (pvalue.slen)
+ hdr->rport_param = pj_strtoul(&pvalue);
+ else
+ hdr->rport_param = 0;
+ } else {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ p->name = pname;
+ p->value = pvalue;
+ pj_list_insert_before(&hdr->other_param, p);
+ }
+ }
+}
+
+/* Parse Max-Forwards header. */
+static pjsip_hdr* parse_hdr_max_forwards( pjsip_parse_ctx *ctx )
+{
+ pjsip_max_fwd_hdr *hdr;
+ hdr = pjsip_max_fwd_hdr_create(ctx->pool, 0);
+ parse_generic_int_hdr(hdr, ctx->scanner);
+
+ if (ctx->rdata)
+ ctx->rdata->msg_info.max_fwd = hdr;
+
+ return (pjsip_hdr*)hdr;
+}
+
+/* Parse Min-Expires header. */
+static pjsip_hdr* parse_hdr_min_expires(pjsip_parse_ctx *ctx)
+{
+ pjsip_min_expires_hdr *hdr;
+ hdr = pjsip_min_expires_hdr_create(ctx->pool, 0);
+ parse_generic_int_hdr(hdr, ctx->scanner);
+ return (pjsip_hdr*)hdr;
+}
+
+
+/* Parse Route: or Record-Route: header. */
+static void parse_hdr_rr_route( pj_scanner *scanner, pj_pool_t *pool,
+ pjsip_routing_hdr *hdr )
+{
+ pjsip_name_addr *temp=int_parse_name_addr(scanner, pool);
+
+ pj_memcpy(&hdr->name_addr, temp, sizeof(*temp));
+
+ while (*scanner->curptr == ';') {
+ pjsip_param *p = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ int_parse_param(scanner, pool, &p->name, &p->value, 0);
+ pj_list_insert_before(&hdr->other_param, p);
+ }
+}
+
+/* Parse Record-Route header. */
+static pjsip_hdr* parse_hdr_rr( pjsip_parse_ctx *ctx)
+{
+ pjsip_rr_hdr *first = NULL;
+ pj_scanner *scanner = ctx->scanner;
+
+ do {
+ pjsip_rr_hdr *hdr = pjsip_rr_hdr_create(ctx->pool);
+ if (!first) {
+ first = hdr;
+ } else {
+ pj_list_insert_before(first, hdr);
+ }
+ parse_hdr_rr_route(scanner, ctx->pool, hdr);
+ if (*scanner->curptr == ',') {
+ pj_scan_get_char(scanner);
+ } else {
+ break;
+ }
+ } while (1);
+ parse_hdr_end(scanner);
+
+ if (ctx->rdata && ctx->rdata->msg_info.record_route==NULL)
+ ctx->rdata->msg_info.record_route = first;
+
+ return (pjsip_hdr*)first;
+}
+
+/* Parse Route: header. */
+static pjsip_hdr* parse_hdr_route( pjsip_parse_ctx *ctx )
+{
+ pjsip_route_hdr *first = NULL;
+ pj_scanner *scanner = ctx->scanner;
+
+ do {
+ pjsip_route_hdr *hdr = pjsip_route_hdr_create(ctx->pool);
+ if (!first) {
+ first = hdr;
+ } else {
+ pj_list_insert_before(first, hdr);
+ }
+ parse_hdr_rr_route(scanner, ctx->pool, hdr);
+ if (*scanner->curptr == ',') {
+ pj_scan_get_char(scanner);
+ } else {
+ break;
+ }
+ } while (1);
+ parse_hdr_end(scanner);
+
+ if (ctx->rdata && ctx->rdata->msg_info.route==NULL)
+ ctx->rdata->msg_info.route = first;
+
+ return (pjsip_hdr*)first;
+}
+
+/* Parse Via: header. */
+static pjsip_hdr* parse_hdr_via( pjsip_parse_ctx *ctx )
+{
+ pjsip_via_hdr *first = NULL;
+ pj_scanner *scanner = ctx->scanner;
+
+ do {
+ pjsip_via_hdr *hdr = pjsip_via_hdr_create(ctx->pool);
+ if (!first)
+ first = hdr;
+ else
+ pj_list_insert_before(first, hdr);
+
+ parse_sip_version(scanner);
+ if (pj_scan_get_char(scanner) != '/')
+ on_syntax_error(scanner);
+
+ pj_scan_get( scanner, &pconst.pjsip_TOKEN_SPEC, &hdr->transport);
+ int_parse_host(scanner, &hdr->sent_by.host);
+
+ if (*scanner->curptr==':') {
+ pj_str_t digit;
+ pj_scan_get_char(scanner);
+ pj_scan_get(scanner, &pconst.pjsip_DIGIT_SPEC, &digit);
+ hdr->sent_by.port = pj_strtoul(&digit);
+ }
+
+ int_parse_via_param(hdr, scanner, ctx->pool);
+
+ if (*scanner->curptr == '(') {
+ pj_scan_get_char(scanner);
+ pj_scan_get_until_ch( scanner, ')', &hdr->comment);
+ pj_scan_get_char( scanner );
+ }
+
+ if (*scanner->curptr != ',')
+ break;
+
+ pj_scan_get_char(scanner);
+
+ } while (1);
+
+ parse_hdr_end(scanner);
+
+ if (ctx->rdata && ctx->rdata->msg_info.via == NULL)
+ ctx->rdata->msg_info.via = first;
+
+ return (pjsip_hdr*)first;
+}
+
+/* Parse generic header. */
+static pjsip_hdr* parse_hdr_generic_string( pjsip_parse_ctx *ctx )
+{
+ pjsip_generic_string_hdr *hdr;
+
+ hdr = pjsip_generic_string_hdr_create(ctx->pool, NULL, NULL);
+ parse_generic_string_hdr(hdr, ctx);
+ return (pjsip_hdr*)hdr;
+
+}
+
+/* Public function to parse a header value. */
+PJ_DEF(void*) pjsip_parse_hdr( pj_pool_t *pool, const pj_str_t *hname,
+ char *buf, pj_size_t size, int *parsed_len )
+{
+ pj_scanner scanner;
+ pjsip_hdr *hdr = NULL;
+ pjsip_parse_ctx context;
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, buf, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ context.scanner = &scanner;
+ context.pool = pool;
+ context.rdata = NULL;
+
+ PJ_TRY {
+ pjsip_parse_hdr_func *handler = find_handler(hname);
+ if (handler) {
+ hdr = (*handler)(&context);
+ } else {
+ hdr = parse_hdr_generic_string(&context);
+ hdr->type = PJSIP_H_OTHER;
+ pj_strdup(pool, &hdr->name, hname);
+ hdr->sname = hdr->name;
+ }
+
+ }
+ PJ_CATCH_ANY {
+ hdr = NULL;
+ }
+ PJ_END
+
+ if (parsed_len) {
+ *parsed_len = (scanner.curptr - scanner.begin);
+ }
+
+ pj_scan_fini(&scanner);
+
+ return hdr;
+}
+
+/* Parse multiple header lines */
+PJ_DEF(pj_status_t) pjsip_parse_headers( pj_pool_t *pool, char *input,
+ pj_size_t size, pjsip_hdr *hlist,
+ unsigned options)
+{
+ enum { STOP_ON_ERROR = 1 };
+ pj_scanner scanner;
+ pjsip_parse_ctx ctx;
+ pj_str_t hname;
+ PJ_USE_EXCEPTION;
+
+ pj_scan_init(&scanner, input, size, PJ_SCAN_AUTOSKIP_WS_HEADER,
+ &on_syntax_error);
+
+ pj_bzero(&ctx, sizeof(ctx));
+ ctx.scanner = &scanner;
+ ctx.pool = pool;
+
+retry_parse:
+ PJ_TRY
+ {
+ /* Parse headers. */
+ do {
+ pjsip_parse_hdr_func * handler;
+ pjsip_hdr *hdr = NULL;
+
+ /* Init hname just in case parsing fails.
+ * Ref: PROTOS #2412
+ */
+ hname.slen = 0;
+
+ /* Get hname. */
+ pj_scan_get( &scanner, &pconst.pjsip_TOKEN_SPEC, &hname);
+ if (pj_scan_get_char( &scanner ) != ':') {
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ }
+
+ /* Find handler. */
+ handler = find_handler(&hname);
+
+ /* Call the handler if found.
+ * If no handler is found, then treat the header as generic
+ * hname/hvalue pair.
+ */
+ if (handler) {
+ hdr = (*handler)(&ctx);
+ } else {
+ hdr = parse_hdr_generic_string(&ctx);
+ hdr->name = hdr->sname = hname;
+ }
+
+ /* Single parse of header line can produce multiple headers.
+ * For example, if one Contact: header contains Contact list
+ * separated by comma, then these Contacts will be split into
+ * different Contact headers.
+ * So here we must insert list instead of just insert one header.
+ */
+ if (hdr)
+ pj_list_insert_nodes_before(hlist, hdr);
+
+ /* Parse until EOF or an empty line is found. */
+ } while (!pj_scan_is_eof(&scanner) && !IS_NEWLINE(*scanner.curptr));
+
+ /* If empty line is found, eat it. */
+ if (!pj_scan_is_eof(&scanner)) {
+ if (IS_NEWLINE(*scanner.curptr)) {
+ pj_scan_get_newline(&scanner);
+ }
+ }
+ }
+ PJ_CATCH_ANY
+ {
+ PJ_LOG(4,(THIS_FILE, "Error parsing header: '%.*s' line %d col %d",
+ (int)hname.slen, hname.ptr, scanner.line,
+ pj_scan_get_col(&scanner)));
+
+ /* Exception was thrown during parsing. */
+ if ((options & STOP_ON_ERROR) == STOP_ON_ERROR) {
+ pj_scan_fini(&scanner);
+ return PJSIP_EINVALIDHDR;
+ }
+
+ /* Skip until newline, and parse next header. */
+ if (!pj_scan_is_eof(&scanner)) {
+ /* Skip until next line.
+ * Watch for header continuation.
+ */
+ do {
+ pj_scan_skip_line(&scanner);
+ } while (IS_SPACE(*scanner.curptr));
+ }
+
+ /* Restore flag. Flag may be set in int_parse_sip_url() */
+ scanner.skip_ws = PJ_SCAN_AUTOSKIP_WS_HEADER;
+
+ /* Continue parse next header, if any. */
+ if (!pj_scan_is_eof(&scanner) && !IS_NEWLINE(*scanner.curptr)) {
+ goto retry_parse;
+ }
+
+ }
+ PJ_END;
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_parser_wrap.cpp b/pjsip/src/pjsip/sip_parser_wrap.cpp
new file mode 100644
index 0000000..21f9718
--- /dev/null
+++ b/pjsip/src/pjsip/sip_parser_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_parser_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_parser.c"
diff --git a/pjsip/src/pjsip/sip_resolve.c b/pjsip/src/pjsip/sip_resolve.c
new file mode 100644
index 0000000..f5efc1e
--- /dev/null
+++ b/pjsip/src/pjsip/sip_resolve.c
@@ -0,0 +1,520 @@
+/* $Id: sip_resolve.c 4108 2012-04-27 01:32:12Z bennylp $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_resolve.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_errno.h>
+#include <pjlib-util/errno.h>
+#include <pjlib-util/srv_resolver.h>
+#include <pj/addr_resolv.h>
+#include <pj/array.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/rand.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "sip_resolve.c"
+
+#define ADDR_MAX_COUNT 8
+
+struct naptr_target
+{
+ pj_str_t res_type; /**< e.g. "_sip._udp" */
+ pj_str_t name; /**< Domain name. */
+ pjsip_transport_type_e type; /**< Transport type. */
+ unsigned order; /**< Order */
+ unsigned pref; /**< Preference. */
+};
+
+struct query
+{
+ char *objname;
+
+ pj_dns_type query_type;
+ void *token;
+ pjsip_resolver_callback *cb;
+ pj_dns_async_query *object;
+ pj_status_t last_error;
+
+ /* Original request: */
+ struct {
+ pjsip_host_info target;
+ unsigned def_port;
+ } req;
+
+ /* NAPTR records: */
+ unsigned naptr_cnt;
+ struct naptr_target naptr[8];
+};
+
+
+struct pjsip_resolver_t
+{
+ pj_dns_resolver *res;
+};
+
+
+static void srv_resolver_cb(void *user_data,
+ pj_status_t status,
+ const pj_dns_srv_record *rec);
+static void dns_a_callback(void *user_data,
+ pj_status_t status,
+ pj_dns_parsed_packet *response);
+
+
+/*
+ * Public API to create the resolver.
+ */
+PJ_DEF(pj_status_t) pjsip_resolver_create( pj_pool_t *pool,
+ pjsip_resolver_t **p_res)
+{
+ pjsip_resolver_t *resolver;
+
+ PJ_ASSERT_RETURN(pool && p_res, PJ_EINVAL);
+ resolver = PJ_POOL_ZALLOC_T(pool, pjsip_resolver_t);
+ *p_res = resolver;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Public API to set the DNS resolver instance for the SIP resolver.
+ */
+PJ_DEF(pj_status_t) pjsip_resolver_set_resolver(pjsip_resolver_t *res,
+ pj_dns_resolver *dns_res)
+{
+#if PJSIP_HAS_RESOLVER
+ res->res = dns_res;
+ return PJ_SUCCESS;
+#else
+ PJ_UNUSED_ARG(res);
+ PJ_UNUSED_ARG(dns_res);
+ pj_assert(!"Resolver is disabled (PJSIP_HAS_RESOLVER==0)");
+ return PJ_EINVALIDOP;
+#endif
+}
+
+
+/*
+ * Public API to get the internal DNS resolver.
+ */
+PJ_DEF(pj_dns_resolver*) pjsip_resolver_get_resolver(pjsip_resolver_t *res)
+{
+ return res->res;
+}
+
+
+/*
+ * Public API to create destroy the resolver
+ */
+PJ_DEF(void) pjsip_resolver_destroy(pjsip_resolver_t *resolver)
+{
+ if (resolver->res) {
+#if PJSIP_HAS_RESOLVER
+ pj_dns_resolver_destroy(resolver->res, PJ_FALSE);
+#endif
+ resolver->res = NULL;
+ }
+}
+
+/*
+ * Internal:
+ * determine if an address is a valid IP address, and if it is,
+ * return the IP version (4 or 6).
+ */
+static int get_ip_addr_ver(const pj_str_t *host)
+{
+ pj_in_addr dummy;
+ pj_in6_addr dummy6;
+
+ /* First check with inet_aton() */
+ if (pj_inet_aton(host, &dummy) > 0)
+ return 4;
+
+ /* Then check if this is an IPv6 address */
+ if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS)
+ return 6;
+
+ /* Not an IP address */
+ return 0;
+}
+
+
+/*
+ * This is the main function for performing server resolution.
+ */
+PJ_DEF(void) pjsip_resolve( pjsip_resolver_t *resolver,
+ pj_pool_t *pool,
+ const pjsip_host_info *target,
+ void *token,
+ pjsip_resolver_callback *cb)
+{
+ pjsip_server_addresses svr_addr;
+ pj_status_t status = PJ_SUCCESS;
+ int ip_addr_ver;
+ struct query *query;
+ pjsip_transport_type_e type = target->type;
+
+ /* Is it IP address or hostname? And if it's an IP, which version? */
+ ip_addr_ver = get_ip_addr_ver(&target->addr.host);
+
+ /* Set the transport type if not explicitly specified.
+ * RFC 3263 section 4.1 specify rules to set up this.
+ */
+ if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
+ if (ip_addr_ver || (target->addr.port != 0)) {
+#if PJ_HAS_TCP
+ if (target->flag & PJSIP_TRANSPORT_SECURE)
+ {
+ type = PJSIP_TRANSPORT_TLS;
+ } else if (target->flag & PJSIP_TRANSPORT_RELIABLE)
+ {
+ type = PJSIP_TRANSPORT_TCP;
+ } else
+#endif
+ {
+ type = PJSIP_TRANSPORT_UDP;
+ }
+ } else {
+ /* No type or explicit port is specified, and the address is
+ * not IP address.
+ * In this case, full NAPTR resolution must be performed.
+ * But we don't support it (yet).
+ */
+#if PJ_HAS_TCP
+ if (target->flag & PJSIP_TRANSPORT_SECURE)
+ {
+ type = PJSIP_TRANSPORT_TLS;
+ } else if (target->flag & PJSIP_TRANSPORT_RELIABLE)
+ {
+ type = PJSIP_TRANSPORT_TCP;
+ } else
+#endif
+ {
+ type = PJSIP_TRANSPORT_UDP;
+ }
+ }
+
+ /* Add IPv6 flag for IPv6 address */
+ if (ip_addr_ver == 6)
+ type = (pjsip_transport_type_e)((int)type + PJSIP_TRANSPORT_IPV6);
+ }
+
+
+ /* If target is an IP address, or if resolver is not configured,
+ * we can just finish the resolution now using pj_gethostbyname()
+ */
+ if (ip_addr_ver || resolver->res == NULL) {
+ char addr_str[PJ_INET6_ADDRSTRLEN+10];
+ pj_uint16_t srv_port;
+
+ if (ip_addr_ver != 0) {
+ /* Target is an IP address, no need to resolve */
+ if (ip_addr_ver == 4) {
+ pj_sockaddr_init(pj_AF_INET(), &svr_addr.entry[0].addr,
+ NULL, 0);
+ pj_inet_aton(&target->addr.host,
+ &svr_addr.entry[0].addr.ipv4.sin_addr);
+ } else {
+ pj_sockaddr_init(pj_AF_INET6(), &svr_addr.entry[0].addr,
+ NULL, 0);
+ pj_inet_pton(pj_AF_INET6(), &target->addr.host,
+ &svr_addr.entry[0].addr.ipv6.sin6_addr);
+ }
+ } else {
+ pj_addrinfo ai;
+ unsigned count;
+ int af;
+
+ PJ_LOG(5,(THIS_FILE,
+ "DNS resolver not available, target '%.*s:%d' type=%s "
+ "will be resolved with getaddrinfo()",
+ target->addr.host.slen,
+ target->addr.host.ptr,
+ target->addr.port,
+ pjsip_transport_get_type_name(target->type)));
+
+ if (type & PJSIP_TRANSPORT_IPV6) {
+ af = pj_AF_INET6();
+ } else {
+ af = pj_AF_INET();
+ }
+
+ /* Resolve */
+ count = 1;
+ status = pj_getaddrinfo(af, &target->addr.host, &count, &ai);
+ if (status != PJ_SUCCESS) {
+ /* "Normalize" error to PJ_ERESOLVE. This is a special error
+ * because it will be translated to SIP status 502 by
+ * sip_transaction.c
+ */
+ status = PJ_ERESOLVE;
+ goto on_error;
+ }
+
+ svr_addr.entry[0].addr.addr.sa_family = (pj_uint16_t)af;
+ pj_memcpy(&svr_addr.entry[0].addr, &ai.ai_addr,
+ sizeof(pj_sockaddr));
+ }
+
+ /* Set the port number */
+ if (target->addr.port == 0) {
+ srv_port = (pj_uint16_t)
+ pjsip_transport_get_default_port_for_type(type);
+ } else {
+ srv_port = (pj_uint16_t)target->addr.port;
+ }
+ pj_sockaddr_set_port(&svr_addr.entry[0].addr, srv_port);
+
+ /* Call the callback. */
+ PJ_LOG(5,(THIS_FILE,
+ "Target '%.*s:%d' type=%s resolved to "
+ "'%s' type=%s (%s)",
+ (int)target->addr.host.slen,
+ target->addr.host.ptr,
+ target->addr.port,
+ pjsip_transport_get_type_name(target->type),
+ pj_sockaddr_print(&svr_addr.entry[0].addr, addr_str,
+ sizeof(addr_str), 3),
+ pjsip_transport_get_type_name(type),
+ pjsip_transport_get_type_desc(type)));
+ svr_addr.count = 1;
+ svr_addr.entry[0].priority = 0;
+ svr_addr.entry[0].weight = 0;
+ svr_addr.entry[0].type = type;
+ svr_addr.entry[0].addr_len = pj_sockaddr_get_len(&svr_addr.entry[0].addr);
+ (*cb)(status, token, &svr_addr);
+
+ /* Done. */
+ return;
+ }
+
+ /* Target is not an IP address so we need to resolve it. */
+#if PJSIP_HAS_RESOLVER
+
+ /* Build the query state */
+ query = PJ_POOL_ZALLOC_T(pool, struct query);
+ query->objname = THIS_FILE;
+ query->token = token;
+ query->cb = cb;
+ query->req.target = *target;
+ pj_strdup(pool, &query->req.target.addr.host, &target->addr.host);
+
+ /* If port is not specified, start with SRV resolution
+ * (should be with NAPTR, but we'll do that later)
+ */
+ PJ_TODO(SUPPORT_DNS_NAPTR);
+
+ /* Build dummy NAPTR entry */
+ query->naptr_cnt = 1;
+ pj_bzero(&query->naptr[0], sizeof(query->naptr[0]));
+ query->naptr[0].order = 0;
+ query->naptr[0].pref = 0;
+ query->naptr[0].type = type;
+ pj_strdup(pool, &query->naptr[0].name, &target->addr.host);
+
+
+ /* Start DNS SRV or A resolution, depending on whether port is specified */
+ if (target->addr.port == 0) {
+ query->query_type = PJ_DNS_TYPE_SRV;
+
+ query->req.def_port = 5060;
+
+ if (type == PJSIP_TRANSPORT_TLS) {
+ query->naptr[0].res_type = pj_str("_sips._tcp.");
+ query->req.def_port = 5061;
+ } else if (type == PJSIP_TRANSPORT_TCP)
+ query->naptr[0].res_type = pj_str("_sip._tcp.");
+ else if (type == PJSIP_TRANSPORT_UDP)
+ query->naptr[0].res_type = pj_str("_sip._udp.");
+ else {
+ pj_assert(!"Unknown transport type");
+ query->naptr[0].res_type = pj_str("_sip._udp.");
+
+ }
+
+ } else {
+ /* Otherwise if port is specified, start with A (or AAAA) host
+ * resolution
+ */
+ query->query_type = PJ_DNS_TYPE_A;
+ query->naptr[0].res_type.slen = 0;
+ query->req.def_port = target->addr.port;
+ }
+
+ /* Start the asynchronous query */
+ PJ_LOG(5, (query->objname,
+ "Starting async DNS %s query: target=%.*s%.*s, transport=%s, "
+ "port=%d",
+ pj_dns_get_type_name(query->query_type),
+ (int)query->naptr[0].res_type.slen,
+ query->naptr[0].res_type.ptr,
+ (int)query->naptr[0].name.slen, query->naptr[0].name.ptr,
+ pjsip_transport_get_type_name(target->type),
+ target->addr.port));
+
+ if (query->query_type == PJ_DNS_TYPE_SRV) {
+
+ status = pj_dns_srv_resolve(&query->naptr[0].name,
+ &query->naptr[0].res_type,
+ query->req.def_port, pool, resolver->res,
+ PJ_TRUE, query, &srv_resolver_cb, NULL);
+
+ } else if (query->query_type == PJ_DNS_TYPE_A) {
+
+ status = pj_dns_resolver_start_query(resolver->res,
+ &query->naptr[0].name,
+ PJ_DNS_TYPE_A, 0,
+ &dns_a_callback,
+ query, &query->object);
+
+ } else {
+ pj_assert(!"Unexpected");
+ status = PJ_EBUG;
+ }
+
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ return;
+
+#else /* PJSIP_HAS_RESOLVER */
+ PJ_UNUSED_ARG(pool);
+ PJ_UNUSED_ARG(query);
+ PJ_UNUSED_ARG(srv_name);
+#endif /* PJSIP_HAS_RESOLVER */
+
+on_error:
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ PJ_LOG(4,(THIS_FILE, "Failed to resolve '%.*s'. Err=%d (%s)",
+ (int)target->addr.host.slen,
+ target->addr.host.ptr,
+ status,
+ pj_strerror(status,errmsg,sizeof(errmsg)).ptr));
+ (*cb)(status, token, NULL);
+ return;
+ }
+}
+
+#if PJSIP_HAS_RESOLVER
+
+/*
+ * This callback is called when target is resolved with DNS A query.
+ */
+static void dns_a_callback(void *user_data,
+ pj_status_t status,
+ pj_dns_parsed_packet *pkt)
+{
+ struct query *query = (struct query*) user_data;
+ pjsip_server_addresses srv;
+ pj_dns_a_record rec;
+ unsigned i;
+
+ rec.addr_count = 0;
+
+ /* Parse the response */
+ if (status == PJ_SUCCESS) {
+ status = pj_dns_parse_a_response(pkt, &rec);
+ }
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ /* Log error */
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s",
+ errmsg));
+
+ /* Call the callback */
+ (*query->cb)(status, query->token, NULL);
+ return;
+ }
+
+ /* Build server addresses and call callback */
+ srv.count = 0;
+ for (i=0; i<rec.addr_count; ++i) {
+ srv.entry[srv.count].type = query->naptr[0].type;
+ srv.entry[srv.count].priority = 0;
+ srv.entry[srv.count].weight = 0;
+ srv.entry[srv.count].addr_len = sizeof(pj_sockaddr_in);
+ pj_sockaddr_in_init(&srv.entry[srv.count].addr.ipv4,
+ 0, (pj_uint16_t)query->req.def_port);
+ srv.entry[srv.count].addr.ipv4.sin_addr.s_addr =
+ rec.addr[i].s_addr;
+
+ ++srv.count;
+ }
+
+ /* Call the callback */
+ (*query->cb)(PJ_SUCCESS, query->token, &srv);
+}
+
+
+/* Callback to be called by DNS SRV resolution */
+static void srv_resolver_cb(void *user_data,
+ pj_status_t status,
+ const pj_dns_srv_record *rec)
+{
+ struct query *query = (struct query*) user_data;
+ pjsip_server_addresses srv;
+ unsigned i;
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ /* Log error */
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(query->objname, "DNS A record resolution failed: %s",
+ errmsg));
+
+ /* Call the callback */
+ (*query->cb)(status, query->token, NULL);
+ return;
+ }
+
+ /* Build server addresses and call callback */
+ srv.count = 0;
+ for (i=0; i<rec->count; ++i) {
+ unsigned j;
+
+ for (j=0; j<rec->entry[i].server.addr_count; ++j) {
+ srv.entry[srv.count].type = query->naptr[0].type;
+ srv.entry[srv.count].priority = rec->entry[i].priority;
+ srv.entry[srv.count].weight = rec->entry[i].weight;
+ srv.entry[srv.count].addr_len = sizeof(pj_sockaddr_in);
+ pj_sockaddr_in_init(&srv.entry[srv.count].addr.ipv4,
+ 0, (pj_uint16_t)rec->entry[i].port);
+ srv.entry[srv.count].addr.ipv4.sin_addr.s_addr =
+ rec->entry[i].server.addr[j].s_addr;
+
+ ++srv.count;
+ }
+ }
+
+ /* Call the callback */
+ (*query->cb)(PJ_SUCCESS, query->token, &srv);
+}
+
+#endif /* PJSIP_HAS_RESOLVER */
+
diff --git a/pjsip/src/pjsip/sip_tel_uri.c b/pjsip/src/pjsip/sip_tel_uri.c
new file mode 100644
index 0000000..4120ae0
--- /dev/null
+++ b/pjsip/src/pjsip/sip_tel_uri.c
@@ -0,0 +1,448 @@
+/* $Id: sip_tel_uri.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 <pjsip/sip_tel_uri.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/print_util.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+#include <pj/ctype.h>
+#include <pj/except.h>
+#include <pjlib-util/string.h>
+#include <pjlib-util/scanner.h>
+
+#define ALPHA
+#define DIGITS "0123456789"
+#define HEX "aAbBcCdDeEfF"
+#define HEX_DIGITS DIGITS HEX
+#define VISUAL_SEP "-.()"
+#define PHONE_DIGITS DIGITS VISUAL_SEP
+#define GLOBAL_DIGITS "+" PHONE_DIGITS
+#define LOCAL_DIGITS HEX_DIGITS "*#" VISUAL_SEP
+#define NUMBER_SPEC LOCAL_DIGITS GLOBAL_DIGITS
+#define PHONE_CONTEXT ALPHA GLOBAL_DIGITS
+//#define RESERVED ";/?:@&=+$,"
+#define RESERVED "/:@&$,+"
+#define MARK "-_.!~*'()"
+#define UNRESERVED ALPHA DIGITS MARK
+#define ESCAPED "%"
+#define URIC RESERVED UNRESERVED ESCAPED "[]+"
+#define PARAM_UNRESERVED "[]/:&+$"
+#define PARAM_CHAR PARAM_UNRESERVED UNRESERVED ESCAPED
+
+static pj_cis_buf_t cis_buf;
+static pj_cis_t pjsip_TEL_NUMBER_SPEC;
+static pj_cis_t pjsip_TEL_EXT_VALUE_SPEC;
+static pj_cis_t pjsip_TEL_PHONE_CONTEXT_SPEC;
+static pj_cis_t pjsip_TEL_URIC_SPEC;
+static pj_cis_t pjsip_TEL_VISUAL_SEP_SPEC;
+static pj_cis_t pjsip_TEL_PNAME_SPEC;
+static pj_cis_t pjsip_TEL_PVALUE_SPEC;
+static pj_cis_t pjsip_TEL_PVALUE_SPEC_ESC;
+static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC;
+static pj_cis_t pjsip_TEL_PARSING_PVALUE_SPEC_ESC;
+
+static pj_str_t pjsip_ISUB_STR = { "isub", 4 };
+static pj_str_t pjsip_EXT_STR = { "ext", 3 };
+static pj_str_t pjsip_PH_CTX_STR = { "phone-context", 13 };
+
+
+static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri* );
+static void *tel_uri_get_uri( pjsip_tel_uri* );
+static pj_ssize_t tel_uri_print( pjsip_uri_context_e context,
+ const pjsip_tel_uri *url,
+ char *buf, pj_size_t size);
+static int tel_uri_cmp( pjsip_uri_context_e context,
+ const pjsip_tel_uri *url1, const pjsip_tel_uri *url2);
+static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs);
+static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool,
+ pj_bool_t parse_params);
+
+typedef const pj_str_t* (*P_GET_SCHEME)(const void*);
+typedef void* (*P_GET_URI)(void*);
+typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *,
+ char*,pj_size_t);
+typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*,
+ const void*);
+typedef void* (*P_CLONE)(pj_pool_t*, const void*);
+
+static pjsip_uri_vptr tel_uri_vptr =
+{
+ (P_GET_SCHEME) &tel_uri_get_scheme,
+ (P_GET_URI) &tel_uri_get_uri,
+ (P_PRINT_URI) &tel_uri_print,
+ (P_CMP_URI) &tel_uri_cmp,
+ (P_CLONE) &tel_uri_clone
+};
+
+
+PJ_DEF(pjsip_tel_uri*) pjsip_tel_uri_create(pj_pool_t *pool)
+{
+ pjsip_tel_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_tel_uri);
+ uri->vptr = &tel_uri_vptr;
+ pj_list_init(&uri->other_param);
+ return uri;
+}
+
+
+static const pj_str_t *tel_uri_get_scheme( const pjsip_tel_uri *uri )
+{
+ PJ_UNUSED_ARG(uri);
+ return &pjsip_parser_const()->pjsip_TEL_STR;
+}
+
+static void *tel_uri_get_uri( pjsip_tel_uri *uri )
+{
+ return uri;
+}
+
+
+pj_status_t pjsip_tel_uri_subsys_init(void)
+{
+ pj_status_t status;
+
+ pj_cis_buf_init(&cis_buf);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_EXT_VALUE_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_str(&pjsip_TEL_EXT_VALUE_SPEC, PHONE_DIGITS);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_NUMBER_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_str(&pjsip_TEL_NUMBER_SPEC, NUMBER_SPEC);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_VISUAL_SEP_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_str(&pjsip_TEL_VISUAL_SEP_SPEC, VISUAL_SEP);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_PHONE_CONTEXT_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_alpha(&pjsip_TEL_PHONE_CONTEXT_SPEC);
+ pj_cis_add_num(&pjsip_TEL_PHONE_CONTEXT_SPEC);
+ pj_cis_add_str(&pjsip_TEL_PHONE_CONTEXT_SPEC, PHONE_CONTEXT);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_URIC_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_alpha(&pjsip_TEL_URIC_SPEC);
+ pj_cis_add_num(&pjsip_TEL_URIC_SPEC);
+ pj_cis_add_str(&pjsip_TEL_URIC_SPEC, URIC);
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_PNAME_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_alpha(&pjsip_TEL_PNAME_SPEC);
+ pj_cis_add_num(&pjsip_TEL_PNAME_SPEC);
+ pj_cis_add_str(&pjsip_TEL_PNAME_SPEC, "-");
+
+ status = pj_cis_init(&cis_buf, &pjsip_TEL_PVALUE_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_alpha(&pjsip_TEL_PVALUE_SPEC);
+ pj_cis_add_num(&pjsip_TEL_PVALUE_SPEC);
+ pj_cis_add_str(&pjsip_TEL_PVALUE_SPEC, PARAM_CHAR);
+
+ status = pj_cis_dup(&pjsip_TEL_PVALUE_SPEC_ESC, &pjsip_TEL_PVALUE_SPEC);
+ pj_cis_del_str(&pjsip_TEL_PVALUE_SPEC_ESC, "%");
+
+ status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_URIC_SPEC);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+ pj_cis_add_cis(&pjsip_TEL_PARSING_PVALUE_SPEC, &pjsip_TEL_PVALUE_SPEC);
+ pj_cis_add_str(&pjsip_TEL_PARSING_PVALUE_SPEC, "=");
+
+ status = pj_cis_dup(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC,
+ &pjsip_TEL_PARSING_PVALUE_SPEC);
+ pj_cis_del_str(&pjsip_TEL_PARSING_PVALUE_SPEC_ESC, "%");
+
+ status = pjsip_register_uri_parser("tel", &tel_uri_parse);
+ PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+ return PJ_SUCCESS;
+}
+
+/* Print tel: URI */
+static pj_ssize_t tel_uri_print( pjsip_uri_context_e context,
+ const pjsip_tel_uri *uri,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf+size;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ PJ_UNUSED_ARG(context);
+
+ /* Print scheme. */
+ copy_advance(buf, pc->pjsip_TEL_STR);
+ *buf++ = ':';
+
+ /* Print number. */
+ copy_advance_escape(buf, uri->number, pjsip_TEL_NUMBER_SPEC);
+
+ /* ISDN sub-address or extension must appear first. */
+
+ /* Extension param. */
+ copy_advance_pair_escape(buf, ";ext=", 5, uri->ext_param,
+ pjsip_TEL_EXT_VALUE_SPEC);
+
+ /* ISDN sub-address. */
+ copy_advance_pair_escape(buf, ";isub=", 6, uri->isub_param,
+ pjsip_TEL_URIC_SPEC);
+
+ /* Followed by phone context, if present. */
+ copy_advance_pair_escape(buf, ";phone-context=", 15, uri->context,
+ pjsip_TEL_PHONE_CONTEXT_SPEC);
+
+
+ /* Print other parameters. */
+ printed = pjsip_param_print_on(&uri->other_param, buf, (endbuf-buf),
+ &pjsip_TEL_PNAME_SPEC,
+ &pjsip_TEL_PVALUE_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ return (buf-startbuf);
+}
+
+/* Compare two numbers, according to RFC 3966:
+ * - both must be either local or global numbers.
+ * - The 'global-number-digits' and the 'local-number-digits' must be
+ * equal, after removing all visual separators.
+ */
+PJ_DEF(int) pjsip_tel_nb_cmp(const pj_str_t *number1, const pj_str_t *number2)
+{
+ const char *s1 = number1->ptr,
+ *e1 = number1->ptr + number1->slen,
+ *s2 = number2->ptr,
+ *e2 = number2->ptr + number2->slen;
+
+ /* Compare each number, ignoreing visual separators. */
+ while (s1!=e1 && s2!=e2) {
+ int diff;
+
+ if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1)) {
+ ++s1;
+ continue;
+ }
+ if (pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2)) {
+ ++s2;
+ continue;
+ }
+
+ diff = pj_tolower(*s1) - pj_tolower(*s2);
+ if (!diff) {
+ ++s1, ++s2;
+ continue;
+ } else
+ return diff;
+ }
+
+ /* Exhaust remaining visual separators. */
+ while (s1!=e1 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s1))
+ ++s1;
+ while (s2!=e2 && pj_cis_match(&pjsip_TEL_VISUAL_SEP_SPEC, *s2))
+ ++s2;
+
+ if (s1==e1 && s2==e2)
+ return 0;
+ else if (s1==e1)
+ return -1;
+ else
+ return 1;
+}
+
+/* Compare two tel: URI */
+static int tel_uri_cmp( pjsip_uri_context_e context,
+ const pjsip_tel_uri *url1, const pjsip_tel_uri *url2)
+{
+ int result;
+
+ PJ_UNUSED_ARG(context);
+
+ /* Scheme must match. */
+ if (url1->vptr != url2->vptr)
+ return -1;
+
+ /* Compare number. */
+ result = pjsip_tel_nb_cmp(&url1->number, &url2->number);
+ if (result != 0)
+ return result;
+
+ /* Compare phone-context as hostname or as as global nb. */
+ if (url1->context.slen) {
+ if (*url1->context.ptr != '+')
+ result = pj_stricmp(&url1->context, &url2->context);
+ else
+ result = pjsip_tel_nb_cmp(&url1->context, &url2->context);
+
+ if (result != 0)
+ return result;
+
+ } else if (url2->context.slen)
+ return -1;
+
+ /* Compare extension. */
+ if (url1->ext_param.slen) {
+ result = pjsip_tel_nb_cmp(&url1->ext_param, &url2->ext_param);
+ if (result != 0)
+ return result;
+ }
+
+ /* Compare isub bytes by bytes. */
+ if (url1->isub_param.slen) {
+ result = pj_stricmp(&url1->isub_param, &url2->isub_param);
+ if (result != 0)
+ return result;
+ }
+
+ /* Other parameters are compared regardless of the order.
+ * If one URI has parameter not found in the other URI, the URIs are
+ * not equal.
+ */
+ if (url1->other_param.next != &url1->other_param) {
+ const pjsip_param *p1, *p2;
+ int cnt1 = 0, cnt2 = 0;
+
+ p1 = url1->other_param.next;
+ while (p1 != &url1->other_param) {
+ p2 = pjsip_param_cfind(&url2->other_param, &p1->name);
+ if (!p2 )
+ return 1;
+
+ result = pj_stricmp(&p1->value, &p2->value);
+ if (result != 0)
+ return result;
+
+ p1 = p1->next;
+ ++cnt1;
+ }
+
+ p2 = url2->other_param.next;
+ while (p2 != &url2->other_param)
+ ++cnt2, p2 = p2->next;
+
+ if (cnt1 < cnt2)
+ return -1;
+ else if (cnt1 > cnt2)
+ return 1;
+
+ } else if (url2->other_param.next != &url2->other_param)
+ return -1;
+
+ /* Equal. */
+ return 0;
+}
+
+/* Clone tel: URI */
+static pjsip_tel_uri* tel_uri_clone(pj_pool_t *pool, const pjsip_tel_uri *rhs)
+{
+ pjsip_tel_uri *uri = pjsip_tel_uri_create(pool);
+
+ pj_strdup(pool, &uri->number, &rhs->number);
+ pj_strdup(pool, &uri->context, &rhs->context);
+ pj_strdup(pool, &uri->ext_param, &rhs->ext_param);
+ pj_strdup(pool, &uri->isub_param, &rhs->isub_param);
+ pjsip_param_clone(pool, &uri->other_param, &rhs->other_param);
+
+ return uri;
+}
+
+/* Parse tel: URI
+ * THis actually returns (pjsip_tel_uri *) type.
+ */
+static void* tel_uri_parse( pj_scanner *scanner, pj_pool_t *pool,
+ pj_bool_t parse_params)
+{
+ pjsip_tel_uri *uri;
+ pj_str_t token;
+ int skip_ws = scanner->skip_ws;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ scanner->skip_ws = 0;
+
+ /* Parse scheme. */
+ pj_scan_get(scanner, &pc->pjsip_TOKEN_SPEC, &token);
+ if (pj_scan_get_char(scanner) != ':')
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+ if (pj_stricmp_alnum(&token, &pc->pjsip_TEL_STR) != 0)
+ PJ_THROW(PJSIP_SYN_ERR_EXCEPTION);
+
+ /* Create URI */
+ uri = pjsip_tel_uri_create(pool);
+
+ /* Get the phone number. */
+#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ pj_scan_get_unescape(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number);
+#else
+ pj_scan_get(scanner, &pjsip_TEL_NUMBER_SPEC, &uri->number);
+ uri->number = pj_str_unescape(pool, &uri->number);
+#endif
+
+ /* Get all parameters. */
+ if (parse_params && *scanner->curptr==';') {
+ pj_str_t pname, pvalue;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ do {
+ /* Eat the ';' separator. */
+ pj_scan_get_char(scanner);
+
+ /* Get pname. */
+ pj_scan_get(scanner, &pc->pjsip_PARAM_CHAR_SPEC, &pname);
+
+ if (*scanner->curptr == '=') {
+ pj_scan_get_char(scanner);
+
+# if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
+ pj_scan_get_unescape(scanner,
+ &pjsip_TEL_PARSING_PVALUE_SPEC_ESC,
+ &pvalue);
+# else
+ pj_scan_get(scanner, &pjsip_TEL_PARSING_PVALUE_SPEC,
+ &pvalue);
+ pvalue = pj_str_unescape(pool, &pvalue);
+# endif
+
+ } else {
+ pvalue.slen = 0;
+ pvalue.ptr = NULL;
+ }
+
+ /* Save the parameters. */
+ if (pj_stricmp_alnum(&pname, &pjsip_ISUB_STR)==0) {
+ uri->isub_param = pvalue;
+ } else if (pj_stricmp_alnum(&pname, &pjsip_EXT_STR)==0) {
+ uri->ext_param = pvalue;
+ } else if (pj_stricmp_alnum(&pname, &pjsip_PH_CTX_STR)==0) {
+ uri->context = pvalue;
+ } else {
+ pjsip_param *param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ param->name = pname;
+ param->value = pvalue;
+ pj_list_insert_before(&uri->other_param, param);
+ }
+
+ } while (*scanner->curptr==';');
+ }
+
+ scanner->skip_ws = skip_ws;
+ pj_scan_skip_whitespace(scanner);
+ return uri;
+}
+
diff --git a/pjsip/src/pjsip/sip_tel_uri_wrap.cpp b/pjsip/src/pjsip/sip_tel_uri_wrap.cpp
new file mode 100644
index 0000000..d13fb52
--- /dev/null
+++ b/pjsip/src/pjsip/sip_tel_uri_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_tel_uri_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_tel_uri.c"
diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c
new file mode 100644
index 0000000..4b3dd12
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transaction.c
@@ -0,0 +1,3277 @@
+/* $Id: sip_transaction.c 4165 2012-06-14 09:04:20Z 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 <pjsip/sip_transaction.h>
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_event.h>
+#include <pjlib-util/errno.h>
+#include <pj/hash.h>
+#include <pj/pool.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/assert.h>
+#include <pj/guid.h>
+#include <pj/log.h>
+
+#define THIS_FILE "sip_transaction.c"
+
+#if 0
+#define TSX_TRACE_(expr) PJ_LOG(3,expr)
+#else
+#define TSX_TRACE_(expr)
+#endif
+
+/* When this macro is set, transaction will keep the hashed value
+ * so that future lookup (to unregister transaction) does not need
+ * to recalculate the hash again. It should gains a little bit of
+ * performance, so generally we'd want this.
+ */
+#define PRECALC_HASH
+
+
+/* Defined in sip_util_statefull.c */
+extern pjsip_module mod_stateful_util;
+
+
+/*****************************************************************************
+ **
+ ** Declarations and static variable definitions section.
+ **
+ *****************************************************************************
+ **/
+/* Prototypes. */
+static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt);
+static pj_status_t mod_tsx_layer_start(void);
+static pj_status_t mod_tsx_layer_stop(void);
+static pj_status_t mod_tsx_layer_unload(void);
+static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata);
+
+/* Transaction layer module definition. */
+static struct mod_tsx_layer
+{
+ struct pjsip_module mod;
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pj_mutex_t *mutex;
+ pj_hash_table_t *htable;
+} mod_tsx_layer =
+{ {
+ NULL, NULL, /* List's prev and next. */
+ { "mod-tsx-layer", 13 }, /* Module name. */
+ -1, /* Module ID */
+ PJSIP_MOD_PRIORITY_TSX_LAYER, /* Priority. */
+ mod_tsx_layer_load, /* load(). */
+ mod_tsx_layer_start, /* start() */
+ mod_tsx_layer_stop, /* stop() */
+ mod_tsx_layer_unload, /* unload() */
+ mod_tsx_layer_on_rx_request, /* on_rx_request() */
+ mod_tsx_layer_on_rx_response, /* on_rx_response() */
+ NULL
+ }
+};
+
+/* Thread Local Storage ID for transaction lock */
+static long pjsip_tsx_lock_tls_id;
+
+/* Transaction state names */
+static const char *state_str[] =
+{
+ "Null",
+ "Calling",
+ "Trying",
+ "Proceeding",
+ "Completed",
+ "Confirmed",
+ "Terminated",
+ "Destroyed",
+};
+
+/* Role names */
+static const char *role_name[] =
+{
+ "UAC",
+ "UAS"
+};
+
+/* Transport flag. */
+enum
+{
+ TSX_HAS_PENDING_TRANSPORT = 1,
+ TSX_HAS_PENDING_RESCHED = 2,
+ TSX_HAS_PENDING_SEND = 4,
+ TSX_HAS_PENDING_DESTROY = 8,
+ TSX_HAS_RESOLVED_SERVER = 16,
+};
+
+/* Transaction lock. */
+typedef struct tsx_lock_data {
+ struct tsx_lock_data *prev;
+ pjsip_transaction *tsx;
+ int is_alive;
+} tsx_lock_data;
+
+
+/* Timer timeout value constants */
+static pj_time_val t1_timer_val = { PJSIP_T1_TIMEOUT/1000,
+ PJSIP_T1_TIMEOUT%1000 };
+static pj_time_val t2_timer_val = { PJSIP_T2_TIMEOUT/1000,
+ PJSIP_T2_TIMEOUT%1000 };
+static pj_time_val t4_timer_val = { PJSIP_T4_TIMEOUT/1000,
+ PJSIP_T4_TIMEOUT%1000 };
+static pj_time_val td_timer_val = { PJSIP_TD_TIMEOUT/1000,
+ PJSIP_TD_TIMEOUT%1000 };
+static pj_time_val timeout_timer_val = { (64*PJSIP_T1_TIMEOUT)/1000,
+ (64*PJSIP_T1_TIMEOUT)%1000 };
+
+#define TIMER_INACTIVE 0
+#define TIMER_ACTIVE 1
+
+
+/* Prototypes. */
+static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck);
+static pj_status_t unlock_tsx( pjsip_transaction *tsx,
+ struct tsx_lock_data *lck);
+static pj_status_t tsx_on_state_null( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_proceeding_uac( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx,
+ pjsip_event *event);
+static pj_status_t tsx_on_state_destroyed( pjsip_transaction *tsx,
+ pjsip_event *event);
+static void tsx_timer_callback( pj_timer_heap_t *theap,
+ pj_timer_entry *entry);
+static void tsx_tp_state_callback(
+ pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info);
+static pj_status_t tsx_create( pjsip_module *tsx_user,
+ pjsip_transaction **p_tsx);
+static pj_status_t tsx_destroy( pjsip_transaction *tsx );
+static void tsx_resched_retransmission( pjsip_transaction *tsx );
+static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched);
+static int tsx_send_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata);
+static void tsx_update_transport( pjsip_transaction *tsx,
+ pjsip_transport *tp);
+
+
+/* State handlers for UAC, indexed by state */
+static int (*tsx_state_handler_uac[PJSIP_TSX_STATE_MAX])(pjsip_transaction *,
+ pjsip_event *) =
+{
+ &tsx_on_state_null,
+ &tsx_on_state_calling,
+ NULL,
+ &tsx_on_state_proceeding_uac,
+ &tsx_on_state_completed_uac,
+ &tsx_on_state_confirmed,
+ &tsx_on_state_terminated,
+ &tsx_on_state_destroyed,
+};
+
+/* State handlers for UAS */
+static int (*tsx_state_handler_uas[PJSIP_TSX_STATE_MAX])(pjsip_transaction *,
+ pjsip_event *) =
+{
+ &tsx_on_state_null,
+ NULL,
+ &tsx_on_state_trying,
+ &tsx_on_state_proceeding_uas,
+ &tsx_on_state_completed_uas,
+ &tsx_on_state_confirmed,
+ &tsx_on_state_terminated,
+ &tsx_on_state_destroyed,
+};
+
+/*****************************************************************************
+ **
+ ** Utilities
+ **
+ *****************************************************************************
+ */
+/*
+ * Get transaction state name.
+ */
+PJ_DEF(const char *) pjsip_tsx_state_str(pjsip_tsx_state_e state)
+{
+ return state_str[state];
+}
+
+/*
+ * Get the role name.
+ */
+PJ_DEF(const char *) pjsip_role_name(pjsip_role_e role)
+{
+ return role_name[role];
+}
+
+
+/*
+ * Create transaction key for RFC2543 compliant messages, which don't have
+ * unique branch parameter in the top most Via header.
+ *
+ * INVITE requests matches a transaction if the following attributes
+ * match the original request:
+ * - Request-URI
+ * - To tag
+ * - From tag
+ * - Call-ID
+ * - CSeq
+ * - top Via header
+ *
+ * CANCEL matching is done similarly as INVITE, except:
+ * - CSeq method will differ
+ * - To tag is not matched.
+ *
+ * ACK matching is done similarly, except that:
+ * - method of the CSeq will differ,
+ * - To tag is matched to the response sent by the server transaction.
+ *
+ * The transaction key is constructed from the common components of above
+ * components. Additional comparison is needed to fully match a transaction.
+ */
+static pj_status_t create_tsx_key_2543( pj_pool_t *pool,
+ pj_str_t *str,
+ pjsip_role_e role,
+ const pjsip_method *method,
+ const pjsip_rx_data *rdata )
+{
+#define SEPARATOR '$'
+ char *key, *p, *end;
+ int len;
+ pj_size_t len_required;
+ pjsip_uri *req_uri;
+ pj_str_t *host;
+
+ PJ_ASSERT_RETURN(pool && str && method && rdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.via, PJSIP_EMISSINGHDR);
+ PJ_ASSERT_RETURN(rdata->msg_info.cseq, PJSIP_EMISSINGHDR);
+ PJ_ASSERT_RETURN(rdata->msg_info.from, PJSIP_EMISSINGHDR);
+
+ host = &rdata->msg_info.via->sent_by.host;
+ req_uri = (pjsip_uri*)rdata->msg_info.msg->line.req.uri;
+
+ /* Calculate length required. */
+ len_required = 9 + /* CSeq number */
+ rdata->msg_info.from->tag.slen + /* From tag. */
+ rdata->msg_info.cid->id.slen + /* Call-ID */
+ host->slen + /* Via host. */
+ 9 + /* Via port. */
+ 16; /* Separator+Allowance. */
+ key = p = (char*) pj_pool_alloc(pool, len_required);
+ end = p + len_required;
+
+ /* Add role. */
+ *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's');
+ *p++ = SEPARATOR;
+
+ /* Add method, except when method is INVITE or ACK. */
+ if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
+ pj_memcpy(p, method->name.ptr, method->name.slen);
+ p += method->name.slen;
+ *p++ = '$';
+ }
+
+ /* Add CSeq (only the number). */
+ len = pj_utoa(rdata->msg_info.cseq->cseq, p);
+ p += len;
+ *p++ = SEPARATOR;
+
+ /* Add From tag. */
+ len = rdata->msg_info.from->tag.slen;
+ pj_memcpy( p, rdata->msg_info.from->tag.ptr, len);
+ p += len;
+ *p++ = SEPARATOR;
+
+ /* Add Call-ID. */
+ len = rdata->msg_info.cid->id.slen;
+ pj_memcpy( p, rdata->msg_info.cid->id.ptr, len );
+ p += len;
+ *p++ = SEPARATOR;
+
+ /* Add top Via header.
+ * We don't really care whether the port contains the real port (because
+ * it can be omited if default port is used). Anyway this function is
+ * only used to match request retransmission, and we expect that the
+ * request retransmissions will contain the same port.
+ */
+ pj_memcpy(p, host->ptr, host->slen);
+ p += host->slen;
+ *p++ = ':';
+
+ len = pj_utoa(rdata->msg_info.via->sent_by.port, p);
+ p += len;
+ *p++ = SEPARATOR;
+
+ *p++ = '\0';
+
+ /* Done. */
+ str->ptr = key;
+ str->slen = p-key;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create transaction key for RFC3161 compliant system.
+ */
+static pj_status_t create_tsx_key_3261( pj_pool_t *pool,
+ pj_str_t *key,
+ pjsip_role_e role,
+ const pjsip_method *method,
+ const pj_str_t *branch)
+{
+ char *p;
+
+ PJ_ASSERT_RETURN(pool && key && method && branch, PJ_EINVAL);
+
+ p = key->ptr = (char*)
+ pj_pool_alloc(pool, branch->slen + method->name.slen + 4 );
+
+ /* Add role. */
+ *p++ = (char)(role==PJSIP_ROLE_UAC ? 'c' : 's');
+ *p++ = SEPARATOR;
+
+ /* Add method, except when method is INVITE or ACK. */
+ if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
+ pj_memcpy(p, method->name.ptr, method->name.slen);
+ p += method->name.slen;
+ *p++ = '$';
+ }
+
+ /* Add branch ID. */
+ pj_memcpy(p, branch->ptr, branch->slen);
+ p += branch->slen;
+
+ /* Set length */
+ key->slen = p - key->ptr;
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Create key from the incoming data, to be used to search the transaction
+ * in the transaction hash table.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_create_key( pj_pool_t *pool, pj_str_t *key,
+ pjsip_role_e role,
+ const pjsip_method *method,
+ const pjsip_rx_data *rdata)
+{
+ pj_str_t rfc3261_branch = {PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN};
+
+
+ /* Get the branch parameter in the top-most Via.
+ * If branch parameter is started with "z9hG4bK", then the message was
+ * generated by agent compliant with RFC3261. Otherwise, it will be
+ * handled as RFC2543.
+ */
+ const pj_str_t *branch = &rdata->msg_info.via->branch_param;
+
+ if (pj_strncmp(branch,&rfc3261_branch,PJSIP_RFC3261_BRANCH_LEN)==0) {
+
+ /* Create transaction key. */
+ return create_tsx_key_3261(pool, key, role, method, branch);
+
+ } else {
+ /* Create the key for the message. This key will be matched up
+ * with the transaction key. For RFC2563 transactions, the
+ * transaction key was created by the same function, so it will
+ * match the message.
+ */
+ return create_tsx_key_2543( pool, key, role, method, rdata );
+ }
+}
+
+/*****************************************************************************
+ **
+ ** Transaction layer module
+ **
+ *****************************************************************************
+ **/
+/*
+ * Create transaction layer module and registers it to the endpoint.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_layer_init_module(pjsip_endpoint *endpt)
+{
+ pj_pool_t *pool;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(mod_tsx_layer.endpt==NULL, PJ_EINVALIDOP);
+
+ /* Initialize timer values */
+ t1_timer_val.sec = pjsip_cfg()->tsx.t1 / 1000;
+ t1_timer_val.msec = pjsip_cfg()->tsx.t1 % 1000;
+ t2_timer_val.sec = pjsip_cfg()->tsx.t2 / 1000;
+ t2_timer_val.msec = pjsip_cfg()->tsx.t2 % 1000;
+ t4_timer_val.sec = pjsip_cfg()->tsx.t4 / 1000;
+ t4_timer_val.msec = pjsip_cfg()->tsx.t4 % 1000;
+ td_timer_val.sec = pjsip_cfg()->tsx.td / 1000;
+ td_timer_val.msec = pjsip_cfg()->tsx.td % 1000;
+ /* Changed the initialization below to use td_timer_val instead, to enable
+ * customization to the timeout value.
+ */
+ //timeout_timer_val.sec = (64 * pjsip_cfg()->tsx.t1) / 1000;
+ //timeout_timer_val.msec = (64 * pjsip_cfg()->tsx.t1) % 1000;
+ timeout_timer_val = td_timer_val;
+
+ /* Initialize TLS ID for transaction lock. */
+ status = pj_thread_local_alloc(&pjsip_tsx_lock_tls_id);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_thread_local_set(pjsip_tsx_lock_tls_id, NULL);
+
+ /*
+ * Initialize transaction layer structure.
+ */
+
+ /* Create pool for the module. */
+ pool = pjsip_endpt_create_pool(endpt, "tsxlayer",
+ PJSIP_POOL_TSX_LAYER_LEN,
+ PJSIP_POOL_TSX_LAYER_INC );
+ if (!pool)
+ return PJ_ENOMEM;
+
+
+ /* Initialize some attributes. */
+ mod_tsx_layer.pool = pool;
+ mod_tsx_layer.endpt = endpt;
+
+
+ /* Create hash table. */
+ mod_tsx_layer.htable = pj_hash_create( pool, pjsip_cfg()->tsx.max_count );
+ if (!mod_tsx_layer.htable) {
+ pjsip_endpt_release_pool(endpt, pool);
+ return PJ_ENOMEM;
+ }
+
+ /* Create mutex. */
+ status = pj_mutex_create_recursive(pool, "tsxlayer", &mod_tsx_layer.mutex);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_release_pool(endpt, pool);
+ return status;
+ }
+
+ /*
+ * Register transaction layer module to endpoint.
+ */
+ status = pjsip_endpt_register_module( endpt, &mod_tsx_layer.mod );
+ if (status != PJ_SUCCESS) {
+ pj_mutex_destroy(mod_tsx_layer.mutex);
+ pjsip_endpt_release_pool(endpt, pool);
+ return status;
+ }
+
+ /* Register mod_stateful_util module (sip_util_statefull.c) */
+ status = pjsip_endpt_register_module(endpt, &mod_stateful_util);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the instance of transaction layer module.
+ */
+PJ_DEF(pjsip_module*) pjsip_tsx_layer_instance(void)
+{
+ return &mod_tsx_layer.mod;
+}
+
+
+/*
+ * Unregister and destroy transaction layer module.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_layer_destroy(void)
+{
+ /* Are we registered? */
+ PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, PJ_EINVALIDOP);
+
+ /* Unregister from endpoint.
+ * Clean-ups will be done in the unload() module callback.
+ */
+ return pjsip_endpt_unregister_module( mod_tsx_layer.endpt,
+ &mod_tsx_layer.mod);
+}
+
+
+/*
+ * Register the transaction to the hash table.
+ */
+static pj_status_t mod_tsx_layer_register_tsx( pjsip_transaction *tsx)
+{
+ pj_assert(tsx->transaction_key.slen != 0);
+
+ /* Lock hash table mutex. */
+ pj_mutex_lock(mod_tsx_layer.mutex);
+
+ /* Check if no transaction with the same key exists.
+ * Do not use PJ_ASSERT_RETURN since it evaluates the expression
+ * twice!
+ */
+ if(pj_hash_get(mod_tsx_layer.htable,
+ tsx->transaction_key.ptr,
+ tsx->transaction_key.slen,
+ NULL))
+ {
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+ PJ_LOG(2,(THIS_FILE,
+ "Unable to register %.*s transaction (key exists)",
+ (int)tsx->method.name.slen,
+ tsx->method.name.ptr));
+ return PJ_EEXISTS;
+ }
+
+ TSX_TRACE_((THIS_FILE,
+ "Transaction %p registered with hkey=0x%p and key=%.*s",
+ tsx, tsx->hashed_key, tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+ /* Register the transaction to the hash table. */
+#ifdef PRECALC_HASH
+ pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen, tsx->hashed_key, tsx);
+#else
+ pj_hash_set( tsx->pool, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen, 0, tsx);
+#endif
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Unregister the transaction from the hash table.
+ */
+static void mod_tsx_layer_unregister_tsx( pjsip_transaction *tsx)
+{
+ if (mod_tsx_layer.mod.id == -1) {
+ /* The transaction layer has been unregistered. This could happen
+ * if the transaction was pending on transport and the application
+ * is shutdown. See http://trac.pjsip.org/repos/ticket/1033. In
+ * this case just do nothing.
+ */
+ return;
+ }
+
+ pj_assert(tsx->transaction_key.slen != 0);
+ //pj_assert(tsx->state != PJSIP_TSX_STATE_NULL);
+
+ /* Lock hash table mutex. */
+ pj_mutex_lock(mod_tsx_layer.mutex);
+
+ /* Register the transaction to the hash table. */
+#ifdef PRECALC_HASH
+ pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen, tsx->hashed_key, NULL);
+#else
+ pj_hash_set( NULL, mod_tsx_layer.htable, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen, 0, NULL);
+#endif
+
+ TSX_TRACE_((THIS_FILE,
+ "Transaction %p unregistered, hkey=0x%p and key=%.*s",
+ tsx, tsx->hashed_key, tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+}
+
+
+/*
+ * Retrieve the current number of transactions currently registered in
+ * the hash table.
+ */
+PJ_DEF(unsigned) pjsip_tsx_layer_get_tsx_count(void)
+{
+ unsigned count;
+
+ /* Are we registered? */
+ PJ_ASSERT_RETURN(mod_tsx_layer.endpt!=NULL, 0);
+
+ pj_mutex_lock(mod_tsx_layer.mutex);
+ count = pj_hash_count(mod_tsx_layer.htable);
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+
+ return count;
+}
+
+
+/*
+ * Find a transaction.
+ */
+PJ_DEF(pjsip_transaction*) pjsip_tsx_layer_find_tsx( const pj_str_t *key,
+ pj_bool_t lock )
+{
+ pjsip_transaction *tsx;
+ pj_uint32_t hval = 0;
+
+ pj_mutex_lock(mod_tsx_layer.mutex);
+ tsx = (pjsip_transaction*)
+ pj_hash_get( mod_tsx_layer.htable, key->ptr, key->slen, &hval );
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+
+ TSX_TRACE_((THIS_FILE,
+ "Finding tsx with hkey=0x%p and key=%.*s: found %p",
+ hval, key->slen, key->ptr, tsx));
+
+ /* Race condition!
+ * Transaction may gets deleted before we have chance to lock it.
+ */
+ PJ_TODO(FIX_RACE_CONDITION_HERE);
+ if (tsx && lock)
+ pj_mutex_lock(tsx->mutex);
+
+ return tsx;
+}
+
+
+/* This module callback is called when module is being loaded by
+ * endpoint. It does nothing for this module.
+ */
+static pj_status_t mod_tsx_layer_load(pjsip_endpoint *endpt)
+{
+ PJ_UNUSED_ARG(endpt);
+ return PJ_SUCCESS;
+}
+
+
+/* This module callback is called when module is being started by
+ * endpoint. It does nothing for this module.
+ */
+static pj_status_t mod_tsx_layer_start(void)
+{
+ return PJ_SUCCESS;
+}
+
+
+/* This module callback is called when module is being stopped by
+ * endpoint.
+ */
+static pj_status_t mod_tsx_layer_stop(void)
+{
+ pj_hash_iterator_t it_buf, *it;
+
+ PJ_LOG(4,(THIS_FILE, "Stopping transaction layer module"));
+
+ pj_mutex_lock(mod_tsx_layer.mutex);
+
+ /* Destroy all transactions. */
+ it = pj_hash_first(mod_tsx_layer.htable, &it_buf);
+ while (it) {
+ pjsip_transaction *tsx = (pjsip_transaction*)
+ pj_hash_this(mod_tsx_layer.htable, it);
+ pj_hash_iterator_t *next = pj_hash_next(mod_tsx_layer.htable, it);
+ if (tsx) {
+ pjsip_tsx_terminate(tsx, PJSIP_SC_SERVICE_UNAVAILABLE);
+ mod_tsx_layer_unregister_tsx(tsx);
+ tsx_destroy(tsx);
+ }
+ it = next;
+ }
+
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+
+ PJ_LOG(4,(THIS_FILE, "Stopped transaction layer module"));
+
+ return PJ_SUCCESS;
+}
+
+
+/* Destroy this module */
+static void tsx_layer_destroy(pjsip_endpoint *endpt)
+{
+ PJ_UNUSED_ARG(endpt);
+
+ /* Destroy mutex. */
+ pj_mutex_destroy(mod_tsx_layer.mutex);
+
+ /* Release pool. */
+ pjsip_endpt_release_pool(mod_tsx_layer.endpt, mod_tsx_layer.pool);
+
+ /* Free TLS */
+ pj_thread_local_free(pjsip_tsx_lock_tls_id);
+
+ /* Mark as unregistered. */
+ mod_tsx_layer.endpt = NULL;
+
+ PJ_LOG(4,(THIS_FILE, "Transaction layer module destroyed"));
+}
+
+
+/* This module callback is called when module is being unloaded by
+ * endpoint.
+ */
+static pj_status_t mod_tsx_layer_unload(void)
+{
+ /* Only self destroy when there's no transaction in the table.
+ * Transaction may refuse to destroy when it has pending
+ * transmission. If we destroy the module now, application will
+ * crash when the pending transaction finally got error response
+ * from transport and when it tries to unregister itself.
+ */
+ if (pj_hash_count(mod_tsx_layer.htable) != 0) {
+ if (pjsip_endpt_atexit(mod_tsx_layer.endpt, &tsx_layer_destroy) !=
+ PJ_SUCCESS)
+ {
+ PJ_LOG(3,(THIS_FILE, "Failed to register transaction layer "
+ "module destroy."));
+ }
+ return PJ_EBUSY;
+ }
+
+ tsx_layer_destroy(mod_tsx_layer.endpt);
+
+ return PJ_SUCCESS;
+}
+
+
+/* This module callback is called when endpoint has received an
+ * incoming request message.
+ */
+static pj_bool_t mod_tsx_layer_on_rx_request(pjsip_rx_data *rdata)
+{
+ pj_str_t key;
+ pj_uint32_t hval = 0;
+ pjsip_transaction *tsx;
+
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAS,
+ &rdata->msg_info.cseq->method, rdata);
+
+ /* Find transaction. */
+ pj_mutex_lock( mod_tsx_layer.mutex );
+
+ tsx = (pjsip_transaction*)
+ pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval );
+
+
+ TSX_TRACE_((THIS_FILE,
+ "Finding tsx for request, hkey=0x%p and key=%.*s, found %p",
+ hval, key.slen, key.ptr, tsx));
+
+
+ if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ /* Transaction not found.
+ * Reject the request so that endpoint passes the request to
+ * upper layer modules.
+ */
+ pj_mutex_unlock( mod_tsx_layer.mutex);
+ return PJ_FALSE;
+ }
+
+ /* Unlock hash table. */
+ pj_mutex_unlock( mod_tsx_layer.mutex );
+
+ /* Race condition!
+ * Transaction may gets deleted before we have chance to lock it
+ * in pjsip_tsx_recv_msg().
+ */
+ PJ_TODO(FIX_RACE_CONDITION_HERE);
+
+ /* Pass the message to the transaction. */
+ pjsip_tsx_recv_msg(tsx, rdata );
+
+ return PJ_TRUE;
+}
+
+
+/* This module callback is called when endpoint has received an
+ * incoming response message.
+ */
+static pj_bool_t mod_tsx_layer_on_rx_response(pjsip_rx_data *rdata)
+{
+ pj_str_t key;
+ pj_uint32_t hval = 0;
+ pjsip_transaction *tsx;
+
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key, PJSIP_ROLE_UAC,
+ &rdata->msg_info.cseq->method, rdata);
+
+ /* Find transaction. */
+ pj_mutex_lock( mod_tsx_layer.mutex );
+
+ tsx = (pjsip_transaction*)
+ pj_hash_get( mod_tsx_layer.htable, key.ptr, key.slen, &hval );
+
+
+ TSX_TRACE_((THIS_FILE,
+ "Finding tsx for response, hkey=0x%p and key=%.*s, found %p",
+ hval, key.slen, key.ptr, tsx));
+
+
+ if (tsx == NULL || tsx->state == PJSIP_TSX_STATE_TERMINATED) {
+ /* Transaction not found.
+ * Reject the request so that endpoint passes the request to
+ * upper layer modules.
+ */
+ pj_mutex_unlock( mod_tsx_layer.mutex);
+ return PJ_FALSE;
+ }
+
+ /* Unlock hash table. */
+ pj_mutex_unlock( mod_tsx_layer.mutex );
+
+ /* Race condition!
+ * Transaction may gets deleted before we have chance to lock it
+ * in pjsip_tsx_recv_msg().
+ */
+ PJ_TODO(FIX_RACE_CONDITION_HERE);
+
+ /* Pass the message to the transaction. */
+ pjsip_tsx_recv_msg(tsx, rdata );
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Get transaction instance in the rdata.
+ */
+PJ_DEF(pjsip_transaction*) pjsip_rdata_get_tsx( pjsip_rx_data *rdata )
+{
+ return (pjsip_transaction*)
+ rdata->endpt_info.mod_data[mod_tsx_layer.mod.id];
+}
+
+
+/*
+ * Dump transaction layer.
+ */
+PJ_DEF(void) pjsip_tsx_layer_dump(pj_bool_t detail)
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ pj_hash_iterator_t itbuf, *it;
+
+ /* Lock mutex. */
+ pj_mutex_lock(mod_tsx_layer.mutex);
+
+ PJ_LOG(3, (THIS_FILE, "Dumping transaction table:"));
+ PJ_LOG(3, (THIS_FILE, " Total %d transactions",
+ pj_hash_count(mod_tsx_layer.htable)));
+
+ if (detail) {
+ it = pj_hash_first(mod_tsx_layer.htable, &itbuf);
+ if (it == NULL) {
+ PJ_LOG(3, (THIS_FILE, " - none - "));
+ } else {
+ while (it != NULL) {
+ pjsip_transaction *tsx = (pjsip_transaction*)
+ pj_hash_this(mod_tsx_layer.htable,it);
+
+ PJ_LOG(3, (THIS_FILE, " %s %s|%d|%s",
+ tsx->obj_name,
+ (tsx->last_tx?
+ pjsip_tx_data_get_info(tsx->last_tx):
+ "none"),
+ tsx->status_code,
+ pjsip_tsx_state_str(tsx->state)));
+
+ it = pj_hash_next(mod_tsx_layer.htable, it);
+ }
+ }
+ }
+
+ /* Unlock mutex. */
+ pj_mutex_unlock(mod_tsx_layer.mutex);
+#endif
+}
+
+/*****************************************************************************
+ **
+ ** Transaction
+ **
+ *****************************************************************************
+ **/
+/*
+ * Lock transaction and set the value of Thread Local Storage.
+ */
+static void lock_tsx(pjsip_transaction *tsx, struct tsx_lock_data *lck)
+{
+ struct tsx_lock_data *prev_data;
+
+ pj_mutex_lock(tsx->mutex);
+ prev_data = (struct tsx_lock_data *)
+ pj_thread_local_get(pjsip_tsx_lock_tls_id);
+ lck->prev = prev_data;
+ lck->tsx = tsx;
+ lck->is_alive = 1;
+ pj_thread_local_set(pjsip_tsx_lock_tls_id, lck);
+}
+
+
+/*
+ * Unlock transaction.
+ * This will selectively unlock the mutex ONLY IF the transaction has not been
+ * destroyed. The function knows whether the transaction has been destroyed
+ * because when transaction is destroyed the is_alive flag for the transaction
+ * will be set to zero.
+ */
+static pj_status_t unlock_tsx( pjsip_transaction *tsx,
+ struct tsx_lock_data *lck)
+{
+ pj_assert( (void*)pj_thread_local_get(pjsip_tsx_lock_tls_id) == lck);
+ pj_assert( lck->tsx == tsx );
+ pj_thread_local_set(pjsip_tsx_lock_tls_id, lck->prev);
+ if (lck->is_alive)
+ pj_mutex_unlock(tsx->mutex);
+
+ return lck->is_alive ? PJ_SUCCESS : PJSIP_ETSXDESTROYED;
+}
+
+
+/* Lock transaction for accessing the timeout timer only. */
+static void lock_timer(pjsip_transaction *tsx)
+{
+ pj_mutex_lock(tsx->mutex_b);
+}
+
+/* Unlock timer */
+static void unlock_timer(pjsip_transaction *tsx)
+{
+ pj_mutex_unlock(tsx->mutex_b);
+}
+
+/* Create and initialize basic transaction structure.
+ * This function is called by both UAC and UAS creation.
+ */
+static pj_status_t tsx_create( pjsip_module *tsx_user,
+ pjsip_transaction **p_tsx)
+{
+ pj_pool_t *pool;
+ pjsip_transaction *tsx;
+ pj_status_t status;
+
+ pool = pjsip_endpt_create_pool( mod_tsx_layer.endpt, "tsx",
+ PJSIP_POOL_TSX_LEN, PJSIP_POOL_TSX_INC );
+ if (!pool)
+ return PJ_ENOMEM;
+
+ tsx = PJ_POOL_ZALLOC_T(pool, pjsip_transaction);
+ tsx->pool = pool;
+ tsx->tsx_user = tsx_user;
+ tsx->endpt = mod_tsx_layer.endpt;
+
+ pj_ansi_snprintf(tsx->obj_name, sizeof(tsx->obj_name),
+ "tsx%p", tsx);
+ pj_memcpy(pool->obj_name, tsx->obj_name, sizeof(pool->obj_name));
+
+ tsx->handle_200resp = 1;
+ tsx->retransmit_timer.id = 0;
+ tsx->retransmit_timer.user_data = tsx;
+ tsx->retransmit_timer.cb = &tsx_timer_callback;
+ tsx->timeout_timer.id = 0;
+ tsx->timeout_timer.user_data = tsx;
+ tsx->timeout_timer.cb = &tsx_timer_callback;
+
+ status = pj_mutex_create_recursive(pool, tsx->obj_name, &tsx->mutex);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool);
+ return status;
+ }
+
+ status = pj_mutex_create_simple(pool, tsx->obj_name, &tsx->mutex_b);
+ if (status != PJ_SUCCESS) {
+ pj_mutex_destroy(tsx->mutex);
+ pjsip_endpt_release_pool(mod_tsx_layer.endpt, pool);
+ return status;
+ }
+
+ *p_tsx = tsx;
+ return PJ_SUCCESS;
+}
+
+
+/* Destroy transaction. */
+static pj_status_t tsx_destroy( pjsip_transaction *tsx )
+{
+ struct tsx_lock_data *lck;
+
+ /* Release the transport */
+ tsx_update_transport(tsx, NULL);
+
+ /* Decrement reference counter in transport selector */
+ pjsip_tpselector_dec_ref(&tsx->tp_sel);
+
+ /* Free last transmitted message. */
+ if (tsx->last_tx) {
+ pjsip_tx_data_dec_ref( tsx->last_tx );
+ tsx->last_tx = NULL;
+ }
+ /* Cancel timeout timer. */
+ if (tsx->timeout_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = 0;
+ }
+ /* Cancel retransmission timer. */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* Clear some pending flags. */
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED | TSX_HAS_PENDING_SEND);
+
+ /* Refuse to destroy transaction if it has pending resolving. */
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_DESTROY;
+ tsx->tsx_user = NULL;
+ PJ_LOG(4,(tsx->obj_name, "Will destroy later because transport is "
+ "in progress"));
+ return PJ_EBUSY;
+ }
+
+ /* Clear TLS, so that mutex will not be unlocked */
+ lck = (struct tsx_lock_data*) pj_thread_local_get(pjsip_tsx_lock_tls_id);
+ while (lck) {
+ if (lck->tsx == tsx) {
+ lck->is_alive = 0;
+ }
+ lck = lck->prev;
+ }
+
+ pj_mutex_destroy(tsx->mutex_b);
+ pj_mutex_destroy(tsx->mutex);
+
+ PJ_LOG(5,(tsx->obj_name, "Transaction destroyed!"));
+
+ pjsip_endpt_release_pool(tsx->endpt, tsx->pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback when timer expires.
+ */
+static void tsx_timer_callback( pj_timer_heap_t *theap, pj_timer_entry *entry)
+{
+ pjsip_event event;
+ pjsip_transaction *tsx = (pjsip_transaction*) entry->user_data;
+ struct tsx_lock_data lck;
+
+ PJ_UNUSED_ARG(theap);
+
+ entry->id = 0;
+
+ PJ_LOG(5,(tsx->obj_name, "%s timer event",
+ (entry==&tsx->retransmit_timer ? "Retransmit":"Timeout")));
+ pj_log_push_indent();
+
+
+ PJSIP_EVENT_INIT_TIMER(event, entry);
+
+ /* Dispatch event to transaction. */
+ lock_tsx(tsx, &lck);
+ (*tsx->state_handler)(tsx, &event);
+ unlock_tsx(tsx, &lck);
+
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Set transaction state, and inform TU about the transaction state change.
+ */
+static void tsx_set_state( pjsip_transaction *tsx,
+ pjsip_tsx_state_e state,
+ pjsip_event_id_e event_src_type,
+ void *event_src )
+{
+ pjsip_tsx_state_e prev_state = tsx->state;
+
+ /* New state must be greater than previous state */
+ pj_assert(state >= tsx->state);
+
+ PJ_LOG(5, (tsx->obj_name, "State changed from %s to %s, event=%s",
+ state_str[tsx->state], state_str[state],
+ pjsip_event_str(event_src_type)));
+ pj_log_push_indent();
+
+ /* Change state. */
+ tsx->state = state;
+
+ /* Update the state handlers. */
+ if (tsx->role == PJSIP_ROLE_UAC) {
+ tsx->state_handler = tsx_state_handler_uac[state];
+ } else {
+ tsx->state_handler = tsx_state_handler_uas[state];
+ }
+
+ /* Before informing TU about state changed, inform TU about
+ * rx event.
+ */
+ if (event_src_type==PJSIP_EVENT_RX_MSG && tsx->tsx_user) {
+ pjsip_rx_data *rdata = (pjsip_rx_data*) event_src;
+
+ pj_assert(rdata != NULL);
+
+ if (rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG &&
+ tsx->tsx_user->on_rx_response)
+ {
+ (*tsx->tsx_user->on_rx_response)(rdata);
+ }
+
+ }
+
+ /* Inform TU about state changed. */
+ if (tsx->tsx_user && tsx->tsx_user->on_tsx_state) {
+ pjsip_event e;
+ PJSIP_EVENT_INIT_TSX_STATE(e, tsx, event_src_type, event_src,
+ prev_state);
+ (*tsx->tsx_user->on_tsx_state)(tsx, &e);
+ }
+
+
+ /* When the transaction is terminated, release transport, and free the
+ * saved last transmitted message.
+ */
+ if (state == PJSIP_TSX_STATE_TERMINATED) {
+ pj_time_val timeout = {0, 0};
+
+ /* If we're still waiting for a message to be sent.. */
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ /* Disassociate ourselves from the outstanding transmit data
+ * so that when the send callback is called we will be able
+ * to ignore that (otherwise we'll get assertion, see
+ * http://trac.pjsip.org/repos/ticket/1033)
+ */
+ if (tsx->pending_tx) {
+ tsx->pending_tx->mod_data[mod_tsx_layer.mod.id] = NULL;
+ tsx->pending_tx = NULL;
+ }
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT);
+ }
+
+ lock_timer(tsx);
+
+ /* Cancel timeout timer. */
+ if (tsx->timeout_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = 0;
+ }
+
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+
+ unlock_timer(tsx);
+
+ } else if (state == PJSIP_TSX_STATE_DESTROYED) {
+
+ /* Unregister transaction. */
+ mod_tsx_layer_unregister_tsx(tsx);
+
+ /* Destroy transaction. */
+ tsx_destroy(tsx);
+ }
+
+ pj_log_pop_indent();
+}
+
+
+/*
+ * Create, initialize, and register UAC transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_create_uac( pjsip_module *tsx_user,
+ pjsip_tx_data *tdata,
+ pjsip_transaction **p_tsx)
+{
+ pjsip_transaction *tsx;
+ pjsip_msg *msg;
+ pjsip_cseq_hdr *cseq;
+ pjsip_via_hdr *via;
+ pjsip_host_info dst_info;
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ /* Validate arguments. */
+ PJ_ASSERT_RETURN(tdata && tdata->msg && p_tsx, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Method MUST NOT be ACK! */
+ PJ_ASSERT_RETURN(tdata->msg->line.req.method.id != PJSIP_ACK_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Keep shortcut */
+ msg = tdata->msg;
+
+ /* Make sure CSeq header is present. */
+ cseq = (pjsip_cseq_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL);
+ if (!cseq) {
+ pj_assert(!"CSeq header not present in outgoing message!");
+ return PJSIP_EMISSINGHDR;
+ }
+
+
+ /* Create transaction instance. */
+ status = tsx_create( tsx_user, &tsx);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Lock transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Role is UAC. */
+ tsx->role = PJSIP_ROLE_UAC;
+
+ /* Save method. */
+ pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method);
+
+ /* Save CSeq. */
+ tsx->cseq = cseq->cseq;
+
+ /* Generate Via header if it doesn't exist. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_VIA, NULL);
+ if (via == NULL) {
+ via = pjsip_via_hdr_create(tdata->pool);
+ pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*) via);
+ }
+
+ /* Generate branch parameter if it doesn't exist. */
+ if (via->branch_param.slen == 0) {
+ pj_str_t tmp;
+ via->branch_param.ptr = (char*)
+ pj_pool_alloc(tsx->pool, PJSIP_MAX_BRANCH_LEN);
+ via->branch_param.slen = PJSIP_MAX_BRANCH_LEN;
+ pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN);
+ tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2;
+ *(tmp.ptr-2) = 80; *(tmp.ptr-1) = 106;
+ pj_generate_unique_string( &tmp );
+
+ /* Save branch parameter. */
+ tsx->branch = via->branch_param;
+
+ } else {
+ /* Copy branch parameter. */
+ pj_strdup(tsx->pool, &tsx->branch, &via->branch_param);
+ }
+
+ /* Generate transaction key. */
+ create_tsx_key_3261( tsx->pool, &tsx->transaction_key,
+ PJSIP_ROLE_UAC, &tsx->method,
+ &via->branch_param);
+
+ /* Calculate hashed key value. */
+#ifdef PRECALC_HASH
+ tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen);
+#endif
+
+ PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+ /* Begin with State_Null.
+ * Manually set-up the state becase we don't want to call the callback.
+ */
+ tsx->state = PJSIP_TSX_STATE_NULL;
+ tsx->state_handler = &tsx_on_state_null;
+
+ /* Save the message. */
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref(tsx->last_tx);
+
+ /* Determine whether reliable transport should be used initially.
+ * This will be updated whenever transport has changed.
+ */
+ status = pjsip_get_request_dest(tdata, &dst_info);
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+ tsx->is_reliable = (dst_info.flag & PJSIP_TRANSPORT_RELIABLE);
+
+ /* Register transaction to hash table. */
+ status = mod_tsx_layer_register_tsx(tsx);
+ if (status != PJ_SUCCESS) {
+ /* The assertion is removed by #1090:
+ pj_assert(!"Bug in branch_param generator (i.e. not unique)");
+ */
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+
+
+ /* Unlock transaction and return. */
+ unlock_tsx(tsx, &lck);
+
+ pj_log_push_indent();
+ PJ_LOG(5,(tsx->obj_name, "Transaction created for %s",
+ pjsip_tx_data_get_info(tdata)));
+ pj_log_pop_indent();
+
+ *p_tsx = tsx;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Create, initialize, and register UAS transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_create_uas( pjsip_module *tsx_user,
+ pjsip_rx_data *rdata,
+ pjsip_transaction **p_tsx)
+{
+ pjsip_transaction *tsx;
+ pjsip_msg *msg;
+ pj_str_t *branch;
+ pjsip_cseq_hdr *cseq;
+ pj_status_t status;
+ struct tsx_lock_data lck;
+
+ /* Validate arguments. */
+ PJ_ASSERT_RETURN(rdata && rdata->msg_info.msg && p_tsx, PJ_EINVAL);
+
+ /* Keep shortcut to message */
+ msg = rdata->msg_info.msg;
+
+ /* Make sure this is a request message. */
+ PJ_ASSERT_RETURN(msg->type == PJSIP_REQUEST_MSG, PJSIP_ENOTREQUESTMSG);
+
+ /* Make sure method is not ACK */
+ PJ_ASSERT_RETURN(msg->line.req.method.id != PJSIP_ACK_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Make sure CSeq header is present. */
+ cseq = rdata->msg_info.cseq;
+ if (!cseq)
+ return PJSIP_EMISSINGHDR;
+
+ /* Make sure Via header is present. */
+ if (rdata->msg_info.via == NULL)
+ return PJSIP_EMISSINGHDR;
+
+ /* Check that method in CSeq header match request method.
+ * Reference: PROTOS #1922
+ */
+ if (pjsip_method_cmp(&msg->line.req.method,
+ &rdata->msg_info.cseq->method) != 0)
+ {
+ PJ_LOG(4,(THIS_FILE, "Error: CSeq header contains different "
+ "method than the request line"));
+ return PJSIP_EINVALIDHDR;
+ }
+
+ /*
+ * Create transaction instance.
+ */
+ status = tsx_create( tsx_user, &tsx);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Lock transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Role is UAS */
+ tsx->role = PJSIP_ROLE_UAS;
+
+ /* Save method. */
+ pjsip_method_copy( tsx->pool, &tsx->method, &msg->line.req.method);
+
+ /* Save CSeq */
+ tsx->cseq = cseq->cseq;
+
+ /* Get transaction key either from branch for RFC3261 message, or
+ * create transaction key.
+ */
+ status = pjsip_tsx_create_key(tsx->pool, &tsx->transaction_key,
+ PJSIP_ROLE_UAS, &tsx->method, rdata);
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+
+ /* Calculate hashed key value. */
+#ifdef PRECALC_HASH
+ tsx->hashed_key = pj_hash_calc(0, tsx->transaction_key.ptr,
+ tsx->transaction_key.slen);
+#endif
+
+ /* Duplicate branch parameter for transaction. */
+ branch = &rdata->msg_info.via->branch_param;
+ pj_strdup(tsx->pool, &tsx->branch, branch);
+
+ PJ_LOG(6, (tsx->obj_name, "tsx_key=%.*s", tsx->transaction_key.slen,
+ tsx->transaction_key.ptr));
+
+
+ /* Begin with state NULL.
+ * Manually set-up the state becase we don't want to call the callback.
+ */
+ tsx->state = PJSIP_TSX_STATE_NULL;
+ tsx->state_handler = &tsx_on_state_null;
+
+ /* Get response address. */
+ status = pjsip_get_response_addr( tsx->pool, rdata, &tsx->res_addr );
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+
+ /* If it's decided that we should use current transport, keep the
+ * transport.
+ */
+ if (tsx->res_addr.transport) {
+ tsx_update_transport(tsx, tsx->res_addr.transport);
+ pj_memcpy(&tsx->addr, &tsx->res_addr.addr, tsx->res_addr.addr_len);
+ tsx->addr_len = tsx->res_addr.addr_len;
+ tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport);
+ } else {
+ tsx->is_reliable =
+ (tsx->res_addr.dst_host.flag & PJSIP_TRANSPORT_RELIABLE);
+ }
+
+
+ /* Register the transaction. */
+ status = mod_tsx_layer_register_tsx(tsx);
+ if (status != PJ_SUCCESS) {
+ unlock_tsx(tsx, &lck);
+ tsx_destroy(tsx);
+ return status;
+ }
+
+ /* Put this transaction in rdata's mod_data. */
+ rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx;
+
+ /* Unlock transaction and return. */
+ unlock_tsx(tsx, &lck);
+
+ pj_log_push_indent();
+ PJ_LOG(5,(tsx->obj_name, "Transaction created for %s",
+ pjsip_rx_data_get_info(rdata)));
+ pj_log_pop_indent();
+
+
+ *p_tsx = tsx;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Bind transaction to a specific transport/listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_set_transport(pjsip_transaction *tsx,
+ const pjsip_tpselector *sel)
+{
+ struct tsx_lock_data lck;
+
+ /* Must be UAC transaction */
+ PJ_ASSERT_RETURN(tsx && sel, PJ_EINVAL);
+
+ /* Start locking the transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Decrement reference counter of previous transport selector */
+ pjsip_tpselector_dec_ref(&tsx->tp_sel);
+
+ /* Copy transport selector structure .*/
+ pj_memcpy(&tsx->tp_sel, sel, sizeof(*sel));
+
+ /* Increment reference counter */
+ pjsip_tpselector_add_ref(&tsx->tp_sel);
+
+ /* Unlock transaction. */
+ unlock_tsx(tsx, &lck);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Set transaction status code and reason.
+ */
+static void tsx_set_status_code(pjsip_transaction *tsx,
+ int code, const pj_str_t *reason)
+{
+ tsx->status_code = code;
+ if (reason)
+ pj_strdup(tsx->pool, &tsx->status_text, reason);
+ else
+ tsx->status_text = *pjsip_get_status_text(code);
+}
+
+
+/*
+ * Forcely terminate transaction.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_terminate( pjsip_transaction *tsx, int code )
+{
+ struct tsx_lock_data lck;
+
+ PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL);
+
+ PJ_LOG(5,(tsx->obj_name, "Request to terminate transaction"));
+
+ PJ_ASSERT_RETURN(code >= 200, PJ_EINVAL);
+
+ if (tsx->state >= PJSIP_TSX_STATE_TERMINATED)
+ return PJ_SUCCESS;
+
+ pj_log_push_indent();
+
+ lock_tsx(tsx, &lck);
+ tsx_set_status_code(tsx, code, NULL);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED, PJSIP_EVENT_USER, NULL);
+ unlock_tsx(tsx, &lck);
+
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Cease retransmission on the UAC transaction. The UAC transaction is
+ * still considered running, and it will complete when either final
+ * response is received or the transaction times out.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_stop_retransmit(pjsip_transaction *tsx)
+{
+ struct tsx_lock_data lck;
+
+ PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tsx->role == PJSIP_ROLE_UAC &&
+ tsx->method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVALIDOP);
+
+ PJ_LOG(5,(tsx->obj_name, "Request to stop retransmission"));
+
+ pj_log_push_indent();
+
+ lock_tsx(tsx, &lck);
+ /* Cancel retransmission timer. */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+ unlock_tsx(tsx, &lck);
+
+ pj_log_pop_indent();
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Start a timer to terminate transaction after the specified time
+ * has elapsed.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_set_timeout( pjsip_transaction *tsx,
+ unsigned millisec)
+{
+ pj_time_val timeout;
+
+ PJ_ASSERT_RETURN(tsx != NULL, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tsx->role == PJSIP_ROLE_UAC &&
+ tsx->method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Note: must not call lock_tsx() as that would introduce deadlock.
+ * See #1121.
+ */
+ lock_timer(tsx);
+
+ /* Transaction should normally not have final response, but as
+ * #1121 says there is a (tolerable) window of race condition
+ * where this might happen.
+ */
+ if (tsx->status_code >= 200 && tsx->timeout_timer.id != 0) {
+ /* Timeout is already set */
+ unlock_timer(tsx);
+ return PJ_EEXISTS;
+ }
+
+ if (tsx->timeout_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = 0;
+ }
+
+ timeout.sec = 0;
+ timeout.msec = millisec;
+ pj_time_val_normalize(&timeout);
+
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+
+
+ unlock_timer(tsx);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This function is called by TU to send a message.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_send_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata )
+{
+ pjsip_event event;
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ if (tdata == NULL)
+ tdata = tsx->last_tx;
+
+ PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP);
+
+ PJ_LOG(5,(tsx->obj_name, "Sending %s in state %s",
+ pjsip_tx_data_get_info(tdata),
+ state_str[tsx->state]));
+ pj_log_push_indent();
+
+ PJSIP_EVENT_INIT_TX_MSG(event, tdata);
+
+ /* Dispatch to transaction. */
+ lock_tsx(tsx, &lck);
+
+ /* Set transport selector to tdata */
+ pjsip_tx_data_set_transport(tdata, &tsx->tp_sel);
+
+ /* Dispatch to state handler */
+ status = (*tsx->state_handler)(tsx, &event);
+
+ unlock_tsx(tsx, &lck);
+
+ /* Only decrement reference counter when it returns success.
+ * (This is the specification from the .PDF design document).
+ */
+ if (status == PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ pj_log_pop_indent();
+
+ return status;
+}
+
+
+/*
+ * This function is called by endpoint when incoming message for the
+ * transaction is received.
+ */
+PJ_DEF(void) pjsip_tsx_recv_msg( pjsip_transaction *tsx,
+ pjsip_rx_data *rdata)
+{
+ pjsip_event event;
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ PJ_LOG(5,(tsx->obj_name, "Incoming %s in state %s",
+ pjsip_rx_data_get_info(rdata), state_str[tsx->state]));
+ pj_log_push_indent();
+
+ /* Put the transaction in the rdata's mod_data. */
+ rdata->endpt_info.mod_data[mod_tsx_layer.mod.id] = tsx;
+
+ /* Init event. */
+ PJSIP_EVENT_INIT_RX_MSG(event, rdata);
+
+ /* Dispatch to transaction. */
+ lock_tsx(tsx, &lck);
+ status = (*tsx->state_handler)(tsx, &event);
+ unlock_tsx(tsx, &lck);
+
+ pj_log_pop_indent();
+}
+
+
+/* Callback called by send message framework */
+static void send_msg_callback( pjsip_send_state *send_state,
+ pj_ssize_t sent, pj_bool_t *cont )
+{
+ pjsip_transaction *tsx = (pjsip_transaction*) send_state->token;
+ pjsip_tx_data *tdata = send_state->tdata;
+ struct tsx_lock_data lck;
+
+ /* Check if transaction has cancelled itself from this transmit
+ * notification (https://trac.pjsip.org/repos/ticket/1033).
+ * Also check if the transaction layer itself may have been shutdown
+ * (https://trac.pjsip.org/repos/ticket/1535)
+ */
+ if (mod_tsx_layer.mod.id < 0 ||
+ tdata->mod_data[mod_tsx_layer.mod.id] == NULL)
+ {
+ *cont = PJ_FALSE;
+ return;
+ }
+
+ /* Reset */
+ tdata->mod_data[mod_tsx_layer.mod.id] = NULL;
+ tsx->pending_tx = NULL;
+
+ lock_tsx(tsx, &lck);
+
+ if (sent > 0) {
+ /* Successfully sent! */
+ pj_assert(send_state->cur_transport != NULL);
+
+ if (tsx->transport != send_state->cur_transport) {
+ /* Update transport. */
+ tsx_update_transport(tsx, send_state->cur_transport);
+
+ /* Update remote address. */
+ tsx->addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len;
+ pj_memcpy(&tsx->addr,
+ &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr,
+ tsx->addr_len);
+
+ /* Update is_reliable flag. */
+ tsx->is_reliable = PJSIP_TRANSPORT_IS_RELIABLE(tsx->transport);
+ }
+
+ /* Clear pending transport flag. */
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT);
+
+ /* Mark that we have resolved the addresses. */
+ tsx->transport_flag |= TSX_HAS_RESOLVED_SERVER;
+
+ /* Pending destroy? */
+ if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY) {
+ tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED,
+ PJSIP_EVENT_UNKNOWN, NULL );
+ unlock_tsx(tsx, &lck);
+ return;
+ }
+
+ /* Need to transmit a message? */
+ if (tsx->transport_flag & TSX_HAS_PENDING_SEND) {
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_SEND);
+ tsx_send_msg(tsx, tsx->last_tx);
+ }
+
+ /* Need to reschedule retransmission? */
+ if (tsx->transport_flag & TSX_HAS_PENDING_RESCHED) {
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED);
+
+ /* Only update when transport turns out to be unreliable. */
+ if (!tsx->is_reliable) {
+ tsx_resched_retransmission(tsx);
+ }
+ }
+
+ } else {
+ /* Failed to send! */
+ pj_assert(sent != 0);
+
+ /* If transaction is using the same transport as the failed one,
+ * release the transport.
+ */
+ if (send_state->cur_transport==tsx->transport)
+ tsx_update_transport(tsx, NULL);
+
+ /* Also stop processing if transaction has been flagged with
+ * pending destroy (http://trac.pjsip.org/repos/ticket/906)
+ */
+ if ((!*cont) || (tsx->transport_flag & TSX_HAS_PENDING_DESTROY)) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pjsip_status_code sc;
+ pj_str_t err;
+
+ tsx->transport_err = -sent;
+
+ err =pj_strerror(-sent, errmsg, sizeof(errmsg));
+
+ PJ_LOG(2,(tsx->obj_name,
+ "Failed to send %s! err=%d (%s)",
+ pjsip_tx_data_get_info(send_state->tdata), -sent,
+ errmsg));
+
+ /* Clear pending transport flag. */
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_TRANSPORT);
+
+ /* Mark that we have resolved the addresses. */
+ tsx->transport_flag |= TSX_HAS_RESOLVED_SERVER;
+
+ /* Server resolution error is now mapped to 502 instead of 503,
+ * since with 503 normally client should try again.
+ * See http://trac.pjsip.org/repos/ticket/870
+ */
+ if (-sent==PJ_ERESOLVE || -sent==PJLIB_UTIL_EDNS_NXDOMAIN)
+ sc = PJSIP_SC_BAD_GATEWAY;
+ else
+ sc = PJSIP_SC_TSX_TRANSPORT_ERROR;
+
+ /* Terminate transaction, if it's not already terminated. */
+ tsx_set_status_code(tsx, sc, &err);
+ if (tsx->state != PJSIP_TSX_STATE_TERMINATED &&
+ tsx->state != PJSIP_TSX_STATE_DESTROYED)
+ {
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata);
+ }
+ /* Don't forget to destroy if we have pending destroy flag
+ * (http://trac.pjsip.org/repos/ticket/906)
+ */
+ else if (tsx->transport_flag & TSX_HAS_PENDING_DESTROY)
+ {
+ tsx_set_state( tsx, PJSIP_TSX_STATE_DESTROYED,
+ PJSIP_EVENT_TRANSPORT_ERROR, send_state->tdata);
+ }
+
+ } else {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_LOG(2,(tsx->obj_name,
+ "Temporary failure in sending %s, "
+ "will try next server. Err=%d (%s)",
+ pjsip_tx_data_get_info(send_state->tdata), -sent,
+ pj_strerror(-sent, errmsg, sizeof(errmsg)).ptr));
+
+ /* Reset retransmission count */
+ tsx->retransmit_count = 0;
+
+ /* And reset timeout timer */
+ if (tsx->timeout_timer.id) {
+ lock_timer(tsx);
+
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = TIMER_INACTIVE;
+
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout_timer_val);
+
+ unlock_timer(tsx);
+ }
+
+ /* Put again pending tdata */
+ tdata->mod_data[mod_tsx_layer.mod.id] = tsx;
+ tsx->pending_tx = tdata;
+ }
+ }
+
+ unlock_tsx(tsx, &lck);
+}
+
+
+/* Transport callback. */
+static void transport_callback(void *token, pjsip_tx_data *tdata,
+ pj_ssize_t sent)
+{
+ if (sent < 0) {
+ pjsip_transaction *tsx = (pjsip_transaction*) token;
+ struct tsx_lock_data lck;
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t err;
+
+ tsx->transport_err = -sent;
+
+ err = pj_strerror(-sent, errmsg, sizeof(errmsg));
+
+ PJ_LOG(2,(tsx->obj_name, "Transport failed to send %s! Err=%d (%s)",
+ pjsip_tx_data_get_info(tdata), -sent, errmsg));
+
+ lock_tsx(tsx, &lck);
+
+ /* Release transport. */
+ tsx_update_transport(tsx, NULL);
+
+ /* Terminate transaction. */
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, tdata );
+
+ unlock_tsx(tsx, &lck);
+ }
+}
+
+
+/*
+ * Callback when transport state changes.
+ */
+static void tsx_tp_state_callback( pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info)
+{
+ PJ_UNUSED_ARG(tp);
+
+ if (state == PJSIP_TP_STATE_DISCONNECTED) {
+ pjsip_transaction *tsx;
+ struct tsx_lock_data lck;
+
+ pj_assert(tp && info && info->user_data);
+
+ tsx = (pjsip_transaction*)info->user_data;
+
+ lock_tsx(tsx, &lck);
+
+ /* Terminate transaction when transport disconnected */
+ if (tsx->state < PJSIP_TSX_STATE_TERMINATED) {
+ pj_str_t err;
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ err = pj_strerror(info->status, errmsg, sizeof(errmsg));
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, NULL);
+ }
+
+ unlock_tsx(tsx, &lck);
+ }
+}
+
+
+/*
+ * Send message to the transport.
+ */
+static pj_status_t tsx_send_msg( pjsip_transaction *tsx,
+ pjsip_tx_data *tdata)
+{
+ pj_status_t status = PJ_SUCCESS;
+
+ PJ_ASSERT_RETURN(tsx && tdata, PJ_EINVAL);
+
+ /* Send later if transport is still pending. */
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_SEND;
+ return PJ_SUCCESS;
+ }
+
+ /* If we have the transport, send the message using that transport.
+ * Otherwise perform full transport resolution.
+ */
+ if (tsx->transport) {
+ status = pjsip_transport_send( tsx->transport, tdata, &tsx->addr,
+ tsx->addr_len, tsx,
+ &transport_callback);
+ if (status == PJ_EPENDING)
+ status = PJ_SUCCESS;
+
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ PJ_LOG(2,(tsx->obj_name,
+ "Error sending %s: Err=%d (%s)",
+ pjsip_tx_data_get_info(tdata), status,
+ pj_strerror(status, errmsg, sizeof(errmsg)).ptr));
+
+ /* On error, release transport to force using full transport
+ * resolution procedure.
+ */
+ tsx_update_transport(tsx, NULL);
+
+ tsx->addr_len = 0;
+ tsx->res_addr.transport = NULL;
+ tsx->res_addr.addr_len = 0;
+ } else {
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* We are here because we don't have transport, or we failed to send
+ * the message using existing transport. If we haven't resolved the
+ * server before, then begin the long process of resolving the server
+ * and send the message with possibly new server.
+ */
+ pj_assert(status != PJ_SUCCESS || tsx->transport == NULL);
+
+ /* If we have resolved the server, we treat the error as permanent error.
+ * Terminate transaction with transport error failure.
+ */
+ if (tsx->transport_flag & TSX_HAS_RESOLVED_SERVER) {
+
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_str_t err;
+
+ if (status == PJ_SUCCESS) {
+ pj_assert(!"Unexpected status!");
+ status = PJ_EUNKNOWN;
+ }
+
+ /* We have resolved the server!.
+ * Treat this as permanent transport error.
+ */
+ err = pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(2,(tsx->obj_name,
+ "Transport error, terminating transaction. "
+ "Err=%d (%s)",
+ status, errmsg));
+
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TRANSPORT_ERROR, &err);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TRANSPORT_ERROR, NULL );
+
+ return status;
+ }
+
+ /* Must add reference counter because the send request functions
+ * decrement the reference counter.
+ */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Also attach ourselves to the transmit data so that we'll be able
+ * to unregister ourselves from the send notification of this
+ * transmit data.
+ */
+ tdata->mod_data[mod_tsx_layer.mod.id] = tsx;
+ tsx->pending_tx = tdata;
+
+ /* Begin resolving destination etc to send the message. */
+ if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+
+ tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT;
+ status = pjsip_endpt_send_request_stateless(tsx->endpt, tdata, tsx,
+ &send_msg_callback);
+ if (status == PJ_EPENDING)
+ status = PJ_SUCCESS;
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ tdata->mod_data[mod_tsx_layer.mod.id] = NULL;
+ tsx->pending_tx = NULL;
+ }
+
+ /* Check if transaction is terminated. */
+ if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TERMINATED)
+ status = tsx->transport_err;
+
+ } else {
+
+ tsx->transport_flag |= TSX_HAS_PENDING_TRANSPORT;
+ status = pjsip_endpt_send_response( tsx->endpt, &tsx->res_addr,
+ tdata, tsx,
+ &send_msg_callback);
+ if (status == PJ_EPENDING)
+ status = PJ_SUCCESS;
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ tdata->mod_data[mod_tsx_layer.mod.id] = NULL;
+ tsx->pending_tx = NULL;
+ }
+
+ /* Check if transaction is terminated. */
+ if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TERMINATED)
+ status = tsx->transport_err;
+
+ }
+
+
+ return status;
+}
+
+
+/*
+ * Manually retransmit the last messagewithout updating the transaction state.
+ */
+PJ_DEF(pj_status_t) pjsip_tsx_retransmit_no_state(pjsip_transaction *tsx,
+ pjsip_tx_data *tdata)
+{
+ struct tsx_lock_data lck;
+ pj_status_t status;
+
+ lock_tsx(tsx, &lck);
+ if (tdata == NULL) {
+ tdata = tsx->last_tx;
+ }
+ status = tsx_send_msg(tsx, tdata);
+ unlock_tsx(tsx, &lck);
+
+ /* Only decrement reference counter when it returns success.
+ * (This is the specification from the .PDF design document).
+ */
+ if (status == PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ return status;
+}
+
+
+/*
+ * Retransmit last message sent.
+ */
+static void tsx_resched_retransmission( pjsip_transaction *tsx )
+{
+ pj_uint32_t msec_time;
+
+ pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0);
+
+ if (tsx->role==PJSIP_ROLE_UAC && tsx->status_code >= 100)
+ msec_time = pjsip_cfg()->tsx.t2;
+ else
+ msec_time = (1 << (tsx->retransmit_count)) * pjsip_cfg()->tsx.t1;
+
+ if (tsx->role == PJSIP_ROLE_UAC) {
+ pj_assert(tsx->status_code < 200);
+ /* Retransmission for non-INVITE transaction caps-off at T2 */
+ if (msec_time > pjsip_cfg()->tsx.t2 &&
+ tsx->method.id != PJSIP_INVITE_METHOD)
+ {
+ msec_time = pjsip_cfg()->tsx.t2;
+ }
+ } else {
+ /* For UAS, this can be retransmission of 2xx response for INVITE
+ * or non-100 1xx response.
+ */
+ if (tsx->status_code < 200) {
+ /* non-100 1xx retransmission is at 60 seconds */
+ msec_time = PJSIP_TSX_1XX_RETRANS_DELAY * 1000;
+ } else {
+ /* Retransmission of INVITE final response also caps-off at T2 */
+ pj_assert(tsx->status_code >= 200);
+ if (msec_time > pjsip_cfg()->tsx.t2)
+ msec_time = pjsip_cfg()->tsx.t2;
+ }
+ }
+
+ if (msec_time != 0) {
+ pj_time_val timeout;
+
+ timeout.sec = msec_time / 1000;
+ timeout.msec = msec_time % 1000;
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->retransmit_timer,
+ &timeout);
+ }
+}
+
+/*
+ * Retransmit last message sent.
+ */
+static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tsx->last_tx!=NULL, PJ_EBUG);
+
+ PJ_LOG(5,(tsx->obj_name, "Retransmiting %s, count=%d, restart?=%d",
+ pjsip_tx_data_get_info(tsx->last_tx),
+ tsx->retransmit_count, resched));
+
+ ++tsx->retransmit_count;
+
+ /* Restart timer T1 first before sending the message to ensure that
+ * retransmission timer is not engaged when loop transport is used.
+ */
+ if (resched) {
+ pj_assert(tsx->state != PJSIP_TSX_STATE_CONFIRMED);
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ tsx_resched_retransmission(tsx);
+ }
+ }
+
+ status = tsx_send_msg( tsx, tsx->last_tx);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static void tsx_update_transport( pjsip_transaction *tsx,
+ pjsip_transport *tp)
+{
+ pj_assert(tsx);
+
+ if (tsx->transport) {
+ pjsip_transport_remove_state_listener(tsx->transport,
+ tsx->tp_st_key, tsx);
+ pjsip_transport_dec_ref( tsx->transport );
+ tsx->transport = NULL;
+ }
+
+ if (tp) {
+ tsx->transport = tp;
+ pjsip_transport_add_ref(tp);
+ pjsip_transport_add_state_listener(tp, &tsx_tp_state_callback, tsx,
+ &tsx->tp_st_key);
+ }
+}
+
+
+/*
+ * Handler for events in state Null.
+ */
+static pj_status_t tsx_on_state_null( pjsip_transaction *tsx,
+ pjsip_event *event )
+{
+ pj_status_t status;
+
+ pj_assert(tsx->state == PJSIP_TSX_STATE_NULL);
+
+ if (tsx->role == PJSIP_ROLE_UAS) {
+
+ /* Set state to Trying. */
+ pj_assert(event->type == PJSIP_EVENT_RX_MSG &&
+ event->body.rx_msg.rdata->msg_info.msg->type ==
+ PJSIP_REQUEST_MSG);
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TRYING, PJSIP_EVENT_RX_MSG,
+ event->body.rx_msg.rdata);
+
+ } else {
+ pjsip_tx_data *tdata;
+
+ /* Must be transmit event.
+ * You may got this assertion when using loop transport with delay
+ * set to zero. That would cause on_rx_response() callback to be
+ * called before tsx_send_msg() has completed.
+ */
+ PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG, PJ_EBUG);
+
+ /* Get the txdata */
+ tdata = event->body.tx_msg.tdata;
+
+ /* Save the message for retransmission. */
+ if (tsx->last_tx && tsx->last_tx != tdata) {
+ pjsip_tx_data_dec_ref(tsx->last_tx);
+ tsx->last_tx = NULL;
+ }
+ if (tsx->last_tx != tdata) {
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref(tdata);
+ }
+
+ /* Send the message. */
+ status = tsx_send_msg( tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Start Timer B (or called timer F for non-INVITE) for transaction
+ * timeout.
+ */
+ lock_timer(tsx);
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout_timer_val);
+ unlock_timer(tsx);
+
+ /* Start Timer A (or timer E) for retransmission only if unreliable
+ * transport is being used.
+ */
+ if (!tsx->is_reliable) {
+ tsx->retransmit_count = 0;
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt, &tsx->retransmit_timer,
+ &t1_timer_val);
+ }
+ }
+
+ /* Move state. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_CALLING,
+ PJSIP_EVENT_TX_MSG, tdata);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * State Calling is for UAC after it sends request but before any responses
+ * is received.
+ */
+static pj_status_t tsx_on_state_calling( pjsip_transaction *tsx,
+ pjsip_event *event )
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_CALLING);
+ pj_assert(tsx->role == PJSIP_ROLE_UAC);
+
+ if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->retransmit_timer)
+ {
+ pj_status_t status;
+
+ /* Retransmit the request. */
+ status = tsx_retransmit( tsx, 1 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->timeout_timer)
+ {
+
+ /* Cancel retransmission timer. */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED);
+
+ /* Set status code */
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL);
+
+ /* Inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer);
+
+ /* Transaction is destroyed */
+ //return PJSIP_ETSXDESTROYED;
+
+ } else if (event->type == PJSIP_EVENT_RX_MSG) {
+ pjsip_msg *msg;
+ int code;
+
+ /* Get message instance */
+ msg = event->body.rx_msg.rdata->msg_info.msg;
+
+ /* Better be a response message. */
+ if (msg->type != PJSIP_RESPONSE_MSG)
+ return PJSIP_ENOTRESPONSEMSG;
+
+ code = msg->line.status.code;
+
+ /* If the response is final, cancel both retransmission and timeout
+ * timer.
+ */
+ if (code >= 200) {
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ if (tsx->timeout_timer.id != 0) {
+ lock_timer(tsx);
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ tsx->timeout_timer.id = 0;
+ unlock_timer(tsx);
+ }
+
+ } else {
+ /* Cancel retransmit timer (for non-INVITE transaction, the
+ * retransmit timer will be rescheduled at T2.
+ */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* For provisional response, only cancel retransmit when this
+ * is an INVITE transaction. For non-INVITE, section 17.1.2.1
+ * of RFC 3261 says that:
+ * - retransmit timer is set to T2
+ * - timeout timer F is not deleted.
+ */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+ /* Cancel timeout timer */
+ lock_timer(tsx);
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->timeout_timer);
+ unlock_timer(tsx);
+
+ } else {
+ if (!tsx->is_reliable) {
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,
+ &tsx->retransmit_timer,
+ &t2_timer_val);
+ }
+ }
+ }
+
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED);
+
+
+ /* Discard retransmission message if it is not INVITE.
+ * The INVITE tdata is needed in case we have to generate ACK for
+ * the final response.
+ */
+ /* Keep last_tx for authorization. */
+ //blp: always keep last_tx until transaction is destroyed
+ //code = msg->line.status.code;
+ //if (tsx->method.id != PJSIP_INVITE_METHOD && code!=401 && code!=407) {
+ // pjsip_tx_data_dec_ref(tsx->last_tx);
+ // tsx->last_tx = NULL;
+ //}
+
+ /* Processing is similar to state Proceeding. */
+ tsx_on_state_proceeding_uac( tsx, event);
+
+ } else {
+ pj_assert(!"Unexpected event");
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * State Trying is for UAS after it received request but before any responses
+ * is sent.
+ * Note: this is different than RFC3261, which can use Trying state for
+ * non-INVITE client transaction (bug in RFC?).
+ */
+static pj_status_t tsx_on_state_trying( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_status_t status;
+
+ pj_assert(tsx->state == PJSIP_TSX_STATE_TRYING);
+
+ /* This state is only for UAS */
+ pj_assert(tsx->role == PJSIP_ROLE_UAS);
+
+ /* Better be transmission of response message.
+ * If we've got request retransmission, this means that the TU hasn't
+ * transmitted any responses within 500 ms, which is not allowed. If
+ * this happens, just ignore the event (we couldn't retransmit last
+ * response because we haven't sent any!).
+ */
+ if (event->type != PJSIP_EVENT_TX_MSG) {
+ return PJ_SUCCESS;
+ }
+
+ /* The rest of the processing of the event is exactly the same as in
+ * "Proceeding" state.
+ */
+ status = tsx_on_state_proceeding_uas( tsx, event);
+
+ /* Inform the TU of the state transision if state is still State_Trying */
+ if (status==PJ_SUCCESS && tsx->state == PJSIP_TSX_STATE_TRYING) {
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING,
+ PJSIP_EVENT_TX_MSG, event->body.tx_msg.tdata);
+
+ }
+
+ return status;
+}
+
+
+/*
+ * Handler for events in Proceeding for UAS
+ * This state happens after the TU sends provisional response.
+ */
+static pj_status_t tsx_on_state_proceeding_uas( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING ||
+ tsx->state == PJSIP_TSX_STATE_TRYING);
+
+ /* This state is only for UAS. */
+ pj_assert(tsx->role == PJSIP_ROLE_UAS);
+
+ /* Receive request retransmission. */
+ if (event->type == PJSIP_EVENT_RX_MSG) {
+
+ pj_status_t status;
+
+ /* Must have last response sent. */
+ PJ_ASSERT_RETURN(tsx->last_tx != NULL, PJ_EBUG);
+
+ /* Send last response */
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_SEND;
+ } else {
+ status = tsx_send_msg(tsx, tsx->last_tx);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ } else if (event->type == PJSIP_EVENT_TX_MSG ) {
+ pjsip_tx_data *tdata = event->body.tx_msg.tdata;
+ pj_status_t status;
+
+ /* The TU sends response message to the request. Save this message so
+ * that we can retransmit the last response in case we receive request
+ * retransmission.
+ */
+ pjsip_msg *msg = tdata->msg;
+
+ /* This can only be a response message. */
+ PJ_ASSERT_RETURN(msg->type==PJSIP_RESPONSE_MSG, PJSIP_ENOTRESPONSEMSG);
+
+ /* Update last status */
+ tsx_set_status_code(tsx, msg->line.status.code,
+ &msg->line.status.reason);
+
+ /* Discard the saved last response (it will be updated later as
+ * necessary).
+ */
+ if (tsx->last_tx && tsx->last_tx != tdata) {
+ pjsip_tx_data_dec_ref( tsx->last_tx );
+ tsx->last_tx = NULL;
+ }
+
+ /* Send the message. */
+ status = tsx_send_msg(tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ // Update To tag header for RFC2543 transaction.
+ // TODO:
+
+ /* Update transaction state */
+ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 100)) {
+
+ if (tsx->last_tx != tdata) {
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref( tdata );
+ }
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING,
+ PJSIP_EVENT_TX_MSG, tdata );
+
+ /* Retransmit provisional response every 1 minute if this is
+ * an INVITE provisional response greater than 100.
+ */
+ if (PJSIP_TSX_1XX_RETRANS_DELAY > 0 &&
+ tsx->method.id==PJSIP_INVITE_METHOD && tsx->status_code>100)
+ {
+
+ /* Stop 1xx retransmission timer, if any */
+ if (tsx->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(tsx->endpt,
+ &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* Schedule retransmission */
+ tsx->retransmit_count = 0;
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ pj_time_val delay = {PJSIP_TSX_1XX_RETRANS_DELAY, 0};
+
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt,
+ &tsx->retransmit_timer,
+ &delay);
+ }
+ }
+
+ } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 200)) {
+
+ /* Stop 1xx retransmission timer, if any */
+ if (tsx->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD && tsx->handle_200resp==0) {
+
+ /* 2xx class message is not saved, because retransmission
+ * is handled by TU.
+ */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TX_MSG, tdata );
+
+ /* Transaction is destroyed. */
+ //return PJSIP_ETSXDESTROYED;
+
+ } else {
+ pj_time_val timeout;
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ tsx->retransmit_count = 0;
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt,
+ &tsx->retransmit_timer,
+ &t1_timer_val);
+ }
+ }
+
+ /* Save last response sent for retransmission when request
+ * retransmission is received.
+ */
+ if (tsx->last_tx != tdata) {
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref(tdata);
+ }
+
+ /* Setup timeout timer: */
+
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+ /* Start Timer H at 64*T1 for INVITE server transaction,
+ * regardless of transport.
+ */
+ timeout = timeout_timer_val;
+
+ } else if (!tsx->is_reliable) {
+
+ /* For non-INVITE, start timer J at 64*T1 for unreliable
+ * transport.
+ */
+ timeout = timeout_timer_val;
+
+ } else {
+
+ /* Transaction terminates immediately for non-INVITE when
+ * reliable transport is used.
+ */
+ timeout.sec = timeout.msec = 0;
+ }
+
+ lock_timer(tsx);
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+ unlock_timer(tsx);
+
+ /* Set state to "Completed" */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_TX_MSG, tdata );
+ }
+
+ } else if (tsx->status_code >= 300) {
+
+ /* Stop 1xx retransmission timer, if any */
+ if (tsx->retransmit_timer.id) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* 3xx-6xx class message causes transaction to move to
+ * "Completed" state.
+ */
+ if (tsx->last_tx != tdata) {
+ tsx->last_tx = tdata;
+ pjsip_tx_data_add_ref( tdata );
+ }
+
+ /* For INVITE, start timer H for transaction termination
+ * regardless whether transport is reliable or not.
+ * For non-INVITE, start timer J with the value of 64*T1 for
+ * non-reliable transports, and zero for reliable transports.
+ */
+ lock_timer(tsx);
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ /* Start timer H for INVITE */
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer,
+ &timeout_timer_val);
+ } else if (!tsx->is_reliable) {
+ /* Start timer J on 64*T1 seconds for non-INVITE */
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer,
+ &timeout_timer_val);
+ } else {
+ /* Start timer J on zero seconds for non-INVITE */
+ pj_time_val zero_time = { 0, 0 };
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,&tsx->timeout_timer,
+ &zero_time);
+ }
+ unlock_timer(tsx);
+
+ /* For INVITE, if unreliable transport is used, retransmission
+ * timer G will be scheduled (retransmission).
+ */
+ if (!tsx->is_reliable) {
+ pjsip_cseq_hdr *cseq = (pjsip_cseq_hdr*)
+ pjsip_msg_find_hdr( msg, PJSIP_H_CSEQ,
+ NULL);
+ if (cseq->method.id == PJSIP_INVITE_METHOD) {
+ tsx->retransmit_count = 0;
+ if (tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) {
+ tsx->transport_flag |= TSX_HAS_PENDING_RESCHED;
+ } else {
+ tsx->retransmit_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer(tsx->endpt,
+ &tsx->retransmit_timer,
+ &t1_timer_val);
+ }
+ }
+ }
+
+ /* Inform TU */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_TX_MSG, tdata );
+
+ } else {
+ pj_assert(0);
+ }
+
+
+ } else if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->retransmit_timer) {
+
+ /* Retransmission timer elapsed. */
+ pj_status_t status;
+
+ /* Must not be triggered while transport is pending. */
+ pj_assert((tsx->transport_flag & TSX_HAS_PENDING_TRANSPORT) == 0);
+
+ /* Must have last response to retransmit. */
+ pj_assert(tsx->last_tx != NULL);
+
+ /* Retransmit the last response. */
+ status = tsx_retransmit( tsx, 1 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->timeout_timer) {
+
+ /* Timeout timer. should not happen? */
+ pj_assert(!"Should not happen(?)");
+
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL);
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer);
+
+ return PJ_EBUG;
+
+ } else {
+ pj_assert(!"Unexpected event");
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in Proceeding for UAC
+ * This state happens after provisional response(s) has been received from
+ * UAS.
+ */
+static pj_status_t tsx_on_state_proceeding_uac(pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+
+ pj_assert(tsx->state == PJSIP_TSX_STATE_PROCEEDING ||
+ tsx->state == PJSIP_TSX_STATE_CALLING);
+
+ if (event->type != PJSIP_EVENT_TIMER) {
+ pjsip_msg *msg;
+
+ /* Must be incoming response, because we should not retransmit
+ * request once response has been received.
+ */
+ pj_assert(event->type == PJSIP_EVENT_RX_MSG);
+ if (event->type != PJSIP_EVENT_RX_MSG) {
+ return PJ_EINVALIDOP;
+ }
+
+ msg = event->body.rx_msg.rdata->msg_info.msg;
+
+ /* Must be a response message. */
+ if (msg->type != PJSIP_RESPONSE_MSG) {
+ pj_assert(!"Expecting response message!");
+ return PJSIP_ENOTRESPONSEMSG;
+ }
+
+ tsx_set_status_code(tsx, msg->line.status.code,
+ &msg->line.status.reason);
+
+ } else {
+ if (event->body.timer.entry == &tsx->retransmit_timer) {
+ /* Retransmit message. */
+ pj_status_t status;
+
+ status = tsx_retransmit( tsx, 1 );
+
+ return status;
+
+ } else {
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL);
+ }
+ }
+
+ if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code, 100)) {
+
+ /* Inform the message to TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_PROCEEDING,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+
+ } else if (PJSIP_IS_STATUS_IN_CLASS(tsx->status_code,200)) {
+
+ /* Stop timeout timer B/F. */
+ lock_timer(tsx);
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ unlock_timer(tsx);
+
+ /* For INVITE, the state moves to Terminated state (because ACK is
+ * handled in TU). For non-INVITE, state moves to Completed.
+ */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+ //return PJSIP_ETSXDESTROYED;
+
+ } else {
+ pj_time_val timeout;
+
+ /* For unreliable transport, start timer D (for INVITE) or
+ * timer K for non-INVITE. */
+ if (!tsx->is_reliable) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ timeout = td_timer_val;
+ } else {
+ timeout = t4_timer_val;
+ }
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+ lock_timer(tsx);
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+ unlock_timer(tsx);
+
+ /* Cancel retransmission timer */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* Move state to Completed, inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+ }
+
+ } else if (event->type == PJSIP_EVENT_TIMER &&
+ event->body.timer.entry == &tsx->timeout_timer) {
+
+ /* Inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer);
+
+
+ } else if (tsx->status_code >= 300 && tsx->status_code <= 699) {
+
+
+#if 0
+ /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
+ /*
+ * This is the old code; it's broken for authentication.
+ */
+ pj_time_val timeout;
+ pj_status_t status;
+
+ /* Stop timer B. */
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+
+ /* Generate and send ACK for INVITE. */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ pjsip_tx_data *ack;
+
+ status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx,
+ event->body.rx_msg.rdata,
+ &ack);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (ack != tsx->last_tx) {
+ pjsip_tx_data_dec_ref(tsx->last_tx);
+ tsx->last_tx = ack;
+ }
+
+ status = tsx_send_msg( tsx, tsx->last_tx);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+ }
+
+ /* Start Timer D with TD/T4 timer if unreliable transport is used. */
+ if (!tsx->is_reliable) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ timeout = td_timer_val;
+ } else {
+ timeout = t4_timer_val;
+ }
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+ tsx->timeout->timer.id = TSX_TIMER_TIMEOUT;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout);
+
+ /* Inform TU.
+ * blp: You might be tempted to move this notification before
+ * sending ACK, but I think you shouldn't. Better set-up
+ * everything before calling tsx_user's callback to avoid
+ * mess up.
+ */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+
+ /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
+#endif
+
+ /* New code, taken from 0.2.9.x branch */
+ pj_time_val timeout;
+ pjsip_tx_data *ack_tdata = NULL;
+
+ /* Cancel retransmission timer */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+
+ /* Stop timer B. */
+ lock_timer(tsx);
+ tsx->timeout_timer.id = 0;
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ unlock_timer(tsx);
+
+ /* Generate and send ACK (for INVITE) */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ pj_status_t status;
+
+ status = pjsip_endpt_create_ack( tsx->endpt, tsx->last_tx,
+ event->body.rx_msg.rdata,
+ &ack_tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = tsx_send_msg( tsx, ack_tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Inform TU. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_COMPLETED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata);
+
+ /* Generate and send ACK for INVITE. */
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ if (ack_tdata != tsx->last_tx) {
+ pjsip_tx_data_dec_ref(tsx->last_tx);
+ tsx->last_tx = ack_tdata;
+
+ /* This is a bug.
+ tsx_send_msg() does NOT decrement tdata's reference counter,
+ so if we add the reference counter here, tdata will have
+ reference counter 2, causing it to leak.
+ pjsip_tx_data_add_ref(ack_tdata);
+ */
+ }
+ }
+
+ /* Start Timer D with TD/T4 timer if unreliable transport is used. */
+ /* Note: tsx->transport may be NULL! */
+ if (!tsx->is_reliable) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ timeout = td_timer_val;
+ } else {
+ timeout = t4_timer_val;
+ }
+ } else {
+ timeout.sec = timeout.msec = 0;
+ }
+ lock_timer(tsx);
+ /* In the short period above timer may have been inserted
+ * by set_timeout() (by CANCEL). Cancel it if necessary. See:
+ * https://trac.pjsip.org/repos/ticket/1374
+ */
+ if (tsx->timeout_timer.id)
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer, &timeout);
+ unlock_timer(tsx);
+
+ } else {
+ // Shouldn't happen because there's no timer for this state.
+ pj_assert(!"Unexpected event");
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in Completed state for UAS
+ */
+static pj_status_t tsx_on_state_completed_uas( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED);
+
+ if (event->type == PJSIP_EVENT_RX_MSG) {
+ pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg;
+
+ /* This must be a request message retransmission. */
+ if (msg->type != PJSIP_REQUEST_MSG)
+ return PJSIP_ENOTREQUESTMSG;
+
+ /* On receive request retransmission, retransmit last response. */
+ if (msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ pj_status_t status;
+
+ status = tsx_retransmit( tsx, 0 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else {
+ pj_time_val timeout;
+
+ /* Process incoming ACK request. */
+
+ /* Verify that this is an INVITE transaction */
+ if (tsx->method.id != PJSIP_INVITE_METHOD) {
+ PJ_LOG(2, (tsx->obj_name,
+ "Received illegal ACK for %.*s transaction",
+ (int)tsx->method.name.slen,
+ tsx->method.name.ptr));
+ return PJSIP_EINVALIDMETHOD;
+ }
+
+ /* Cease retransmission. */
+ if (tsx->retransmit_timer.id != 0) {
+ pjsip_endpt_cancel_timer(tsx->endpt, &tsx->retransmit_timer);
+ tsx->retransmit_timer.id = 0;
+ }
+ tsx->transport_flag &= ~(TSX_HAS_PENDING_RESCHED);
+
+ /* Reschedule timeout timer. */
+ lock_timer(tsx);
+ pjsip_endpt_cancel_timer( tsx->endpt, &tsx->timeout_timer );
+ tsx->timeout_timer.id = TIMER_ACTIVE;
+
+ /* Timer I is T4 timer for unreliable transports, and
+ * zero seconds for reliable transports.
+ */
+ if (!tsx->is_reliable) {
+ timeout.sec = 0;
+ timeout.msec = 0;
+ } else {
+ timeout.sec = t4_timer_val.sec;
+ timeout.msec = t4_timer_val.msec;
+ }
+ pjsip_endpt_schedule_timer( tsx->endpt, &tsx->timeout_timer,
+ &timeout);
+ unlock_timer(tsx);
+
+ /* Move state to "Confirmed" */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_CONFIRMED,
+ PJSIP_EVENT_RX_MSG, event->body.rx_msg.rdata );
+ }
+
+ } else if (event->type == PJSIP_EVENT_TIMER) {
+
+ if (event->body.timer.entry == &tsx->retransmit_timer) {
+ /* Retransmit message. */
+ pj_status_t status;
+
+ status = tsx_retransmit( tsx, 1 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ } else {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+
+ /* For INVITE, this means that ACK was never received.
+ * Set state to Terminated, and inform TU.
+ */
+
+ tsx_set_status_code(tsx, PJSIP_SC_TSX_TIMEOUT, NULL);
+
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer );
+
+ //return PJSIP_ETSXDESTROYED;
+
+ } else {
+ /* Transaction terminated, it can now be deleted. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer );
+ //return PJSIP_ETSXDESTROYED;
+ }
+ }
+
+ } else {
+ /* Ignore request to transmit. */
+ PJ_ASSERT_RETURN(event->type == PJSIP_EVENT_TX_MSG &&
+ event->body.tx_msg.tdata == tsx->last_tx,
+ PJ_EINVALIDOP);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in Completed state for UAC transaction.
+ */
+static pj_status_t tsx_on_state_completed_uac( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_COMPLETED);
+
+ if (event->type == PJSIP_EVENT_TIMER) {
+ /* Must be the timeout timer. */
+ pj_assert(event->body.timer.entry == &tsx->timeout_timer);
+
+ /* Move to Terminated state. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, event->body.timer.entry );
+
+ /* Transaction has been destroyed. */
+ //return PJSIP_ETSXDESTROYED;
+
+ } else if (event->type == PJSIP_EVENT_RX_MSG) {
+ if (tsx->method.id == PJSIP_INVITE_METHOD) {
+ /* On received of final response retransmission, retransmit the ACK.
+ * TU doesn't need to be informed.
+ */
+ pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg;
+ pj_assert(msg->type == PJSIP_RESPONSE_MSG);
+ if (msg->type==PJSIP_RESPONSE_MSG &&
+ msg->line.status.code >= 200)
+ {
+ pj_status_t status;
+
+ status = tsx_retransmit( tsx, 0 );
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+ } else {
+ /* Very late retransmission of privisional response. */
+ pj_assert( msg->type == PJSIP_RESPONSE_MSG );
+ }
+ } else {
+ /* Just drop the response. */
+ }
+
+ } else {
+ pj_assert(!"Unexpected event");
+ return PJ_EINVALIDOP;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in state Confirmed.
+ */
+static pj_status_t tsx_on_state_confirmed( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_CONFIRMED);
+
+ /* This state is only for UAS for INVITE. */
+ pj_assert(tsx->role == PJSIP_ROLE_UAS);
+ pj_assert(tsx->method.id == PJSIP_INVITE_METHOD);
+
+ /* Absorb any ACK received. */
+ if (event->type == PJSIP_EVENT_RX_MSG) {
+
+ pjsip_msg *msg = event->body.rx_msg.rdata->msg_info.msg;
+
+ /* Only expecting request message. */
+ if (msg->type != PJSIP_REQUEST_MSG)
+ return PJSIP_ENOTREQUESTMSG;
+
+ /* Must be an ACK request or a late INVITE retransmission. */
+ pj_assert(msg->line.req.method.id == PJSIP_ACK_METHOD ||
+ msg->line.req.method.id == PJSIP_INVITE_METHOD);
+
+ } else if (event->type == PJSIP_EVENT_TIMER) {
+ /* Must be from timeout_timer_. */
+ pj_assert(event->body.timer.entry == &tsx->timeout_timer);
+
+ /* Move to Terminated state. */
+ tsx_set_state( tsx, PJSIP_TSX_STATE_TERMINATED,
+ PJSIP_EVENT_TIMER, &tsx->timeout_timer );
+
+ /* Transaction has been destroyed. */
+ //return PJSIP_ETSXDESTROYED;
+
+ } else {
+ pj_assert(!"Unexpected event");
+ return PJ_EBUG;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in state Terminated.
+ */
+static pj_status_t tsx_on_state_terminated( pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ pj_assert(tsx->state == PJSIP_TSX_STATE_TERMINATED);
+
+ /* Ignore events other than timer. This used to be an assertion but
+ * events may genuinely arrive at this state.
+ */
+ if (event->type != PJSIP_EVENT_TIMER) {
+ return PJ_EIGNORED;
+ }
+
+ /* Destroy this transaction */
+ tsx_set_state(tsx, PJSIP_TSX_STATE_DESTROYED,
+ event->type, event->body.user.user1 );
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Handler for events in state Destroyed.
+ * Shouldn't happen!
+ */
+static pj_status_t tsx_on_state_destroyed(pjsip_transaction *tsx,
+ pjsip_event *event)
+{
+ PJ_UNUSED_ARG(tsx);
+ PJ_UNUSED_ARG(event);
+
+ // See https://trac.pjsip.org/repos/ticket/1432
+ //pj_assert(!"Not expecting any events!!");
+
+ return PJ_EIGNORED;
+}
+
diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c
new file mode 100644
index 0000000..4d8b77e
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport.c
@@ -0,0 +1,1942 @@
+/* $Id: sip_transport.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 <pjsip/sip_transport.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_private.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_module.h>
+#include <pj/except.h>
+#include <pj/os.h>
+#include <pj/log.h>
+#include <pj/ioqueue.h>
+#include <pj/hash.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/list.h>
+
+
+#define THIS_FILE "sip_transport.c"
+
+#if 0
+# define TRACE_(x) PJ_LOG(5,x)
+
+static const char *addr_string(const pj_sockaddr_t *addr)
+{
+ static char str[PJ_INET6_ADDRSTRLEN];
+ pj_inet_ntop(((const pj_sockaddr*)addr)->addr.sa_family,
+ pj_sockaddr_get_addr(addr),
+ str, sizeof(str));
+ return str;
+}
+#else
+# define TRACE_(x)
+#endif
+
+/* Prototype. */
+static pj_status_t mod_on_tx_msg(pjsip_tx_data *tdata);
+
+/* This module has sole purpose to print transmit data to contigous buffer
+ * before actually transmitted to the wire.
+ */
+static pjsip_module mod_msg_print =
+{
+ NULL, NULL, /* prev and next */
+ { "mod-msg-print", 13}, /* 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_on_tx_msg, /* on_tx_request() */
+ &mod_on_tx_msg, /* on_tx_response() */
+ NULL, /* on_tsx_state() */
+};
+
+/*
+ * Transport manager.
+ */
+struct pjsip_tpmgr
+{
+ pj_hash_table_t *table;
+ pj_lock_t *lock;
+ pjsip_endpoint *endpt;
+ pjsip_tpfactory factory_list;
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ pj_atomic_t *tdata_counter;
+#endif
+ void (*on_rx_msg)(pjsip_endpoint*, pj_status_t, pjsip_rx_data*);
+ pj_status_t (*on_tx_msg)(pjsip_endpoint*, pjsip_tx_data*);
+ pjsip_tp_state_callback tp_state_cb;
+};
+
+
+/* Transport state listener list type */
+typedef struct tp_state_listener
+{
+ PJ_DECL_LIST_MEMBER(struct tp_state_listener);
+
+ pjsip_tp_state_callback cb;
+ void *user_data;
+} tp_state_listener;
+
+
+/*
+ * Transport data.
+ */
+typedef struct transport_data
+{
+ /* Transport listeners */
+ tp_state_listener st_listeners;
+ tp_state_listener st_listeners_empty;
+} transport_data;
+
+
+/*****************************************************************************
+ *
+ * GENERAL TRANSPORT (NAMES, TYPES, ETC.)
+ *
+ *****************************************************************************/
+
+/*
+ * Transport names.
+ */
+struct transport_names_t
+{
+ pjsip_transport_type_e type; /* Transport type */
+ pj_uint16_t port; /* Default port number */
+ pj_str_t name; /* Id tag */
+ const char *description; /* Longer description */
+ unsigned flag; /* Flags */
+ char name_buf[16]; /* For user's transport */
+} transport_names[16] =
+{
+ {
+ PJSIP_TRANSPORT_UNSPECIFIED,
+ 0,
+ {"Unspecified", 11},
+ "Unspecified",
+ 0
+ },
+ {
+ PJSIP_TRANSPORT_UDP,
+ 5060,
+ {"UDP", 3},
+ "UDP transport",
+ PJSIP_TRANSPORT_DATAGRAM
+ },
+ {
+ PJSIP_TRANSPORT_TCP,
+ 5060,
+ {"TCP", 3},
+ "TCP transport",
+ PJSIP_TRANSPORT_RELIABLE
+ },
+ {
+ PJSIP_TRANSPORT_TLS,
+ 5061,
+ {"TLS", 3},
+ "TLS transport",
+ PJSIP_TRANSPORT_RELIABLE | PJSIP_TRANSPORT_SECURE
+ },
+ {
+ PJSIP_TRANSPORT_SCTP,
+ 5060,
+ {"SCTP", 4},
+ "SCTP transport",
+ PJSIP_TRANSPORT_RELIABLE
+ },
+ {
+ PJSIP_TRANSPORT_LOOP,
+ 15060,
+ {"LOOP", 4},
+ "Loopback transport",
+ PJSIP_TRANSPORT_RELIABLE
+ },
+ {
+ PJSIP_TRANSPORT_LOOP_DGRAM,
+ 15060,
+ {"LOOP-DGRAM", 10},
+ "Loopback datagram transport",
+ PJSIP_TRANSPORT_DATAGRAM
+ },
+ {
+ PJSIP_TRANSPORT_UDP6,
+ 5060,
+ {"UDP", 3},
+ "UDP IPv6 transport",
+ PJSIP_TRANSPORT_DATAGRAM
+ },
+ {
+ PJSIP_TRANSPORT_TCP6,
+ 5060,
+ {"TCP", 3},
+ "TCP IPv6 transport",
+ PJSIP_TRANSPORT_RELIABLE
+ },
+};
+
+static void tp_state_callback(pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info);
+
+
+struct transport_names_t *get_tpname(pjsip_transport_type_e type)
+{
+ unsigned i;
+ for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) {
+ if (transport_names[i].type == type)
+ return &transport_names[i];
+ }
+ pj_assert(!"Invalid transport type!");
+ return NULL;
+}
+
+
+
+/*
+ * Register new transport type to PJSIP.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_register_type( unsigned tp_flag,
+ const char *tp_name,
+ int def_port,
+ int *p_tp_type)
+{
+ unsigned i;
+
+ PJ_ASSERT_RETURN(tp_flag && tp_name && def_port, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pj_ansi_strlen(tp_name) <
+ PJ_ARRAY_SIZE(transport_names[0].name_buf),
+ PJ_ENAMETOOLONG);
+
+ for (i=1; i<PJ_ARRAY_SIZE(transport_names); ++i) {
+ if (transport_names[i].type == 0)
+ break;
+ }
+
+ if (i == PJ_ARRAY_SIZE(transport_names))
+ return PJ_ETOOMANY;
+
+ transport_names[i].type = (pjsip_transport_type_e)i;
+ transport_names[i].port = (pj_uint16_t)def_port;
+ pj_ansi_strcpy(transport_names[i].name_buf, tp_name);
+ transport_names[i].name = pj_str(transport_names[i].name_buf);
+ transport_names[i].flag = tp_flag;
+
+ if (p_tp_type)
+ *p_tp_type = i;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get transport type from name.
+ */
+PJ_DEF(pjsip_transport_type_e) pjsip_transport_get_type_from_name(const pj_str_t *name)
+{
+ unsigned i;
+
+ if (name->slen == 0)
+ return PJSIP_TRANSPORT_UNSPECIFIED;
+
+ /* Get transport type from name. */
+ for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) {
+ if (pj_stricmp(name, &transport_names[i].name) == 0) {
+ return transport_names[i].type;
+ }
+ }
+
+ pj_assert(!"Invalid transport name");
+ return PJSIP_TRANSPORT_UNSPECIFIED;
+}
+
+
+/*
+ * Get the transport type for the specified flags.
+ */
+PJ_DEF(pjsip_transport_type_e) pjsip_transport_get_type_from_flag(unsigned flag)
+{
+ unsigned i;
+
+ /* Get the transport type for the specified flags. */
+ for (i=0; i<PJ_ARRAY_SIZE(transport_names); ++i) {
+ if (transport_names[i].flag == flag) {
+ return transport_names[i].type;
+ }
+ }
+
+ pj_assert(!"Invalid transport type");
+ return PJSIP_TRANSPORT_UNSPECIFIED;
+}
+
+/*
+ * Get the socket address family of a given transport type.
+ */
+PJ_DEF(int) pjsip_transport_type_get_af(pjsip_transport_type_e type)
+{
+ if (type & PJSIP_TRANSPORT_IPV6)
+ return pj_AF_INET6();
+ else
+ return pj_AF_INET();
+}
+
+PJ_DEF(unsigned) pjsip_transport_get_flag_from_type(pjsip_transport_type_e type)
+{
+ /* Return transport flag. */
+ return get_tpname(type)->flag;
+}
+
+/*
+ * Get the default SIP port number for the specified type.
+ */
+PJ_DEF(int) pjsip_transport_get_default_port_for_type(pjsip_transport_type_e type)
+{
+ /* Return the port. */
+ return get_tpname(type)->port;
+}
+
+/*
+ * Get transport name.
+ */
+PJ_DEF(const char*) pjsip_transport_get_type_name(pjsip_transport_type_e type)
+{
+ /* Return the name. */
+ return get_tpname(type)->name.ptr;
+}
+
+/*
+ * Get transport description.
+ */
+PJ_DEF(const char*) pjsip_transport_get_type_desc(pjsip_transport_type_e type)
+{
+ /* Return the description. */
+ return get_tpname(type)->description;
+}
+
+
+/*****************************************************************************
+ *
+ * TRANSPORT SELECTOR
+ *
+ *****************************************************************************/
+
+/*
+ * Add transport/listener reference in the selector.
+ */
+PJ_DEF(void) pjsip_tpselector_add_ref(pjsip_tpselector *sel)
+{
+ if (sel->type == PJSIP_TPSELECTOR_TRANSPORT && sel->u.transport != NULL)
+ pjsip_transport_add_ref(sel->u.transport);
+ else if (sel->type == PJSIP_TPSELECTOR_LISTENER && sel->u.listener != NULL)
+ ; /* Hmm.. looks like we don't have reference counter for listener */
+}
+
+
+/*
+ * Decrement transport/listener reference in the selector.
+ */
+PJ_DEF(void) pjsip_tpselector_dec_ref(pjsip_tpselector *sel)
+{
+ if (sel->type == PJSIP_TPSELECTOR_TRANSPORT && sel->u.transport != NULL)
+ pjsip_transport_dec_ref(sel->u.transport);
+ else if (sel->type == PJSIP_TPSELECTOR_LISTENER && sel->u.listener != NULL)
+ ; /* Hmm.. looks like we don't have reference counter for listener */
+}
+
+
+/*****************************************************************************
+ *
+ * TRANSMIT DATA BUFFER MANIPULATION.
+ *
+ *****************************************************************************/
+
+/*
+ * Create new transmit buffer.
+ */
+PJ_DEF(pj_status_t) pjsip_tx_data_create( pjsip_tpmgr *mgr,
+ pjsip_tx_data **p_tdata )
+{
+ pj_pool_t *pool;
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(mgr && p_tdata, PJ_EINVAL);
+
+ pool = pjsip_endpt_create_pool( mgr->endpt, "tdta%p",
+ PJSIP_POOL_LEN_TDATA,
+ PJSIP_POOL_INC_TDATA );
+ if (!pool)
+ return PJ_ENOMEM;
+
+ tdata = PJ_POOL_ZALLOC_T(pool, pjsip_tx_data);
+ tdata->pool = pool;
+ tdata->mgr = mgr;
+ pj_memcpy(tdata->obj_name, pool->obj_name, PJ_MAX_OBJ_NAME);
+
+ status = pj_atomic_create(tdata->pool, 0, &tdata->ref_cnt);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_release_pool( mgr->endpt, tdata->pool );
+ return status;
+ }
+
+ //status = pj_lock_create_simple_mutex(pool, "tdta%p", &tdata->lock);
+ status = pj_lock_create_null_mutex(pool, "tdta%p", &tdata->lock);
+ if (status != PJ_SUCCESS) {
+ pjsip_endpt_release_pool( mgr->endpt, tdata->pool );
+ return status;
+ }
+
+ pj_ioqueue_op_key_init(&tdata->op_key.key, sizeof(tdata->op_key.key));
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ pj_atomic_inc( tdata->mgr->tdata_counter );
+#endif
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Add reference to tx buffer.
+ */
+PJ_DEF(void) pjsip_tx_data_add_ref( pjsip_tx_data *tdata )
+{
+ pj_atomic_inc(tdata->ref_cnt);
+}
+
+/*
+ * Decrease transport data reference, destroy it when the reference count
+ * reaches zero.
+ */
+PJ_DEF(pj_status_t) pjsip_tx_data_dec_ref( pjsip_tx_data *tdata )
+{
+ pj_assert( pj_atomic_get(tdata->ref_cnt) > 0);
+ if (pj_atomic_dec_and_get(tdata->ref_cnt) <= 0) {
+ PJ_LOG(5,(tdata->obj_name, "Destroying txdata %s",
+ pjsip_tx_data_get_info(tdata)));
+ pjsip_tpselector_dec_ref(&tdata->tp_sel);
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ pj_atomic_dec( tdata->mgr->tdata_counter );
+#endif
+ pj_atomic_destroy( tdata->ref_cnt );
+ pj_lock_destroy( tdata->lock );
+ pjsip_endpt_release_pool( tdata->mgr->endpt, tdata->pool );
+ return PJSIP_EBUFDESTROYED;
+ } else {
+ return PJ_SUCCESS;
+ }
+}
+
+/*
+ * Invalidate the content of the print buffer to force the message to be
+ * re-printed when sent.
+ */
+PJ_DEF(void) pjsip_tx_data_invalidate_msg( pjsip_tx_data *tdata )
+{
+ tdata->buf.cur = tdata->buf.start;
+ tdata->info = NULL;
+}
+
+/*
+ * Print the SIP message to transmit data buffer's internal buffer.
+ */
+PJ_DEF(pj_status_t) pjsip_tx_data_encode(pjsip_tx_data *tdata)
+{
+ /* Allocate buffer if necessary. */
+ if (tdata->buf.start == NULL) {
+ PJ_USE_EXCEPTION;
+
+ PJ_TRY {
+ tdata->buf.start = (char*)
+ pj_pool_alloc(tdata->pool, PJSIP_MAX_PKT_LEN);
+ }
+ PJ_CATCH_ANY {
+ return PJ_ENOMEM;
+ }
+ PJ_END
+
+ tdata->buf.cur = tdata->buf.start;
+ tdata->buf.end = tdata->buf.start + PJSIP_MAX_PKT_LEN;
+ }
+
+ /* Do we need to reprint? */
+ if (!pjsip_tx_data_is_valid(tdata)) {
+ pj_ssize_t size;
+
+ size = pjsip_msg_print( tdata->msg, tdata->buf.start,
+ tdata->buf.end - tdata->buf.start);
+ if (size < 0) {
+ return PJSIP_EMSGTOOLONG;
+ }
+ pj_assert(size != 0);
+ tdata->buf.cur[size] = '\0';
+ tdata->buf.cur += size;
+ }
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_bool_t) pjsip_tx_data_is_valid( pjsip_tx_data *tdata )
+{
+ return tdata->buf.cur != tdata->buf.start;
+}
+
+static char *get_msg_info(pj_pool_t *pool, const char *obj_name,
+ const pjsip_msg *msg)
+{
+ char info_buf[128], *info;
+ const pjsip_cseq_hdr *cseq;
+ int len;
+
+ cseq = (const pjsip_cseq_hdr*) pjsip_msg_find_hdr(msg, PJSIP_H_CSEQ, NULL);
+ PJ_ASSERT_RETURN(cseq != NULL, "INVALID MSG");
+
+ if (msg->type == PJSIP_REQUEST_MSG) {
+ len = pj_ansi_snprintf(info_buf, sizeof(info_buf),
+ "Request msg %.*s/cseq=%d (%s)",
+ (int)msg->line.req.method.name.slen,
+ msg->line.req.method.name.ptr,
+ cseq->cseq, obj_name);
+ } else {
+ len = pj_ansi_snprintf(info_buf, sizeof(info_buf),
+ "Response msg %d/%.*s/cseq=%d (%s)",
+ msg->line.status.code,
+ (int)cseq->method.name.slen,
+ cseq->method.name.ptr,
+ cseq->cseq, obj_name);
+ }
+
+ if (len < 1 || len >= (int)sizeof(info_buf)) {
+ return (char*)obj_name;
+ }
+
+ info = (char*) pj_pool_alloc(pool, len+1);
+ pj_memcpy(info, info_buf, len+1);
+
+ return info;
+}
+
+PJ_DEF(char*) pjsip_tx_data_get_info( pjsip_tx_data *tdata )
+{
+ /* tdata->info may be assigned by application so if it exists
+ * just return it.
+ */
+ if (tdata->info)
+ return tdata->info;
+
+ if (tdata==NULL || tdata->msg==NULL)
+ return "NULL";
+
+ pj_lock_acquire(tdata->lock);
+ tdata->info = get_msg_info(tdata->pool, tdata->obj_name, tdata->msg);
+ pj_lock_release(tdata->lock);
+
+ return tdata->info;
+}
+
+PJ_DEF(pj_status_t) pjsip_tx_data_set_transport(pjsip_tx_data *tdata,
+ const pjsip_tpselector *sel)
+{
+ PJ_ASSERT_RETURN(tdata && sel, PJ_EINVAL);
+
+ pj_lock_acquire(tdata->lock);
+
+ pjsip_tpselector_dec_ref(&tdata->tp_sel);
+
+ pj_memcpy(&tdata->tp_sel, sel, sizeof(*sel));
+ pjsip_tpselector_add_ref(&tdata->tp_sel);
+
+ pj_lock_release(tdata->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(char*) pjsip_rx_data_get_info(pjsip_rx_data *rdata)
+{
+ char obj_name[PJ_MAX_OBJ_NAME];
+
+ PJ_ASSERT_RETURN(rdata->msg_info.msg, "INVALID MSG");
+
+ if (rdata->msg_info.info)
+ return rdata->msg_info.info;
+
+ pj_ansi_strcpy(obj_name, "rdata");
+ pj_ansi_snprintf(obj_name+5, sizeof(obj_name)-5, "%p", rdata);
+
+ rdata->msg_info.info = get_msg_info(rdata->tp_info.pool, obj_name,
+ rdata->msg_info.msg);
+ return rdata->msg_info.info;
+}
+
+/*****************************************************************************
+ *
+ * TRANSPORT KEY
+ *
+ *****************************************************************************/
+
+
+/*****************************************************************************
+ *
+ * TRANSPORT
+ *
+ *****************************************************************************/
+
+static void transport_send_callback(pjsip_transport *transport,
+ void *token,
+ pj_ssize_t size)
+{
+ pjsip_tx_data *tdata = (pjsip_tx_data*) token;
+
+ PJ_UNUSED_ARG(transport);
+
+ /* Mark pending off so that app can resend/reuse txdata from inside
+ * the callback.
+ */
+ tdata->is_pending = 0;
+
+ /* Call callback, if any. */
+ if (tdata->cb) {
+ (*tdata->cb)(tdata->token, tdata, size);
+ }
+
+ /* Decrement reference count. */
+ pjsip_tx_data_dec_ref(tdata);
+}
+
+/* This function is called by endpoint for on_tx_request() and on_tx_response()
+ * notification.
+ */
+static pj_status_t mod_on_tx_msg(pjsip_tx_data *tdata)
+{
+ return pjsip_tx_data_encode(tdata);
+}
+
+/*
+ * Send a SIP message using the specified transport.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_send( pjsip_transport *tr,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *addr,
+ int addr_len,
+ void *token,
+ pjsip_tp_send_callback cb)
+{
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tr && tdata && addr, PJ_EINVAL);
+
+ /* Is it currently being sent? */
+ if (tdata->is_pending) {
+ pj_assert(!"Invalid operation step!");
+ PJ_LOG(2,(THIS_FILE, "Unable to send %s: message is pending",
+ pjsip_tx_data_get_info(tdata)));
+ return PJSIP_EPENDINGTX;
+ }
+
+ /* Add reference to prevent deletion, and to cancel idle timer if
+ * it's running.
+ */
+ pjsip_transport_add_ref(tr);
+
+ /* Fill in tp_info. */
+ tdata->tp_info.transport = tr;
+ pj_memcpy(&tdata->tp_info.dst_addr, addr, addr_len);
+ tdata->tp_info.dst_addr_len = addr_len;
+
+ pj_inet_ntop(((pj_sockaddr*)addr)->addr.sa_family,
+ pj_sockaddr_get_addr(addr),
+ tdata->tp_info.dst_name,
+ sizeof(tdata->tp_info.dst_name));
+ tdata->tp_info.dst_port = pj_sockaddr_get_port(addr);
+
+ /* Distribute to modules.
+ * When the message reach mod_msg_print, the contents of the message will
+ * be "printed" to contiguous buffer.
+ */
+ if (tr->tpmgr->on_tx_msg) {
+ status = (*tr->tpmgr->on_tx_msg)(tr->endpt, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_transport_dec_ref(tr);
+ return status;
+ }
+ }
+
+ /* Save callback data. */
+ tdata->token = token;
+ tdata->cb = cb;
+
+ /* Add reference counter. */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Mark as pending. */
+ tdata->is_pending = 1;
+
+ /* Send to transport. */
+ status = (*tr->send_msg)(tr, tdata, addr, addr_len, (void*)tdata,
+ &transport_send_callback);
+
+ if (status != PJ_EPENDING) {
+ tdata->is_pending = 0;
+ pjsip_tx_data_dec_ref(tdata);
+ }
+
+ pjsip_transport_dec_ref(tr);
+ return status;
+}
+
+
+/* send_raw() callback */
+static void send_raw_callback(pjsip_transport *transport,
+ void *token,
+ pj_ssize_t size)
+{
+ pjsip_tx_data *tdata = (pjsip_tx_data*) token;
+
+ /* Mark pending off so that app can resend/reuse txdata from inside
+ * the callback.
+ */
+ tdata->is_pending = 0;
+
+ /* Call callback, if any. */
+ if (tdata->cb) {
+ (*tdata->cb)(tdata->token, tdata, size);
+ }
+
+ /* Decrement tdata reference count. */
+ pjsip_tx_data_dec_ref(tdata);
+
+ /* Decrement transport reference count */
+ pjsip_transport_dec_ref(transport);
+}
+
+
+/* Send raw data */
+PJ_DEF(pj_status_t) pjsip_tpmgr_send_raw(pjsip_tpmgr *mgr,
+ pjsip_transport_type_e tp_type,
+ const pjsip_tpselector *sel,
+ pjsip_tx_data *tdata,
+ const void *raw_data,
+ pj_size_t data_len,
+ const pj_sockaddr_t *addr,
+ int addr_len,
+ void *token,
+ pjsip_tp_send_callback cb)
+{
+ pjsip_transport *tr;
+ pj_status_t status;
+
+ /* Acquire the transport */
+ status = pjsip_tpmgr_acquire_transport(mgr, tp_type, addr, addr_len,
+ sel, &tr);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create transmit data buffer if one is not specified */
+ if (tdata == NULL) {
+ status = pjsip_endpt_create_tdata(tr->endpt, &tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_transport_dec_ref(tr);
+ return status;
+ }
+
+ tdata->info = "raw";
+
+ /* Add reference counter. */
+ pjsip_tx_data_add_ref(tdata);
+ }
+
+ /* Allocate buffer */
+ if (tdata->buf.start == NULL ||
+ (tdata->buf.end - tdata->buf.start) < (int)data_len)
+ {
+ /* Note: data_len may be zero, so allocate +1 */
+ tdata->buf.start = (char*) pj_pool_alloc(tdata->pool, data_len+1);
+ tdata->buf.end = tdata->buf.start + data_len + 1;
+ }
+
+ /* Copy data, if any! (application may send zero len packet) */
+ if (data_len) {
+ pj_memcpy(tdata->buf.start, raw_data, data_len);
+ }
+ tdata->buf.cur = tdata->buf.start + data_len;
+
+ /* Save callback data. */
+ tdata->token = token;
+ tdata->cb = cb;
+
+ /* Mark as pending. */
+ tdata->is_pending = 1;
+
+ /* Send to transport */
+ status = tr->send_msg(tr, tdata, addr, addr_len,
+ tdata, &send_raw_callback);
+
+ if (status != PJ_EPENDING) {
+ /* callback will not be called, so destroy tdata now. */
+ pjsip_tx_data_dec_ref(tdata);
+ pjsip_transport_dec_ref(tr);
+ }
+
+ return status;
+}
+
+
+static void transport_idle_callback(pj_timer_heap_t *timer_heap,
+ struct pj_timer_entry *entry)
+{
+ pjsip_transport *tp = (pjsip_transport*) entry->user_data;
+ pj_assert(tp != NULL);
+
+ PJ_UNUSED_ARG(timer_heap);
+
+ entry->id = PJ_FALSE;
+ pjsip_transport_destroy(tp);
+}
+
+/*
+ * Add ref.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_add_ref( pjsip_transport *tp )
+{
+ PJ_ASSERT_RETURN(tp != NULL, PJ_EINVAL);
+
+ if (pj_atomic_inc_and_get(tp->ref_cnt) == 1) {
+ pj_lock_acquire(tp->tpmgr->lock);
+ /* Verify again. */
+ if (pj_atomic_get(tp->ref_cnt) == 1) {
+ if (tp->idle_timer.id != PJ_FALSE) {
+ pjsip_endpt_cancel_timer(tp->tpmgr->endpt, &tp->idle_timer);
+ tp->idle_timer.id = PJ_FALSE;
+ }
+ }
+ pj_lock_release(tp->tpmgr->lock);
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Dec ref.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_dec_ref( pjsip_transport *tp )
+{
+ PJ_ASSERT_RETURN(tp != NULL, PJ_EINVAL);
+
+ pj_assert(pj_atomic_get(tp->ref_cnt) > 0);
+
+ if (pj_atomic_dec_and_get(tp->ref_cnt) == 0) {
+ pj_lock_acquire(tp->tpmgr->lock);
+ /* Verify again. Do not register timer if the transport is
+ * being destroyed.
+ */
+ if (pj_atomic_get(tp->ref_cnt) == 0 && !tp->is_destroying) {
+ pj_time_val delay;
+
+ /* If transport is in graceful shutdown, then this is the
+ * last user who uses the transport. Schedule to destroy the
+ * transport immediately. Otherwise schedule idle timer.
+ */
+ if (tp->is_shutdown) {
+ delay.sec = delay.msec = 0;
+ } else {
+ delay.sec = (tp->dir==PJSIP_TP_DIR_OUTGOING) ?
+ PJSIP_TRANSPORT_IDLE_TIME :
+ PJSIP_TRANSPORT_SERVER_IDLE_TIME;
+ delay.msec = 0;
+ }
+
+ pj_assert(tp->idle_timer.id == 0);
+ tp->idle_timer.id = PJ_TRUE;
+ pjsip_endpt_schedule_timer(tp->tpmgr->endpt, &tp->idle_timer,
+ &delay);
+ }
+ pj_lock_release(tp->tpmgr->lock);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Register a transport.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_register( pjsip_tpmgr *mgr,
+ pjsip_transport *tp )
+{
+ int key_len;
+ pj_uint32_t hval;
+ void *entry;
+
+ /* Init. */
+ tp->tpmgr = mgr;
+ pj_bzero(&tp->idle_timer, sizeof(tp->idle_timer));
+ tp->idle_timer.user_data = tp;
+ tp->idle_timer.cb = &transport_idle_callback;
+
+ /*
+ * Register to hash table (see Trac ticket #42).
+ */
+ key_len = sizeof(tp->key.type) + tp->addr_len;
+ pj_lock_acquire(mgr->lock);
+
+ /* If entry already occupied, unregister previous entry */
+ hval = 0;
+ entry = pj_hash_get(mgr->table, &tp->key, key_len, &hval);
+ if (entry != NULL)
+ pj_hash_set(NULL, mgr->table, &tp->key, key_len, hval, NULL);
+
+ /* Register new entry */
+ pj_hash_set(tp->pool, mgr->table, &tp->key, key_len, hval, tp);
+
+ pj_lock_release(mgr->lock);
+
+ TRACE_((THIS_FILE,"Transport %s registered: type=%s, remote=%s:%d",
+ tp->obj_name,
+ pjsip_transport_get_type_name(tp->key.type),
+ addr_string(&tp->key.rem_addr),
+ pj_sockaddr_get_port(&tp->key.rem_addr)));
+
+ return PJ_SUCCESS;
+}
+
+/* Force destroy transport (e.g. during transport manager shutdown. */
+static pj_status_t destroy_transport( pjsip_tpmgr *mgr,
+ pjsip_transport *tp )
+{
+ int key_len;
+ pj_uint32_t hval;
+ void *entry;
+
+ TRACE_((THIS_FILE, "Transport %s is being destroyed", tp->obj_name));
+
+ pj_lock_acquire(tp->lock);
+ pj_lock_acquire(mgr->lock);
+
+ tp->is_destroying = PJ_TRUE;
+
+ /*
+ * Unregister timer, if any.
+ */
+ //pj_assert(tp->idle_timer.id == PJ_FALSE);
+ if (tp->idle_timer.id != PJ_FALSE) {
+ pjsip_endpt_cancel_timer(mgr->endpt, &tp->idle_timer);
+ tp->idle_timer.id = PJ_FALSE;
+ }
+
+ /*
+ * Unregister from hash table (see Trac ticket #42).
+ */
+ key_len = sizeof(tp->key.type) + tp->addr_len;
+ hval = 0;
+ entry = pj_hash_get(mgr->table, &tp->key, key_len, &hval);
+ if (entry == (void*)tp)
+ pj_hash_set(NULL, mgr->table, &tp->key, key_len, hval, NULL);
+
+ pj_lock_release(mgr->lock);
+
+ /* Destroy. */
+ return tp->destroy(tp);
+}
+
+
+/*
+ * Start graceful shutdown procedure for this transport.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_shutdown(pjsip_transport *tp)
+{
+ pjsip_tpmgr *mgr;
+ pj_status_t status;
+
+ TRACE_((THIS_FILE, "Transport %s shutting down", tp->obj_name));
+
+ pj_lock_acquire(tp->lock);
+
+ mgr = tp->tpmgr;
+ pj_lock_acquire(mgr->lock);
+
+ /* Do nothing if transport is being shutdown already */
+ if (tp->is_shutdown) {
+ pj_lock_release(tp->lock);
+ pj_lock_release(mgr->lock);
+ return PJ_SUCCESS;
+ }
+
+ status = PJ_SUCCESS;
+
+ /* Instruct transport to shutdown itself */
+ if (tp->do_shutdown)
+ status = tp->do_shutdown(tp);
+
+ if (status == PJ_SUCCESS)
+ tp->is_shutdown = PJ_TRUE;
+
+ /* If transport reference count is zero, start timer count-down */
+ if (pj_atomic_get(tp->ref_cnt) == 0) {
+ pjsip_transport_add_ref(tp);
+ pjsip_transport_dec_ref(tp);
+ }
+
+ pj_lock_release(tp->lock);
+ pj_lock_release(mgr->lock);
+
+ return status;
+}
+
+
+/**
+ * Unregister transport.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_destroy( pjsip_transport *tp)
+{
+ /* Must have no user. */
+ PJ_ASSERT_RETURN(pj_atomic_get(tp->ref_cnt) == 0, PJSIP_EBUSY);
+
+ /* Destroy. */
+ return destroy_transport(tp->tpmgr, tp);
+}
+
+
+
+/*****************************************************************************
+ *
+ * TRANSPORT FACTORY
+ *
+ *****************************************************************************/
+
+
+PJ_DEF(pj_status_t) pjsip_tpmgr_register_tpfactory( pjsip_tpmgr *mgr,
+ pjsip_tpfactory *tpf)
+{
+ pjsip_tpfactory *p;
+ pj_status_t status;
+
+ pj_lock_acquire(mgr->lock);
+
+ /* Check that no factory with the same type has been registered. */
+ status = PJ_SUCCESS;
+ for (p=mgr->factory_list.next; p!=&mgr->factory_list; p=p->next) {
+ if (p->type == tpf->type) {
+ status = PJSIP_ETYPEEXISTS;
+ break;
+ }
+ if (p == tpf) {
+ status = PJ_EEXISTS;
+ break;
+ }
+ }
+
+ if (status != PJ_SUCCESS) {
+ pj_lock_release(mgr->lock);
+ return status;
+ }
+
+ pj_list_insert_before(&mgr->factory_list, tpf);
+
+ pj_lock_release(mgr->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/**
+ * Unregister factory.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_unregister_tpfactory( pjsip_tpmgr *mgr,
+ pjsip_tpfactory *tpf)
+{
+ pj_lock_acquire(mgr->lock);
+
+ pj_assert(pj_list_find_node(&mgr->factory_list, tpf) == tpf);
+ pj_list_erase(tpf);
+
+ pj_lock_release(mgr->lock);
+
+ return PJ_SUCCESS;
+}
+
+
+/*****************************************************************************
+ *
+ * TRANSPORT MANAGER
+ *
+ *****************************************************************************/
+
+/*
+ * Create a new transport manager.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_create( pj_pool_t *pool,
+ pjsip_endpoint *endpt,
+ pjsip_rx_callback rx_cb,
+ pjsip_tx_callback tx_cb,
+ pjsip_tpmgr **p_mgr)
+{
+ pjsip_tpmgr *mgr;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(pool && endpt && rx_cb && p_mgr, PJ_EINVAL);
+
+ /* Register mod_msg_print module. */
+ status = pjsip_endpt_register_module(endpt, &mod_msg_print);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Create and initialize transport manager. */
+ mgr = PJ_POOL_ZALLOC_T(pool, pjsip_tpmgr);
+ mgr->endpt = endpt;
+ mgr->on_rx_msg = rx_cb;
+ mgr->on_tx_msg = tx_cb;
+ pj_list_init(&mgr->factory_list);
+
+ mgr->table = pj_hash_create(pool, PJSIP_TPMGR_HTABLE_SIZE);
+ if (!mgr->table)
+ return PJ_ENOMEM;
+
+ status = pj_lock_create_recursive_mutex(pool, "tmgr%p", &mgr->lock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ status = pj_atomic_create(pool, 0, &mgr->tdata_counter);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
+ /* Set transport state callback */
+ status = pjsip_tpmgr_set_state_cb(mgr, &tp_state_callback);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ PJ_LOG(5, (THIS_FILE, "Transport manager created."));
+
+ *p_mgr = mgr;
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Find out the appropriate local address info (IP address and port) to
+ * advertise in Contact header based on the remote address to be
+ * contacted. The local address info would be the address name of the
+ * transport or listener which will be used to send the request.
+ *
+ * In this implementation, it will only select the transport based on
+ * the transport type in the request.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_find_local_addr( pjsip_tpmgr *tpmgr,
+ pj_pool_t *pool,
+ pjsip_transport_type_e type,
+ const pjsip_tpselector *sel,
+ pj_str_t *ip_addr,
+ int *port)
+{
+ pj_status_t status = PJSIP_EUNSUPTRANSPORT;
+ unsigned flag;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(tpmgr && pool && ip_addr && port, PJ_EINVAL);
+
+ ip_addr->slen = 0;
+ *port = 0;
+
+ flag = pjsip_transport_get_flag_from_type(type);
+
+ if (sel && sel->type == PJSIP_TPSELECTOR_TRANSPORT &&
+ sel->u.transport)
+ {
+ pj_strdup(pool, ip_addr, &sel->u.transport->local_name.host);
+ *port = sel->u.transport->local_name.port;
+ status = PJ_SUCCESS;
+
+ } else if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER &&
+ sel->u.listener)
+ {
+ pj_strdup(pool, ip_addr, &sel->u.listener->addr_name.host);
+ *port = sel->u.listener->addr_name.port;
+ status = PJ_SUCCESS;
+
+ } else if ((flag & PJSIP_TRANSPORT_DATAGRAM) != 0) {
+
+ pj_sockaddr remote;
+ int addr_len;
+ pjsip_transport *tp;
+
+ pj_bzero(&remote, sizeof(remote));
+ if (type & PJSIP_TRANSPORT_IPV6) {
+ addr_len = sizeof(pj_sockaddr_in6);
+ remote.addr.sa_family = pj_AF_INET6();
+ } else {
+ addr_len = sizeof(pj_sockaddr_in);
+ remote.addr.sa_family = pj_AF_INET();
+ }
+
+ status = pjsip_tpmgr_acquire_transport(tpmgr, type, &remote,
+ addr_len, NULL, &tp);
+
+ if (status == PJ_SUCCESS) {
+ pj_strdup(pool, ip_addr, &tp->local_name.host);
+ *port = tp->local_name.port;
+ status = PJ_SUCCESS;
+
+ pjsip_transport_dec_ref(tp);
+ }
+
+ } else {
+ /* For connection oriented transport, enum the factories */
+ pjsip_tpfactory *f;
+
+ pj_lock_acquire(tpmgr->lock);
+
+ f = tpmgr->factory_list.next;
+ while (f != &tpmgr->factory_list) {
+ if (f->type == type)
+ break;
+ f = f->next;
+ }
+
+ if (f != &tpmgr->factory_list) {
+ pj_strdup(pool, ip_addr, &f->addr_name.host);
+ *port = f->addr_name.port;
+ status = PJ_SUCCESS;
+ }
+ pj_lock_release(tpmgr->lock);
+ }
+
+ return status;
+}
+
+/*
+ * Return number of transports currently registered to the transport
+ * manager.
+ */
+PJ_DEF(unsigned) pjsip_tpmgr_get_transport_count(pjsip_tpmgr *mgr)
+{
+ pj_hash_iterator_t itr_val;
+ pj_hash_iterator_t *itr;
+ int nr_of_transports = 0;
+
+ pj_lock_acquire(mgr->lock);
+
+ itr = pj_hash_first(mgr->table, &itr_val);
+ while (itr) {
+ nr_of_transports++;
+ itr = pj_hash_next(mgr->table, itr);
+ }
+
+ pj_lock_release(mgr->lock);
+
+ return nr_of_transports;
+}
+
+/*
+ * pjsip_tpmgr_destroy()
+ *
+ * Destroy transport manager.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_destroy( pjsip_tpmgr *mgr )
+{
+ pj_hash_iterator_t itr_val;
+ pj_hash_iterator_t *itr;
+ pjsip_tpfactory *factory;
+ pjsip_endpoint *endpt = mgr->endpt;
+
+ PJ_LOG(5, (THIS_FILE, "Destroying transport manager"));
+
+ pj_lock_acquire(mgr->lock);
+
+ /*
+ * Destroy all transports.
+ */
+ itr = pj_hash_first(mgr->table, &itr_val);
+ while (itr != NULL) {
+ pj_hash_iterator_t *next;
+ pjsip_transport *transport;
+
+ transport = (pjsip_transport*) pj_hash_this(mgr->table, itr);
+
+ next = pj_hash_next(mgr->table, itr);
+
+ destroy_transport(mgr, transport);
+
+ itr = next;
+ }
+
+ /*
+ * Destroy all factories/listeners.
+ */
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+ pjsip_tpfactory *next = factory->next;
+
+ factory->destroy(factory);
+
+ factory = next;
+ }
+
+ pj_lock_release(mgr->lock);
+ pj_lock_destroy(mgr->lock);
+
+ /* Unregister mod_msg_print. */
+ if (mod_msg_print.id != -1) {
+ pjsip_endpt_unregister_module(endpt, &mod_msg_print);
+ }
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ /* If you encounter assert error on this line, it means there are
+ * leakings in transmit data (i.e. some transmit data have not been
+ * destroyed).
+ */
+ //pj_assert(pj_atomic_get(mgr->tdata_counter) == 0);
+ if (pj_atomic_get(mgr->tdata_counter) != 0) {
+ PJ_LOG(3,(THIS_FILE, "Warning: %d transmit buffer(s) not freed!",
+ pj_atomic_get(mgr->tdata_counter)));
+ }
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * pjsip_tpmgr_receive_packet()
+ *
+ * Called by tranports when they receive a new packet.
+ */
+PJ_DEF(pj_ssize_t) pjsip_tpmgr_receive_packet( pjsip_tpmgr *mgr,
+ pjsip_rx_data *rdata)
+{
+ pjsip_transport *tr = rdata->tp_info.transport;
+
+ char *current_pkt;
+ pj_size_t remaining_len;
+ pj_size_t total_processed = 0;
+
+ /* Check size. */
+ pj_assert(rdata->pkt_info.len > 0);
+ if (rdata->pkt_info.len <= 0)
+ return -1;
+
+ current_pkt = rdata->pkt_info.packet;
+ remaining_len = rdata->pkt_info.len;
+
+ /* Must NULL terminate buffer. This is the requirement of the
+ * parser etc.
+ */
+ current_pkt[remaining_len] = '\0';
+
+ /* Process all message fragments. */
+ while (remaining_len > 0) {
+
+ pjsip_msg *msg;
+ char *p, *end;
+ char saved;
+ pj_size_t msg_fragment_size;
+
+ /* Skip leading newlines as pjsip_find_msg() currently can't
+ * handle leading newlines.
+ */
+ for (p=current_pkt, end=p+remaining_len; p!=end; ++p) {
+ if (*p != '\r' && *p != '\n')
+ break;
+ }
+ if (p!=current_pkt) {
+ remaining_len -= (p - current_pkt);
+ total_processed += (p - current_pkt);
+ current_pkt = p;
+ if (remaining_len == 0) {
+ return total_processed;
+ }
+ }
+
+ /* Initialize default fragment size. */
+ msg_fragment_size = remaining_len;
+
+ /* Clear and init msg_info in rdata.
+ * Endpoint might inspect the values there when we call the callback
+ * to report some errors.
+ */
+ pj_bzero(&rdata->msg_info, sizeof(rdata->msg_info));
+ pj_list_init(&rdata->msg_info.parse_err);
+ rdata->msg_info.msg_buf = current_pkt;
+ rdata->msg_info.len = remaining_len;
+
+ /* For TCP transport, check if the whole message has been received. */
+ if ((tr->flag & PJSIP_TRANSPORT_DATAGRAM) == 0) {
+ pj_status_t msg_status;
+ msg_status = pjsip_find_msg(current_pkt, remaining_len, PJ_FALSE,
+ &msg_fragment_size);
+ if (msg_status != PJ_SUCCESS) {
+ if (remaining_len == PJSIP_MAX_PKT_LEN) {
+ mgr->on_rx_msg(mgr->endpt, PJSIP_ERXOVERFLOW, rdata);
+ /* Exhaust all data. */
+ return rdata->pkt_info.len;
+ } else {
+ /* Not enough data in packet. */
+ return total_processed;
+ }
+ }
+ }
+
+ /* Update msg_info. */
+ rdata->msg_info.len = msg_fragment_size;
+
+ /* Null terminate packet */
+ saved = current_pkt[msg_fragment_size];
+ current_pkt[msg_fragment_size] = '\0';
+
+ /* Parse the message. */
+ rdata->msg_info.msg = msg =
+ pjsip_parse_rdata( current_pkt, msg_fragment_size, rdata);
+
+ /* Restore null termination */
+ current_pkt[msg_fragment_size] = saved;
+
+ /* Check for parsing syntax error */
+ if (msg==NULL || !pj_list_empty(&rdata->msg_info.parse_err)) {
+ pjsip_parser_err_report *err;
+ char buf[128];
+ pj_str_t tmp;
+
+ /* Gather syntax error information */
+ tmp.ptr = buf; tmp.slen = 0;
+ err = rdata->msg_info.parse_err.next;
+ while (err != &rdata->msg_info.parse_err) {
+ int len;
+ len = pj_ansi_snprintf(tmp.ptr+tmp.slen, sizeof(buf)-tmp.slen,
+ ": %s exception when parsing '%.*s' "
+ "header on line %d col %d",
+ pj_exception_id_name(err->except_code),
+ (int)err->hname.slen, err->hname.ptr,
+ err->line, err->col);
+ if (len > 0 && len < (int) (sizeof(buf)-tmp.slen)) {
+ tmp.slen += len;
+ }
+ err = err->next;
+ }
+
+ /* Only print error message if there's error.
+ * Sometimes we receive blank packets (packets with only CRLF)
+ * which were sent to keep NAT bindings.
+ */
+ if (tmp.slen) {
+ PJ_LOG(1, (THIS_FILE,
+ "Error processing %d bytes packet from %s %s:%d %.*s:\n"
+ "%.*s\n"
+ "-- end of packet.",
+ msg_fragment_size,
+ rdata->tp_info.transport->type_name,
+ rdata->pkt_info.src_name,
+ rdata->pkt_info.src_port,
+ (int)tmp.slen, tmp.ptr,
+ (int)msg_fragment_size,
+ rdata->msg_info.msg_buf));
+ }
+
+ goto finish_process_fragment;
+ }
+
+ /* Perform basic header checking. */
+ if (rdata->msg_info.cid == NULL ||
+ rdata->msg_info.cid->id.slen == 0 ||
+ rdata->msg_info.from == NULL ||
+ rdata->msg_info.to == NULL ||
+ rdata->msg_info.via == NULL ||
+ rdata->msg_info.cseq == NULL)
+ {
+ mgr->on_rx_msg(mgr->endpt, PJSIP_EMISSINGHDR, rdata);
+ goto finish_process_fragment;
+ }
+
+ /* For request: */
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) {
+ /* always add received parameter to the via. */
+ pj_strdup2(rdata->tp_info.pool,
+ &rdata->msg_info.via->recvd_param,
+ rdata->pkt_info.src_name);
+
+ /* RFC 3581:
+ * If message contains "rport" param, put the received port there.
+ */
+ if (rdata->msg_info.via->rport_param == 0) {
+ rdata->msg_info.via->rport_param = rdata->pkt_info.src_port;
+ }
+ } else {
+ /* Drop malformed responses */
+ if (rdata->msg_info.msg->line.status.code < 100 ||
+ rdata->msg_info.msg->line.status.code >= 700)
+ {
+ mgr->on_rx_msg(mgr->endpt, PJSIP_EINVALIDSTATUS, rdata);
+ goto finish_process_fragment;
+ }
+ }
+
+ /* Drop response message if it has more than one Via.
+ */
+ /* This is wrong. Proxy DOES receive responses with multiple
+ * Via headers! Thanks Aldo <acampi at deis.unibo.it> for pointing
+ * this out.
+
+ if (msg->type == PJSIP_RESPONSE_MSG) {
+ pjsip_hdr *hdr;
+ hdr = (pjsip_hdr*)rdata->msg_info.via->next;
+ if (hdr != &msg->hdr) {
+ hdr = pjsip_msg_find_hdr(msg, PJSIP_H_VIA, hdr);
+ if (hdr) {
+ mgr->on_rx_msg(mgr->endpt, PJSIP_EMULTIPLEVIA, rdata);
+ goto finish_process_fragment;
+ }
+ }
+ }
+ */
+
+ /* Call the transport manager's upstream message callback.
+ */
+ mgr->on_rx_msg(mgr->endpt, PJ_SUCCESS, rdata);
+
+
+finish_process_fragment:
+ total_processed += msg_fragment_size;
+ current_pkt += msg_fragment_size;
+ remaining_len -= msg_fragment_size;
+
+ } /* while (rdata->pkt_info.len > 0) */
+
+
+ return total_processed;
+}
+
+
+/*
+ * pjsip_tpmgr_acquire_transport()
+ *
+ * Get transport suitable to communicate to remote. Create a new one
+ * if necessary.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport(pjsip_tpmgr *mgr,
+ pjsip_transport_type_e type,
+ const pj_sockaddr_t *remote,
+ int addr_len,
+ const pjsip_tpselector *sel,
+ pjsip_transport **tp)
+{
+ return pjsip_tpmgr_acquire_transport2(mgr, type, remote, addr_len, sel,
+ NULL, tp);
+}
+
+/*
+ * pjsip_tpmgr_acquire_transport2()
+ *
+ * Get transport suitable to communicate to remote. Create a new one
+ * if necessary.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr,
+ pjsip_transport_type_e type,
+ const pj_sockaddr_t *remote,
+ int addr_len,
+ const pjsip_tpselector *sel,
+ pjsip_tx_data *tdata,
+ pjsip_transport **tp)
+{
+ pjsip_tpfactory *factory;
+ pj_status_t status;
+
+ TRACE_((THIS_FILE,"Acquiring transport type=%s, remote=%s:%d",
+ pjsip_transport_get_type_name(type),
+ addr_string(remote),
+ pj_sockaddr_get_port(remote)));
+
+ pj_lock_acquire(mgr->lock);
+
+ /* If transport is specified, then just use it if it is suitable
+ * for the destination.
+ */
+ if (sel && sel->type == PJSIP_TPSELECTOR_TRANSPORT &&
+ sel->u.transport)
+ {
+ pjsip_transport *seltp = sel->u.transport;
+
+ /* See if the transport is (not) suitable */
+ if (seltp->key.type != type) {
+ pj_lock_release(mgr->lock);
+ return PJSIP_ETPNOTSUITABLE;
+ }
+
+ /* We could also verify that the destination address is reachable
+ * from this transport (i.e. both are equal), but if application
+ * has requested a specific transport to be used, assume that
+ * it knows what to do.
+ *
+ * In other words, I don't think destination verification is a good
+ * idea for now.
+ */
+
+ /* Transport looks to be suitable to use, so just use it. */
+ pjsip_transport_add_ref(seltp);
+ pj_lock_release(mgr->lock);
+ *tp = seltp;
+
+ TRACE_((THIS_FILE, "Transport %s acquired", seltp->obj_name));
+ return PJ_SUCCESS;
+
+
+ } else if (sel && sel->type == PJSIP_TPSELECTOR_LISTENER &&
+ sel->u.listener)
+ {
+ /* Application has requested that a specific listener is to
+ * be used. In this case, skip transport hash table lookup.
+ */
+
+ /* Verify that the listener type matches the destination type */
+ if (sel->u.listener->type != type) {
+ pj_lock_release(mgr->lock);
+ return PJSIP_ETPNOTSUITABLE;
+ }
+
+ /* We'll use this listener to create transport */
+ factory = sel->u.listener;
+
+ } else {
+
+ /*
+ * This is the "normal" flow, where application doesn't specify
+ * specific transport/listener to be used to send message to.
+ * In this case, lookup the transport from the hash table.
+ */
+ pjsip_transport_key key;
+ int key_len;
+ pjsip_transport *transport;
+
+ pj_bzero(&key, sizeof(key));
+ key_len = sizeof(key.type) + addr_len;
+
+ /* First try to get exact destination. */
+ key.type = type;
+ pj_memcpy(&key.rem_addr, remote, addr_len);
+
+ transport = (pjsip_transport*)
+ pj_hash_get(mgr->table, &key, key_len, NULL);
+
+ if (transport == NULL) {
+ unsigned flag = pjsip_transport_get_flag_from_type(type);
+ const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote;
+
+
+ /* Ignore address for loop transports. */
+ if (type == PJSIP_TRANSPORT_LOOP ||
+ type == PJSIP_TRANSPORT_LOOP_DGRAM)
+ {
+ pj_sockaddr *addr = &key.rem_addr;
+
+ pj_bzero(addr, addr_len);
+ key_len = sizeof(key.type) + addr_len;
+ transport = (pjsip_transport*)
+ pj_hash_get(mgr->table, &key, key_len, NULL);
+ }
+ /* For datagram transports, try lookup with zero address.
+ */
+ else if (flag & PJSIP_TRANSPORT_DATAGRAM)
+ {
+ pj_sockaddr *addr = &key.rem_addr;
+
+ pj_bzero(addr, addr_len);
+ addr->addr.sa_family = remote_addr->addr.sa_family;
+
+ key_len = sizeof(key.type) + addr_len;
+ transport = (pjsip_transport*)
+ pj_hash_get(mgr->table, &key, key_len, NULL);
+ }
+ }
+
+ if (transport!=NULL && !transport->is_shutdown) {
+ /*
+ * Transport found!
+ */
+ pjsip_transport_add_ref(transport);
+ pj_lock_release(mgr->lock);
+ *tp = transport;
+
+ TRACE_((THIS_FILE, "Transport %s acquired", transport->obj_name));
+ return PJ_SUCCESS;
+ }
+
+ /*
+ * Transport not found!
+ * Find factory that can create such transport.
+ */
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+ if (factory->type == type)
+ break;
+ factory = factory->next;
+ }
+
+ if (factory == &mgr->factory_list) {
+ /* No factory can create the transport! */
+ pj_lock_release(mgr->lock);
+ TRACE_((THIS_FILE, "No suitable factory was found either"));
+ return PJSIP_EUNSUPTRANSPORT;
+ }
+ }
+
+ TRACE_((THIS_FILE, "Creating new transport from factory"));
+
+ /* Request factory to create transport. */
+ if (factory->create_transport2) {
+ status = factory->create_transport2(factory, mgr, mgr->endpt,
+ (const pj_sockaddr*) remote,
+ addr_len, tdata, tp);
+ } else {
+ status = factory->create_transport(factory, mgr, mgr->endpt,
+ (const pj_sockaddr*) remote,
+ addr_len, tp);
+ }
+ if (status == PJ_SUCCESS) {
+ PJ_ASSERT_ON_FAIL(tp!=NULL,
+ {pj_lock_release(mgr->lock); return PJ_EBUG;});
+ pjsip_transport_add_ref(*tp);
+ }
+ pj_lock_release(mgr->lock);
+ return status;
+}
+
+/**
+ * Dump transport info.
+ */
+PJ_DEF(void) pjsip_tpmgr_dump_transports(pjsip_tpmgr *mgr)
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ pj_hash_iterator_t itr_val;
+ pj_hash_iterator_t *itr;
+ pjsip_tpfactory *factory;
+
+ pj_lock_acquire(mgr->lock);
+
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+ PJ_LOG(3,(THIS_FILE, " Outstanding transmit buffers: %d",
+ pj_atomic_get(mgr->tdata_counter)));
+#endif
+
+ PJ_LOG(3, (THIS_FILE, " Dumping listeners:"));
+ factory = mgr->factory_list.next;
+ while (factory != &mgr->factory_list) {
+ PJ_LOG(3, (THIS_FILE, " %s %s:%.*s:%d",
+ factory->obj_name,
+ factory->type_name,
+ (int)factory->addr_name.host.slen,
+ factory->addr_name.host.ptr,
+ (int)factory->addr_name.port));
+ factory = factory->next;
+ }
+
+ itr = pj_hash_first(mgr->table, &itr_val);
+ if (itr) {
+ PJ_LOG(3, (THIS_FILE, " Dumping transports:"));
+
+ do {
+ pjsip_transport *t = (pjsip_transport*)
+ pj_hash_this(mgr->table, itr);
+
+ PJ_LOG(3, (THIS_FILE, " %s %s (refcnt=%d%s)",
+ t->obj_name,
+ t->info,
+ pj_atomic_get(t->ref_cnt),
+ (t->idle_timer.id ? " [idle]" : "")));
+
+ itr = pj_hash_next(mgr->table, itr);
+ } while (itr);
+ }
+
+ pj_lock_release(mgr->lock);
+#else
+ PJ_UNUSED_ARG(mgr);
+#endif
+}
+
+/**
+ * Set callback of global transport state notification.
+ */
+PJ_DEF(pj_status_t) pjsip_tpmgr_set_state_cb(pjsip_tpmgr *mgr,
+ pjsip_tp_state_callback cb)
+{
+ PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+ mgr->tp_state_cb = cb;
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Get callback of global transport state notification.
+ */
+PJ_DEF(pjsip_tp_state_callback) pjsip_tpmgr_get_state_cb(
+ const pjsip_tpmgr *mgr)
+{
+ PJ_ASSERT_RETURN(mgr, NULL);
+
+ return mgr->tp_state_cb;
+}
+
+
+/**
+ * Allocate and init transport data.
+ */
+static void init_tp_data(pjsip_transport *tp)
+{
+ transport_data *tp_data;
+
+ pj_assert(tp && !tp->data);
+
+ tp_data = PJ_POOL_ZALLOC_T(tp->pool, transport_data);
+ pj_list_init(&tp_data->st_listeners);
+ pj_list_init(&tp_data->st_listeners_empty);
+ tp->data = tp_data;
+}
+
+
+static void tp_state_callback(pjsip_transport *tp,
+ pjsip_transport_state state,
+ const pjsip_transport_state_info *info)
+{
+ transport_data *tp_data;
+
+ pj_lock_acquire(tp->lock);
+
+ tp_data = (transport_data*)tp->data;
+
+ /* Notify the transport state listeners, if any. */
+ if (!tp_data || pj_list_empty(&tp_data->st_listeners)) {
+ goto on_return;
+ } else {
+ pjsip_transport_state_info st_info;
+ tp_state_listener *st_listener = tp_data->st_listeners.next;
+
+ /* As we need to put the user data into the transport state info,
+ * let's use a copy of transport state info.
+ */
+ pj_memcpy(&st_info, info, sizeof(st_info));
+ while (st_listener != &tp_data->st_listeners) {
+ st_info.user_data = st_listener->user_data;
+ (*st_listener->cb)(tp, state, &st_info);
+
+ st_listener = st_listener->next;
+ }
+ }
+
+on_return:
+ pj_lock_release(tp->lock);
+}
+
+
+/**
+ * Add a listener to the specified transport for transport state notification.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_add_state_listener (
+ pjsip_transport *tp,
+ pjsip_tp_state_callback cb,
+ void *user_data,
+ pjsip_tp_state_listener_key **key)
+{
+ transport_data *tp_data;
+ tp_state_listener *entry;
+
+ PJ_ASSERT_RETURN(tp && cb && key, PJ_EINVAL);
+
+ pj_lock_acquire(tp->lock);
+
+ /* Init transport data, if it hasn't */
+ if (!tp->data)
+ init_tp_data(tp);
+
+ tp_data = (transport_data*)tp->data;
+
+ /* Init the new listener entry. Use available empty slot, if any,
+ * otherwise allocate it using the transport pool.
+ */
+ if (!pj_list_empty(&tp_data->st_listeners_empty)) {
+ entry = tp_data->st_listeners_empty.next;
+ pj_list_erase(entry);
+ } else {
+ entry = PJ_POOL_ZALLOC_T(tp->pool, tp_state_listener);
+ }
+ entry->cb = cb;
+ entry->user_data = user_data;
+
+ /* Add the new listener entry to the listeners list */
+ pj_list_push_back(&tp_data->st_listeners, entry);
+
+ *key = entry;
+
+ pj_lock_release(tp->lock);
+
+ return PJ_SUCCESS;
+}
+
+/**
+ * Remove a listener from the specified transport for transport state
+ * notification.
+ */
+PJ_DEF(pj_status_t) pjsip_transport_remove_state_listener (
+ pjsip_transport *tp,
+ pjsip_tp_state_listener_key *key,
+ const void *user_data)
+{
+ transport_data *tp_data;
+ tp_state_listener *entry;
+
+ PJ_ASSERT_RETURN(tp && key, PJ_EINVAL);
+
+ pj_lock_acquire(tp->lock);
+
+ tp_data = (transport_data*)tp->data;
+
+ /* Transport data is NULL or no registered listener? */
+ if (!tp_data || pj_list_empty(&tp_data->st_listeners)) {
+ pj_lock_release(tp->lock);
+ return PJ_ENOTFOUND;
+ }
+
+ entry = (tp_state_listener*)key;
+
+ /* Validate the user data */
+ if (entry->user_data != user_data) {
+ pj_assert(!"Invalid transport state listener key");
+ pj_lock_release(tp->lock);
+ return PJ_EBUG;
+ }
+
+ /* Reset the entry and move it to the empty list */
+ entry->cb = NULL;
+ entry->user_data = NULL;
+ pj_list_erase(entry);
+ pj_list_push_back(&tp_data->st_listeners_empty, entry);
+
+ pj_lock_release(tp->lock);
+
+ return PJ_SUCCESS;
+}
diff --git a/pjsip/src/pjsip/sip_transport_loop.c b/pjsip/src/pjsip/sip_transport_loop.c
new file mode 100644
index 0000000..498b529
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_loop.c
@@ -0,0 +1,509 @@
+/* $Id: sip_transport_loop.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 <pjsip/sip_transport_loop.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/pool.h>
+#include <pj/os.h>
+#include <pj/string.h>
+#include <pj/lock.h>
+#include <pj/assert.h>
+#include <pj/compat/socket.h>
+
+
+#define ADDR_LOOP "128.0.0.1"
+#define ADDR_LOOP_DGRAM "129.0.0.1"
+
+
+/** This structure describes incoming packet. */
+struct recv_list
+{
+ PJ_DECL_LIST_MEMBER(struct recv_list);
+ pjsip_rx_data rdata;
+};
+
+/** This structure is used to keep delayed send failure. */
+struct send_list
+{
+ PJ_DECL_LIST_MEMBER(struct send_list);
+ pj_time_val sent_time;
+ pj_ssize_t sent;
+ pjsip_tx_data *tdata;
+ void *token;
+ void (*callback)(pjsip_transport*, void*, pj_ssize_t);
+};
+
+/** This structure describes the loop transport. */
+struct loop_transport
+{
+ pjsip_transport base;
+ pj_pool_t *pool;
+ pj_thread_t *thread;
+ pj_bool_t thread_quit_flag;
+ pj_bool_t discard;
+ int fail_mode;
+ unsigned recv_delay;
+ unsigned send_delay;
+ struct recv_list recv_list;
+ struct send_list send_list;
+};
+
+
+/* Helper function to create "incoming" packet */
+struct recv_list *create_incoming_packet( struct loop_transport *loop,
+ pjsip_tx_data *tdata )
+{
+ pj_pool_t *pool;
+ struct recv_list *pkt;
+
+ pool = pjsip_endpt_create_pool(loop->base.endpt, "rdata",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC+5);
+ if (!pool)
+ return NULL;
+
+ pkt = PJ_POOL_ZALLOC_T(pool, struct recv_list);
+
+ /* Initialize rdata. */
+ pkt->rdata.tp_info.pool = pool;
+ pkt->rdata.tp_info.transport = &loop->base;
+
+ /* Copy the packet. */
+ pj_memcpy(pkt->rdata.pkt_info.packet, tdata->buf.start,
+ tdata->buf.cur - tdata->buf.start);
+ pkt->rdata.pkt_info.len = tdata->buf.cur - tdata->buf.start;
+
+ /* the source address */
+ pkt->rdata.pkt_info.src_addr.addr.sa_family = pj_AF_INET();
+
+ /* "Source address" info. */
+ pkt->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in);
+ if (loop->base.key.type == PJSIP_TRANSPORT_LOOP) {
+ pj_ansi_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP);
+ } else {
+ pj_ansi_strcpy(pkt->rdata.pkt_info.src_name, ADDR_LOOP_DGRAM);
+ }
+ pkt->rdata.pkt_info.src_port = loop->base.local_name.port;
+
+ /* When do we need to "deliver" this packet. */
+ pj_gettimeofday(&pkt->rdata.pkt_info.timestamp);
+ pkt->rdata.pkt_info.timestamp.msec += loop->recv_delay;
+ pj_time_val_normalize(&pkt->rdata.pkt_info.timestamp);
+
+ /* Done. */
+
+ return pkt;
+}
+
+
+/* Helper function to add pending notification callback. */
+static pj_status_t add_notification( struct loop_transport *loop,
+ pjsip_tx_data *tdata,
+ pj_ssize_t sent,
+ void *token,
+ void (*callback)(pjsip_transport*,
+ void*, pj_ssize_t))
+{
+ struct send_list *sent_status;
+
+ pjsip_tx_data_add_ref(tdata);
+ pj_lock_acquire(tdata->lock);
+ sent_status = PJ_POOL_ALLOC_T(tdata->pool, struct send_list);
+ pj_lock_release(tdata->lock);
+
+ sent_status->sent = sent;
+ sent_status->tdata = tdata;
+ sent_status->token = token;
+ sent_status->callback = callback;
+
+ pj_gettimeofday(&sent_status->sent_time);
+ sent_status->sent_time.msec += loop->send_delay;
+ pj_time_val_normalize(&sent_status->sent_time);
+
+ pj_lock_acquire(loop->base.lock);
+ pj_list_push_back(&loop->send_list, sent_status);
+ pj_lock_release(loop->base.lock);
+
+ return PJ_SUCCESS;
+}
+
+/* Handler for sending outgoing message; called by transport manager. */
+static pj_status_t loop_send_msg( pjsip_transport *tp,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback cb)
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+ struct recv_list *recv_pkt;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ PJ_UNUSED_ARG(rem_addr);
+ PJ_UNUSED_ARG(addr_len);
+
+
+ /* Need to send failure? */
+ if (loop->fail_mode) {
+ if (loop->send_delay == 0) {
+ return PJ_STATUS_FROM_OS(OSERR_ECONNRESET);
+ } else {
+ add_notification(loop, tdata, -PJ_STATUS_FROM_OS(OSERR_ECONNRESET),
+ token, cb);
+
+ return PJ_EPENDING;
+ }
+ }
+
+ /* Discard any packets? */
+ if (loop->discard)
+ return PJ_SUCCESS;
+
+ /* Create rdata for the "incoming" packet. */
+ recv_pkt = create_incoming_packet(loop, tdata);
+ if (!recv_pkt)
+ return PJ_ENOMEM;
+
+ /* If delay is not configured, deliver this packet now! */
+ if (loop->recv_delay == 0) {
+ pj_ssize_t size_eaten;
+
+ size_eaten = pjsip_tpmgr_receive_packet( loop->base.tpmgr,
+ &recv_pkt->rdata);
+ pj_assert(size_eaten == recv_pkt->rdata.pkt_info.len);
+
+ pjsip_endpt_release_pool(loop->base.endpt,
+ recv_pkt->rdata.tp_info.pool);
+
+ } else {
+ /* Otherwise if delay is configured, add the "packet" to the
+ * receive list to be processed by worker thread.
+ */
+ pj_lock_acquire(loop->base.lock);
+ pj_list_push_back(&loop->recv_list, recv_pkt);
+ pj_lock_release(loop->base.lock);
+ }
+
+ if (loop->send_delay != 0) {
+ add_notification(loop, tdata, tdata->buf.cur - tdata->buf.start,
+ token, cb);
+ return PJ_EPENDING;
+ } else {
+ return PJ_SUCCESS;
+ }
+}
+
+/* Handler to destroy the transport; called by transport manager */
+static pj_status_t loop_destroy(pjsip_transport *tp)
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ loop->thread_quit_flag = 1;
+ /* Unlock transport mutex before joining thread. */
+ pj_lock_release(tp->lock);
+ pj_thread_join(loop->thread);
+ pj_thread_destroy(loop->thread);
+
+ /* Clear pending send notifications. */
+ while (!pj_list_empty(&loop->send_list)) {
+ struct send_list *node = loop->send_list.next;
+ /* Notify callback. */
+ if (node->callback) {
+ (*node->callback)(&loop->base, node->token, -PJSIP_ESHUTDOWN);
+ }
+ pj_list_erase(node);
+ pjsip_tx_data_dec_ref(node->tdata);
+ }
+
+ /* Clear "incoming" packets in the queue. */
+ while (!pj_list_empty(&loop->recv_list)) {
+ struct recv_list *node = loop->recv_list.next;
+ pj_list_erase(node);
+ pjsip_endpt_release_pool(loop->base.endpt,
+ node->rdata.tp_info.pool);
+ }
+
+ /* Self destruct.. heheh.. */
+ pj_lock_destroy(loop->base.lock);
+ pj_atomic_destroy(loop->base.ref_cnt);
+ pjsip_endpt_release_pool(loop->base.endpt, loop->base.pool);
+
+ return PJ_SUCCESS;
+}
+
+/* Worker thread for loop transport. */
+static int loop_transport_worker_thread(void *arg)
+{
+ struct loop_transport *loop = (struct loop_transport*) arg;
+ struct recv_list r;
+ struct send_list s;
+
+ pj_list_init(&r);
+ pj_list_init(&s);
+
+ while (!loop->thread_quit_flag) {
+ pj_time_val now;
+
+ pj_thread_sleep(1);
+ pj_gettimeofday(&now);
+
+ pj_lock_acquire(loop->base.lock);
+
+ /* Move expired send notification to local list. */
+ while (!pj_list_empty(&loop->send_list)) {
+ struct send_list *node = loop->send_list.next;
+
+ /* Break when next node time is greater than now. */
+ if (PJ_TIME_VAL_GTE(node->sent_time, now))
+ break;
+
+ /* Delete this from the list. */
+ pj_list_erase(node);
+
+ /* Add to local list. */
+ pj_list_push_back(&s, node);
+ }
+
+ /* Move expired "incoming" packet to local list. */
+ while (!pj_list_empty(&loop->recv_list)) {
+ struct recv_list *node = loop->recv_list.next;
+
+ /* Break when next node time is greater than now. */
+ if (PJ_TIME_VAL_GTE(node->rdata.pkt_info.timestamp, now))
+ break;
+
+ /* Delete this from the list. */
+ pj_list_erase(node);
+
+ /* Add to local list. */
+ pj_list_push_back(&r, node);
+
+ }
+
+ pj_lock_release(loop->base.lock);
+
+ /* Process send notification and incoming packet notification
+ * without holding down the loop's mutex.
+ */
+ while (!pj_list_empty(&s)) {
+ struct send_list *node = s.next;
+
+ pj_list_erase(node);
+
+ /* Notify callback. */
+ if (node->callback) {
+ (*node->callback)(&loop->base, node->token, node->sent);
+ }
+
+ /* Decrement tdata reference counter. */
+ pjsip_tx_data_dec_ref(node->tdata);
+ }
+
+ /* Process "incoming" packet. */
+ while (!pj_list_empty(&r)) {
+ struct recv_list *node = r.next;
+ pj_ssize_t size_eaten;
+
+ pj_list_erase(node);
+
+ /* Notify transport manager about the "incoming packet" */
+ size_eaten = pjsip_tpmgr_receive_packet(loop->base.tpmgr,
+ &node->rdata);
+
+ /* Must "eat" all the packets. */
+ pj_assert(size_eaten == node->rdata.pkt_info.len);
+
+ /* Done. */
+ pjsip_endpt_release_pool(loop->base.endpt,
+ node->rdata.tp_info.pool);
+ }
+ }
+
+ return 0;
+}
+
+
+/* Start loop transport. */
+PJ_DEF(pj_status_t) pjsip_loop_start( pjsip_endpoint *endpt,
+ pjsip_transport **transport)
+{
+ pj_pool_t *pool;
+ struct loop_transport *loop;
+ pj_status_t status;
+
+ /* Create pool. */
+ pool = pjsip_endpt_create_pool(endpt, "loop", 4000, 4000);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Create the loop structure. */
+ loop = PJ_POOL_ZALLOC_T(pool, struct loop_transport);
+
+ /* Initialize transport properties. */
+ pj_ansi_snprintf(loop->base.obj_name, sizeof(loop->base.obj_name),
+ "loop%p", loop);
+ loop->base.pool = pool;
+ status = pj_atomic_create(pool, 0, &loop->base.ref_cnt);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ status = pj_lock_create_recursive_mutex(pool, "loop", &loop->base.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ loop->base.key.type = PJSIP_TRANSPORT_LOOP_DGRAM;
+ //loop->base.key.rem_addr.sa_family = pj_AF_INET();
+ loop->base.type_name = "LOOP-DGRAM";
+ loop->base.info = "LOOP-DGRAM";
+ loop->base.flag = PJSIP_TRANSPORT_DATAGRAM;
+ loop->base.local_name.host = pj_str(ADDR_LOOP_DGRAM);
+ loop->base.local_name.port =
+ pjsip_transport_get_default_port_for_type((pjsip_transport_type_e)
+ loop->base.key.type);
+ loop->base.addr_len = sizeof(pj_sockaddr_in);
+ loop->base.dir = PJSIP_TP_DIR_NONE;
+ loop->base.endpt = endpt;
+ loop->base.tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ loop->base.send_msg = &loop_send_msg;
+ loop->base.destroy = &loop_destroy;
+
+ pj_list_init(&loop->recv_list);
+ pj_list_init(&loop->send_list);
+
+ /* Create worker thread. */
+ status = pj_thread_create(pool, "loop",
+ &loop_transport_worker_thread, loop, 0,
+ PJ_THREAD_SUSPENDED, &loop->thread);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Register to transport manager. */
+ status = pjsip_transport_register( loop->base.tpmgr, &loop->base);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Start the thread. */
+ status = pj_thread_resume(loop->thread);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /*
+ * Done.
+ */
+
+ if (transport)
+ *transport = &loop->base;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (loop->base.lock)
+ pj_lock_destroy(loop->base.lock);
+ if (loop->thread)
+ pj_thread_destroy(loop->thread);
+ if (loop->base.ref_cnt)
+ pj_atomic_destroy(loop->base.ref_cnt);
+ pjsip_endpt_release_pool(endpt, loop->pool);
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_loop_set_discard( pjsip_transport *tp,
+ pj_bool_t discard,
+ pj_bool_t *prev_value )
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ if (prev_value)
+ *prev_value = loop->discard;
+ loop->discard = discard;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_loop_set_failure( pjsip_transport *tp,
+ int fail_flag,
+ int *prev_value )
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ if (prev_value)
+ *prev_value = loop->fail_mode;
+ loop->fail_mode = fail_flag;
+
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_loop_set_recv_delay( pjsip_transport *tp,
+ unsigned delay,
+ unsigned *prev_value)
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ if (prev_value)
+ *prev_value = loop->recv_delay;
+ loop->recv_delay = delay;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_loop_set_send_callback_delay( pjsip_transport *tp,
+ unsigned delay,
+ unsigned *prev_value)
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ if (prev_value)
+ *prev_value = loop->send_delay;
+ loop->send_delay = delay;
+
+ return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pjsip_loop_set_delay( pjsip_transport *tp, unsigned delay )
+{
+ struct loop_transport *loop = (struct loop_transport*)tp;
+
+ PJ_ASSERT_RETURN(tp && (tp->key.type == PJSIP_TRANSPORT_LOOP ||
+ tp->key.type == PJSIP_TRANSPORT_LOOP_DGRAM), PJ_EINVAL);
+
+ loop->recv_delay = delay;
+ loop->send_delay = delay;
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c
new file mode 100644
index 0000000..141434b
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_tcp.c
@@ -0,0 +1,1417 @@
+/* $Id: sip_transport_tcp.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_transport_tcp.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/compat/socket.h>
+#include <pj/addr_resolv.h>
+#include <pj/activesock.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+/* Only declare the API if PJ_HAS_TCP is true */
+#if defined(PJ_HAS_TCP) && PJ_HAS_TCP!=0
+
+
+#define THIS_FILE "sip_transport_tcp.c"
+
+#define MAX_ASYNC_CNT 16
+#define POOL_LIS_INIT 512
+#define POOL_LIS_INC 512
+#define POOL_TP_INIT 512
+#define POOL_TP_INC 512
+
+struct tcp_listener;
+struct tcp_transport;
+
+
+/*
+ * This is the TCP listener, which is a "descendant" of pjsip_tpfactory (the
+ * SIP transport factory).
+ */
+struct tcp_listener
+{
+ pjsip_tpfactory factory;
+ pj_bool_t is_registered;
+ pjsip_endpoint *endpt;
+ pjsip_tpmgr *tpmgr;
+ pj_activesock_t *asock;
+ pj_qos_type qos_type;
+ pj_qos_params qos_params;
+};
+
+
+/*
+ * This structure is used to keep delayed transmit operation in a list.
+ * A delayed transmission occurs when application sends tx_data when
+ * the TCP connect/establishment is still in progress. These delayed
+ * transmission will be "flushed" once the socket is connected (either
+ * successfully or with errors).
+ */
+struct delayed_tdata
+{
+ PJ_DECL_LIST_MEMBER(struct delayed_tdata);
+ pjsip_tx_data_op_key *tdata_op_key;
+};
+
+
+/*
+ * This structure describes the TCP transport, and it's descendant of
+ * pjsip_transport.
+ */
+struct tcp_transport
+{
+ pjsip_transport base;
+ pj_bool_t is_server;
+
+ /* Do not save listener instance in the transport, because
+ * listener might be destroyed during transport's lifetime.
+ * See http://trac.pjsip.org/repos/ticket/491
+ struct tcp_listener *listener;
+ */
+
+ pj_bool_t is_registered;
+ pj_bool_t is_closing;
+ pj_status_t close_reason;
+ pj_sock_t sock;
+ pj_activesock_t *asock;
+ pj_bool_t has_pending_connect;
+
+ /* Keep-alive timer. */
+ pj_timer_entry ka_timer;
+ pj_time_val last_activity;
+ pjsip_tx_data_op_key ka_op_key;
+ pj_str_t ka_pkt;
+
+ /* TCP transport can only have one rdata!
+ * Otherwise chunks of incoming PDU may be received on different
+ * buffer.
+ */
+ pjsip_rx_data rdata;
+
+ /* Pending transmission list. */
+ struct delayed_tdata delayed_list;
+};
+
+
+/****************************************************************************
+ * PROTOTYPES
+ */
+
+/* This callback is called when pending accept() operation completes. */
+static pj_bool_t on_accept_complete(pj_activesock_t *asock,
+ pj_sock_t newsock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len);
+
+/* This callback is called by transport manager to destroy listener */
+static pj_status_t lis_destroy(pjsip_tpfactory *factory);
+
+/* This callback is called by transport manager to create transport */
+static pj_status_t lis_create_transport(pjsip_tpfactory *factory,
+ pjsip_tpmgr *mgr,
+ pjsip_endpoint *endpt,
+ const pj_sockaddr *rem_addr,
+ int addr_len,
+ pjsip_transport **transport);
+
+/* Common function to create and initialize transport */
+static pj_status_t tcp_create(struct tcp_listener *listener,
+ pj_pool_t *pool,
+ pj_sock_t sock, pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ struct tcp_transport **p_tcp);
+
+
+static void tcp_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+static void sockaddr_to_host_port( pj_pool_t *pool,
+ pjsip_host_port *host_port,
+ const pj_sockaddr_in *addr )
+{
+ host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4);
+ pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 2);
+ host_port->host.slen = pj_ansi_strlen(host_port->host.ptr);
+ host_port->port = pj_sockaddr_get_port(addr);
+}
+
+
+static void tcp_init_shutdown(struct tcp_transport *tcp, pj_status_t status)
+{
+ pjsip_tp_state_callback state_cb;
+
+ if (tcp->close_reason == PJ_SUCCESS)
+ tcp->close_reason = status;
+
+ if (tcp->base.is_shutdown)
+ return;
+
+ /* Prevent immediate transport destroy by application, as transport
+ * state notification callback may be stacked and transport instance
+ * must remain valid at any point in the callback.
+ */
+ pjsip_transport_add_ref(&tcp->base);
+
+ /* Notify application of transport disconnected state */
+ state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+
+ pj_bzero(&state_info, sizeof(state_info));
+ state_info.status = tcp->close_reason;
+ (*state_cb)(&tcp->base, PJSIP_TP_STATE_DISCONNECTED, &state_info);
+ }
+
+ /* We can not destroy the transport since high level objects may
+ * still keep reference to this transport. So we can only
+ * instruct transport manager to gracefully start the shutdown
+ * procedure for this transport.
+ */
+ pjsip_transport_shutdown(&tcp->base);
+
+ /* Now, it is ok to destroy the transport. */
+ pjsip_transport_dec_ref(&tcp->base);
+}
+
+
+/*
+ * Initialize pjsip_tcp_transport_cfg structure with default values.
+ */
+PJ_DEF(void) pjsip_tcp_transport_cfg_default(pjsip_tcp_transport_cfg *cfg,
+ int af)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+ cfg->af = af;
+ pj_sockaddr_init(cfg->af, &cfg->bind_addr, NULL, 0);
+ cfg->async_cnt = 1;
+}
+
+
+/****************************************************************************
+ * The TCP listener/transport factory.
+ */
+
+/*
+ * This is the public API to create, initialize, register, and start the
+ * TCP listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tcp_transport_start3(
+ pjsip_endpoint *endpt,
+ const pjsip_tcp_transport_cfg *cfg,
+ pjsip_tpfactory **p_factory
+ )
+{
+ pj_pool_t *pool;
+ pj_sock_t sock = PJ_INVALID_SOCKET;
+ struct tcp_listener *listener;
+ pj_activesock_cfg asock_cfg;
+ pj_activesock_cb listener_cb;
+ pj_sockaddr *listener_addr;
+ int addr_len;
+ pj_status_t status;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(endpt && cfg->async_cnt, PJ_EINVAL);
+
+ /* Verify that address given in a_name (if any) is valid */
+ if (cfg->addr_name.host.slen) {
+ pj_sockaddr tmp;
+
+ status = pj_sockaddr_init(cfg->af, &tmp, &cfg->addr_name.host,
+ (pj_uint16_t)cfg->addr_name.port);
+ if (status != PJ_SUCCESS || !pj_sockaddr_has_addr(&tmp) ||
+ (cfg->af==pj_AF_INET() &&
+ tmp.ipv4.sin_addr.s_addr==PJ_INADDR_NONE))
+ {
+ /* Invalid address */
+ return PJ_EINVAL;
+ }
+ }
+
+ pool = pjsip_endpt_create_pool(endpt, "tcplis", POOL_LIS_INIT,
+ POOL_LIS_INC);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+
+ listener = PJ_POOL_ZALLOC_T(pool, struct tcp_listener);
+ listener->factory.pool = pool;
+ listener->factory.type = PJSIP_TRANSPORT_TCP;
+ listener->factory.type_name = "tcp";
+ listener->factory.flag =
+ pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TCP);
+ listener->qos_type = cfg->qos_type;
+ pj_memcpy(&listener->qos_params, &cfg->qos_params,
+ sizeof(cfg->qos_params));
+
+ pj_ansi_strcpy(listener->factory.obj_name, "tcplis");
+
+ status = pj_lock_create_recursive_mutex(pool, "tcplis",
+ &listener->factory.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Create socket */
+ status = pj_sock_socket(cfg->af, pj_SOCK_STREAM(), 0, &sock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Apply QoS, if specified */
+ status = pj_sock_apply_qos2(sock, cfg->qos_type, &cfg->qos_params,
+ 2, listener->factory.obj_name,
+ "SIP TCP listener socket");
+
+ /* Bind socket */
+ listener_addr = &listener->factory.local_addr;
+ pj_sockaddr_cp(listener_addr, &cfg->bind_addr);
+
+ status = pj_sock_bind(sock, listener_addr,
+ pj_sockaddr_get_len(listener_addr));
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Retrieve the bound address */
+ addr_len = pj_sockaddr_get_len(listener_addr);
+ status = pj_sock_getsockname(sock, listener_addr, &addr_len);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* If published host/IP is specified, then use that address as the
+ * listener advertised address.
+ */
+ if (cfg->addr_name.host.slen) {
+ /* Copy the address */
+ listener->factory.addr_name = cfg->addr_name;
+ pj_strdup(listener->factory.pool, &listener->factory.addr_name.host,
+ &cfg->addr_name.host);
+ listener->factory.addr_name.port = cfg->addr_name.port;
+
+ } else {
+ /* No published address is given, use the bound address */
+
+ /* If the address returns 0.0.0.0, use the default
+ * interface address as the transport's address.
+ */
+ if (!pj_sockaddr_has_addr(listener_addr)) {
+ pj_sockaddr hostip;
+
+ status = pj_gethostip(pj_AF_INET(), &hostip);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ pj_memcpy(pj_sockaddr_get_addr(listener_addr),
+ pj_sockaddr_get_addr(&hostip),
+ pj_sockaddr_get_addr_len(&hostip));
+ }
+
+ /* Save the address name */
+ sockaddr_to_host_port(listener->factory.pool,
+ &listener->factory.addr_name,
+ (pj_sockaddr_in*)listener_addr);
+ }
+
+ /* If port is zero, get the bound port */
+ if (listener->factory.addr_name.port == 0) {
+ listener->factory.addr_name.port = pj_sockaddr_get_port(listener_addr);
+ }
+
+ pj_ansi_snprintf(listener->factory.obj_name,
+ sizeof(listener->factory.obj_name),
+ "tcplis:%d", listener->factory.addr_name.port);
+
+
+ /* Start listening to the address */
+ status = pj_sock_listen(sock, PJSIP_TCP_TRANSPORT_BACKLOG);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Create active socket */
+ pj_activesock_cfg_default(&asock_cfg);
+ if (cfg->async_cnt > MAX_ASYNC_CNT)
+ asock_cfg.async_cnt = MAX_ASYNC_CNT;
+ else
+ asock_cfg.async_cnt = cfg->async_cnt;
+
+ pj_bzero(&listener_cb, sizeof(listener_cb));
+ listener_cb.on_accept_complete = &on_accept_complete;
+ status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg,
+ pjsip_endpt_get_ioqueue(endpt),
+ &listener_cb, listener,
+ &listener->asock);
+
+ /* Register to transport manager */
+ listener->endpt = endpt;
+ listener->tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ listener->factory.create_transport = lis_create_transport;
+ listener->factory.destroy = lis_destroy;
+ listener->is_registered = PJ_TRUE;
+ status = pjsip_tpmgr_register_tpfactory(listener->tpmgr,
+ &listener->factory);
+ if (status != PJ_SUCCESS) {
+ listener->is_registered = PJ_FALSE;
+ goto on_error;
+ }
+
+ /* Start pending accept() operations */
+ status = pj_activesock_start_accept(listener->asock, pool);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ PJ_LOG(4,(listener->factory.obj_name,
+ "SIP TCP listener ready for incoming connections at %.*s:%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port));
+
+ /* Return the pointer to user */
+ if (p_factory) *p_factory = &listener->factory;
+
+ return PJ_SUCCESS;
+
+on_error:
+ if (listener->asock==NULL && sock!=PJ_INVALID_SOCKET)
+ pj_sock_close(sock);
+ lis_destroy(&listener->factory);
+ return status;
+}
+
+
+/*
+ * This is the public API to create, initialize, register, and start the
+ * TCP listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tcp_transport_start2(pjsip_endpoint *endpt,
+ const pj_sockaddr_in *local,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_tpfactory **p_factory)
+{
+ pjsip_tcp_transport_cfg cfg;
+
+ pjsip_tcp_transport_cfg_default(&cfg, pj_AF_INET());
+
+ if (local)
+ pj_sockaddr_cp(&cfg.bind_addr, local);
+ else
+ pj_sockaddr_init(cfg.af, &cfg.bind_addr, NULL, 0);
+
+ if (a_name)
+ pj_memcpy(&cfg.addr_name, a_name, sizeof(*a_name));
+
+ if (async_cnt)
+ cfg.async_cnt = async_cnt;
+
+ return pjsip_tcp_transport_start3(endpt, &cfg, p_factory);
+}
+
+
+/*
+ * This is the public API to create, initialize, register, and start the
+ * TCP listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tcp_transport_start( pjsip_endpoint *endpt,
+ const pj_sockaddr_in *local,
+ unsigned async_cnt,
+ pjsip_tpfactory **p_factory)
+{
+ return pjsip_tcp_transport_start2(endpt, local, NULL, async_cnt, p_factory);
+}
+
+
+/* This callback is called by transport manager to destroy listener */
+static pj_status_t lis_destroy(pjsip_tpfactory *factory)
+{
+ struct tcp_listener *listener = (struct tcp_listener *)factory;
+
+ if (listener->is_registered) {
+ pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory);
+ listener->is_registered = PJ_FALSE;
+ }
+
+ if (listener->asock) {
+ pj_activesock_close(listener->asock);
+ listener->asock = NULL;
+ }
+
+ if (listener->factory.lock) {
+ pj_lock_destroy(listener->factory.lock);
+ listener->factory.lock = NULL;
+ }
+
+ if (listener->factory.pool) {
+ pj_pool_t *pool = listener->factory.pool;
+
+ PJ_LOG(4,(listener->factory.obj_name, "SIP TCP listener destroyed"));
+
+ listener->factory.pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/***************************************************************************/
+/*
+ * TCP Transport
+ */
+
+/*
+ * Prototypes.
+ */
+/* Called by transport manager to send message */
+static pj_status_t tcp_send_msg(pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback);
+
+/* Called by transport manager to shutdown */
+static pj_status_t tcp_shutdown(pjsip_transport *transport);
+
+/* Called by transport manager to destroy transport */
+static pj_status_t tcp_destroy_transport(pjsip_transport *transport);
+
+/* Utility to destroy transport */
+static pj_status_t tcp_destroy(pjsip_transport *transport,
+ pj_status_t reason);
+
+/* Callback on incoming data */
+static pj_bool_t on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder);
+
+/* Callback when packet is sent */
+static pj_bool_t on_data_sent(pj_activesock_t *asock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent);
+
+/* Callback when connect completes */
+static pj_bool_t on_connect_complete(pj_activesock_t *asock,
+ pj_status_t status);
+
+/* TCP keep-alive timer callback */
+static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e);
+
+/*
+ * Common function to create TCP transport, called when pending accept() and
+ * pending connect() complete.
+ */
+static pj_status_t tcp_create( struct tcp_listener *listener,
+ pj_pool_t *pool,
+ pj_sock_t sock, pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ struct tcp_transport **p_tcp)
+{
+ struct tcp_transport *tcp;
+ pj_ioqueue_t *ioqueue;
+ pj_activesock_cfg asock_cfg;
+ pj_activesock_cb tcp_callback;
+ const pj_str_t ka_pkt = PJSIP_TCP_KEEP_ALIVE_DATA;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_EINVAL);
+
+
+ if (pool == NULL) {
+ pool = pjsip_endpt_create_pool(listener->endpt, "tcp",
+ POOL_TP_INIT, POOL_TP_INC);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+ }
+
+ /*
+ * Create and initialize basic transport structure.
+ */
+ tcp = PJ_POOL_ZALLOC_T(pool, struct tcp_transport);
+ tcp->is_server = is_server;
+ tcp->sock = sock;
+ /*tcp->listener = listener;*/
+ pj_list_init(&tcp->delayed_list);
+ tcp->base.pool = pool;
+
+ pj_ansi_snprintf(tcp->base.obj_name, PJ_MAX_OBJ_NAME,
+ (is_server ? "tcps%p" :"tcpc%p"), tcp);
+
+ status = pj_atomic_create(pool, 0, &tcp->base.ref_cnt);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ status = pj_lock_create_recursive_mutex(pool, "tcp", &tcp->base.lock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ tcp->base.key.type = PJSIP_TRANSPORT_TCP;
+ pj_memcpy(&tcp->base.key.rem_addr, remote, sizeof(pj_sockaddr_in));
+ tcp->base.type_name = "tcp";
+ tcp->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TCP);
+
+ tcp->base.info = (char*) pj_pool_alloc(pool, 64);
+ pj_ansi_snprintf(tcp->base.info, 64, "TCP to %s:%d",
+ pj_inet_ntoa(remote->sin_addr),
+ (int)pj_ntohs(remote->sin_port));
+
+ tcp->base.addr_len = sizeof(pj_sockaddr_in);
+ pj_memcpy(&tcp->base.local_addr, local, sizeof(pj_sockaddr_in));
+ sockaddr_to_host_port(pool, &tcp->base.local_name, local);
+ sockaddr_to_host_port(pool, &tcp->base.remote_name, remote);
+ tcp->base.dir = is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING;
+
+ tcp->base.endpt = listener->endpt;
+ tcp->base.tpmgr = listener->tpmgr;
+ tcp->base.send_msg = &tcp_send_msg;
+ tcp->base.do_shutdown = &tcp_shutdown;
+ tcp->base.destroy = &tcp_destroy_transport;
+
+
+ /* Create active socket */
+ pj_activesock_cfg_default(&asock_cfg);
+ asock_cfg.async_cnt = 1;
+
+ pj_bzero(&tcp_callback, sizeof(tcp_callback));
+ tcp_callback.on_data_read = &on_data_read;
+ tcp_callback.on_data_sent = &on_data_sent;
+ tcp_callback.on_connect_complete = &on_connect_complete;
+
+ ioqueue = pjsip_endpt_get_ioqueue(listener->endpt);
+ status = pj_activesock_create(pool, sock, pj_SOCK_STREAM(), &asock_cfg,
+ ioqueue, &tcp_callback, tcp, &tcp->asock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ /* Register transport to transport manager */
+ status = pjsip_transport_register(listener->tpmgr, &tcp->base);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ tcp->is_registered = PJ_TRUE;
+
+ /* Initialize keep-alive timer */
+ tcp->ka_timer.user_data = (void*)tcp;
+ tcp->ka_timer.cb = &tcp_keep_alive_timer;
+ pj_ioqueue_op_key_init(&tcp->ka_op_key.key, sizeof(pj_ioqueue_op_key_t));
+ pj_strdup(tcp->base.pool, &tcp->ka_pkt, &ka_pkt);
+
+ /* Done setting up basic transport. */
+ *p_tcp = tcp;
+
+ PJ_LOG(4,(tcp->base.obj_name, "TCP %s transport created",
+ (tcp->is_server ? "server" : "client")));
+
+ return PJ_SUCCESS;
+
+on_error:
+ tcp_destroy(&tcp->base, status);
+ return status;
+}
+
+
+/* Flush all delayed transmision once the socket is connected. */
+static void tcp_flush_pending_tx(struct tcp_transport *tcp)
+{
+ pj_lock_acquire(tcp->base.lock);
+ while (!pj_list_empty(&tcp->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pjsip_tx_data *tdata;
+ pj_ioqueue_op_key_t *op_key;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ pending_tx = tcp->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ tdata = pending_tx->tdata_op_key->tdata;
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ /* send! */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_activesock_send(tcp->asock, op_key, tdata->buf.start,
+ &size, 0);
+ if (status != PJ_EPENDING) {
+ on_data_sent(tcp->asock, op_key, size);
+ }
+
+ }
+ pj_lock_release(tcp->base.lock);
+}
+
+
+/* Called by transport manager to destroy transport */
+static pj_status_t tcp_destroy_transport(pjsip_transport *transport)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)transport;
+
+ /* Transport would have been unregistered by now since this callback
+ * is called by transport manager.
+ */
+ tcp->is_registered = PJ_FALSE;
+
+ return tcp_destroy(transport, tcp->close_reason);
+}
+
+
+/* Destroy TCP transport */
+static pj_status_t tcp_destroy(pjsip_transport *transport,
+ pj_status_t reason)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)transport;
+
+ if (tcp->close_reason == 0)
+ tcp->close_reason = reason;
+
+ if (tcp->is_registered) {
+ tcp->is_registered = PJ_FALSE;
+ pjsip_transport_destroy(transport);
+
+ /* pjsip_transport_destroy will recursively call this function
+ * again.
+ */
+ return PJ_SUCCESS;
+ }
+
+ /* Mark transport as closing */
+ tcp->is_closing = PJ_TRUE;
+
+ /* Stop keep-alive timer. */
+ if (tcp->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tcp->base.endpt, &tcp->ka_timer);
+ tcp->ka_timer.id = PJ_FALSE;
+ }
+
+ /* Cancel all delayed transmits */
+ while (!pj_list_empty(&tcp->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pj_ioqueue_op_key_t *op_key;
+
+ pending_tx = tcp->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ on_data_sent(tcp->asock, op_key, -reason);
+ }
+
+ if (tcp->rdata.tp_info.pool) {
+ pj_pool_release(tcp->rdata.tp_info.pool);
+ tcp->rdata.tp_info.pool = NULL;
+ }
+
+ if (tcp->asock) {
+ pj_activesock_close(tcp->asock);
+ tcp->asock = NULL;
+ tcp->sock = PJ_INVALID_SOCKET;
+ } else if (tcp->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(tcp->sock);
+ tcp->sock = PJ_INVALID_SOCKET;
+ }
+
+ if (tcp->base.lock) {
+ pj_lock_destroy(tcp->base.lock);
+ tcp->base.lock = NULL;
+ }
+
+ if (tcp->base.ref_cnt) {
+ pj_atomic_destroy(tcp->base.ref_cnt);
+ tcp->base.ref_cnt = NULL;
+ }
+
+ if (tcp->base.pool) {
+ pj_pool_t *pool;
+
+ if (reason != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(reason, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(tcp->base.obj_name,
+ "TCP transport destroyed with reason %d: %s",
+ reason, errmsg));
+
+ } else {
+
+ PJ_LOG(4,(tcp->base.obj_name,
+ "TCP transport destroyed normally"));
+
+ }
+
+ pool = tcp->base.pool;
+ tcp->base.pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This utility function creates receive data buffers and start
+ * asynchronous recv() operations from the socket. It is called after
+ * accept() or connect() operation complete.
+ */
+static pj_status_t tcp_start_read(struct tcp_transport *tcp)
+{
+ pj_pool_t *pool;
+ pj_ssize_t size;
+ pj_sockaddr_in *rem_addr;
+ void *readbuf[1];
+ pj_status_t status;
+
+ /* Init rdata */
+ pool = pjsip_endpt_create_pool(tcp->base.endpt,
+ "rtd%p",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC);
+ if (!pool) {
+ tcp_perror(tcp->base.obj_name, "Unable to create pool", PJ_ENOMEM);
+ return PJ_ENOMEM;
+ }
+
+ tcp->rdata.tp_info.pool = pool;
+
+ tcp->rdata.tp_info.transport = &tcp->base;
+ tcp->rdata.tp_info.tp_data = tcp;
+ tcp->rdata.tp_info.op_key.rdata = &tcp->rdata;
+ pj_ioqueue_op_key_init(&tcp->rdata.tp_info.op_key.op_key,
+ sizeof(pj_ioqueue_op_key_t));
+
+ tcp->rdata.pkt_info.src_addr = tcp->base.key.rem_addr;
+ tcp->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in);
+ rem_addr = (pj_sockaddr_in*) &tcp->base.key.rem_addr;
+ pj_ansi_strcpy(tcp->rdata.pkt_info.src_name,
+ pj_inet_ntoa(rem_addr->sin_addr));
+ tcp->rdata.pkt_info.src_port = pj_ntohs(rem_addr->sin_port);
+
+ size = sizeof(tcp->rdata.pkt_info.packet);
+ readbuf[0] = tcp->rdata.pkt_info.packet;
+ status = pj_activesock_start_read2(tcp->asock, tcp->base.pool, size,
+ readbuf, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ PJ_LOG(4, (tcp->base.obj_name,
+ "pj_activesock_start_read() error, status=%d",
+ status));
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* This callback is called by transport manager for the TCP factory
+ * to create outgoing transport to the specified destination.
+ */
+static pj_status_t lis_create_transport(pjsip_tpfactory *factory,
+ pjsip_tpmgr *mgr,
+ pjsip_endpoint *endpt,
+ const pj_sockaddr *rem_addr,
+ int addr_len,
+ pjsip_transport **p_transport)
+{
+ struct tcp_listener *listener;
+ struct tcp_transport *tcp;
+ pj_sock_t sock;
+ pj_sockaddr_in local_addr;
+ pj_status_t status;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr &&
+ addr_len && p_transport, PJ_EINVAL);
+
+ /* Check that address is a sockaddr_in */
+ PJ_ASSERT_RETURN(rem_addr->addr.sa_family == pj_AF_INET() &&
+ addr_len == sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+ listener = (struct tcp_listener*)factory;
+
+
+ /* Create socket */
+ status = pj_sock_socket(pj_AF_INET(), pj_SOCK_STREAM(), 0, &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Apply QoS, if specified */
+ status = pj_sock_apply_qos2(sock, listener->qos_type,
+ &listener->qos_params,
+ 2, listener->factory.obj_name,
+ "outgoing SIP TCP socket");
+
+ /* Bind to any port */
+ status = pj_sock_bind_in(sock, 0, 0);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ /* Get the local port */
+ addr_len = sizeof(pj_sockaddr_in);
+ status = pj_sock_getsockname(sock, &local_addr, &addr_len);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ /* Initially set the address from the listener's address */
+ local_addr.sin_addr.s_addr =
+ ((pj_sockaddr_in*)&listener->factory.local_addr)->sin_addr.s_addr;
+
+ /* Create the transport descriptor */
+ status = tcp_create(listener, NULL, sock, PJ_FALSE, &local_addr,
+ (pj_sockaddr_in*)rem_addr, &tcp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+
+ /* Start asynchronous connect() operation */
+ tcp->has_pending_connect = PJ_TRUE;
+ status = pj_activesock_start_connect(tcp->asock, tcp->base.pool, rem_addr,
+ sizeof(pj_sockaddr_in));
+ if (status == PJ_SUCCESS) {
+ on_connect_complete(tcp->asock, PJ_SUCCESS);
+ } else if (status != PJ_EPENDING) {
+ tcp_destroy(&tcp->base, status);
+ return status;
+ }
+
+ if (tcp->has_pending_connect) {
+ /* Update (again) local address, just in case local address currently
+ * set is different now that asynchronous connect() is started.
+ */
+ addr_len = sizeof(pj_sockaddr_in);
+ if (pj_sock_getsockname(sock, &local_addr, &addr_len)==PJ_SUCCESS) {
+ pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tcp->base.local_addr;
+
+ /* Some systems (like old Win32 perhaps) may not set local address
+ * properly before socket is fully connected.
+ */
+ if (tp_addr->sin_addr.s_addr != local_addr.sin_addr.s_addr &&
+ local_addr.sin_addr.s_addr != 0)
+ {
+ tp_addr->sin_addr.s_addr = local_addr.sin_addr.s_addr;
+ tp_addr->sin_port = local_addr.sin_port;
+ sockaddr_to_host_port(tcp->base.pool, &tcp->base.local_name,
+ &local_addr);
+ }
+ }
+
+ PJ_LOG(4,(tcp->base.obj_name,
+ "TCP transport %.*s:%d is connecting to %.*s:%d...",
+ (int)tcp->base.local_name.host.slen,
+ tcp->base.local_name.host.ptr,
+ tcp->base.local_name.port,
+ (int)tcp->base.remote_name.host.slen,
+ tcp->base.remote_name.host.ptr,
+ tcp->base.remote_name.port));
+ }
+
+ /* Done */
+ *p_transport = &tcp->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This callback is called by active socket when pending accept() operation
+ * has completed.
+ */
+static pj_bool_t on_accept_complete(pj_activesock_t *asock,
+ pj_sock_t sock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len)
+{
+ struct tcp_listener *listener;
+ struct tcp_transport *tcp;
+ char addr[PJ_INET6_ADDRSTRLEN+10];
+ pjsip_tp_state_callback state_cb;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(src_addr_len);
+
+ listener = (struct tcp_listener*) pj_activesock_get_user_data(asock);
+
+ PJ_ASSERT_RETURN(sock != PJ_INVALID_SOCKET, PJ_TRUE);
+
+ PJ_LOG(4,(listener->factory.obj_name,
+ "TCP listener %.*s:%d: got incoming TCP connection "
+ "from %s, sock=%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port,
+ pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
+ sock));
+
+ /* Apply QoS, if specified */
+ status = pj_sock_apply_qos2(sock, listener->qos_type,
+ &listener->qos_params,
+ 2, listener->factory.obj_name,
+ "incoming SIP TCP socket");
+
+ /*
+ * Incoming connection!
+ * Create TCP transport for the new socket.
+ */
+ status = tcp_create( listener, NULL, sock, PJ_TRUE,
+ (const pj_sockaddr_in*)&listener->factory.local_addr,
+ (const pj_sockaddr_in*)src_addr, &tcp);
+ if (status == PJ_SUCCESS) {
+ status = tcp_start_read(tcp);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(tcp->base.obj_name, "New transport cancelled"));
+ tcp_destroy(&tcp->base, status);
+ } else {
+ /* Start keep-alive timer */
+ if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = {PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0};
+ pjsip_endpt_schedule_timer(listener->endpt,
+ &tcp->ka_timer,
+ &delay);
+ tcp->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tcp->last_activity);
+ }
+
+ /* Notify application of transport state accepted */
+ state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+
+ pj_bzero(&state_info, sizeof(state_info));
+ (*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info);
+ }
+ }
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when packet is sent.
+ */
+static pj_bool_t on_data_sent(pj_activesock_t *asock,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_sent)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)
+ pj_activesock_get_user_data(asock);
+ pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key;
+
+ /* Note that op_key may be the op_key from keep-alive, thus
+ * it will not have tdata etc.
+ */
+
+ tdata_op_key->tdata = NULL;
+
+ if (tdata_op_key->callback) {
+ /*
+ * Notify sip_transport.c that packet has been sent.
+ */
+ if (bytes_sent == 0)
+ bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tdata_op_key->callback(&tcp->base, tdata_op_key->token, bytes_sent);
+
+ /* Mark last activity time */
+ pj_gettimeofday(&tcp->last_activity);
+
+ }
+
+ /* Check for error/closure */
+ if (bytes_sent <= 0) {
+ pj_status_t status;
+
+ PJ_LOG(5,(tcp->base.obj_name, "TCP send() error, sent=%d",
+ bytes_sent));
+
+ status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) :
+ -bytes_sent;
+
+ tcp_init_shutdown(tcp, status);
+
+ return PJ_FALSE;
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * This callback is called by transport manager to send SIP message
+ */
+static pj_status_t tcp_send_msg(pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)transport;
+ pj_ssize_t size;
+ pj_bool_t delayed = PJ_FALSE;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL);
+
+ /* Check that there's no pending operation associated with the tdata */
+ PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX);
+
+ /* Check the address is supported */
+ PJ_ASSERT_RETURN(rem_addr && addr_len==sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+
+ /* Init op key. */
+ tdata->op_key.tdata = tdata;
+ tdata->op_key.token = token;
+ tdata->op_key.callback = callback;
+
+ /* If asynchronous connect() has not completed yet, just put the
+ * transmit data in the pending transmission list since we can not
+ * use the socket yet.
+ */
+ if (tcp->has_pending_connect) {
+
+ /*
+ * Looks like connect() is still in progress. Check again (this time
+ * with holding the lock) to be sure.
+ */
+ pj_lock_acquire(tcp->base.lock);
+
+ if (tcp->has_pending_connect) {
+ struct delayed_tdata *delayed_tdata;
+
+ /*
+ * connect() is still in progress. Put the transmit data to
+ * the delayed list.
+ */
+ delayed_tdata = PJ_POOL_ALLOC_T(tdata->pool,
+ struct delayed_tdata);
+ delayed_tdata->tdata_op_key = &tdata->op_key;
+
+ pj_list_push_back(&tcp->delayed_list, delayed_tdata);
+ status = PJ_EPENDING;
+
+ /* Prevent pj_ioqueue_send() to be called below */
+ delayed = PJ_TRUE;
+ }
+
+ pj_lock_release(tcp->base.lock);
+ }
+
+ if (!delayed) {
+ /*
+ * Transport is ready to go. Send the packet to ioqueue to be
+ * sent asynchronously.
+ */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_activesock_send(tcp->asock,
+ (pj_ioqueue_op_key_t*)&tdata->op_key,
+ tdata->buf.start, &size, 0);
+
+ if (status != PJ_EPENDING) {
+ /* Not pending (could be immediate success or error) */
+ tdata->op_key.tdata = NULL;
+
+ /* Shutdown transport on closure/errors */
+ if (size <= 0) {
+
+ PJ_LOG(5,(tcp->base.obj_name, "TCP send() error, sent=%d",
+ size));
+
+ if (status == PJ_SUCCESS)
+ status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tcp_init_shutdown(tcp, status);
+ }
+ }
+ }
+
+ return status;
+}
+
+
+/*
+ * This callback is called by transport manager to shutdown transport.
+ */
+static pj_status_t tcp_shutdown(pjsip_transport *transport)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*)transport;
+
+ /* Stop keep-alive timer. */
+ if (tcp->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tcp->base.endpt, &tcp->ka_timer);
+ tcp->ka_timer.id = PJ_FALSE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback from ioqueue that an incoming data is received from the socket.
+ */
+static pj_bool_t on_data_read(pj_activesock_t *asock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ enum { MAX_IMMEDIATE_PACKET = 10 };
+ struct tcp_transport *tcp;
+ pjsip_rx_data *rdata;
+
+ PJ_UNUSED_ARG(data);
+
+ tcp = (struct tcp_transport*) pj_activesock_get_user_data(asock);
+ rdata = &tcp->rdata;
+
+ /* Don't do anything if transport is closing. */
+ if (tcp->is_closing) {
+ tcp->is_closing++;
+ return PJ_FALSE;
+ }
+
+ /* Houston, we have packet! Report the packet to transport manager
+ * to be parsed.
+ */
+ if (status == PJ_SUCCESS) {
+ pj_size_t size_eaten;
+
+ /* Mark this as an activity */
+ pj_gettimeofday(&tcp->last_activity);
+
+ pj_assert((void*)rdata->pkt_info.packet == data);
+
+ /* Init pkt_info part. */
+ rdata->pkt_info.len = size;
+ rdata->pkt_info.zero = 0;
+ pj_gettimeofday(&rdata->pkt_info.timestamp);
+
+ /* Report to transport manager.
+ * The transport manager will tell us how many bytes of the packet
+ * have been processed (as valid SIP message).
+ */
+ size_eaten =
+ pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr,
+ rdata);
+
+ pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len);
+
+ /* Move unprocessed data to the front of the buffer */
+ *remainder = size - size_eaten;
+ if (*remainder > 0 && *remainder != size) {
+ pj_memmove(rdata->pkt_info.packet,
+ rdata->pkt_info.packet + size_eaten,
+ *remainder);
+ }
+
+ } else {
+
+ /* Transport is closed */
+ PJ_LOG(4,(tcp->base.obj_name, "TCP connection closed"));
+
+ tcp_init_shutdown(tcp, status);
+
+ return PJ_FALSE;
+
+ }
+
+ /* Reset pool. */
+ pj_pool_reset(rdata->tp_info.pool);
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when asynchronous connect() operation completes.
+ */
+static pj_bool_t on_connect_complete(pj_activesock_t *asock,
+ pj_status_t status)
+{
+ struct tcp_transport *tcp;
+ pj_sockaddr_in addr;
+ int addrlen;
+ pjsip_tp_state_callback state_cb;
+
+ tcp = (struct tcp_transport*) pj_activesock_get_user_data(asock);
+
+ /* Mark that pending connect() operation has completed. */
+ tcp->has_pending_connect = PJ_FALSE;
+
+ /* Check connect() status */
+ if (status != PJ_SUCCESS) {
+
+ tcp_perror(tcp->base.obj_name, "TCP connect() error", status);
+
+ /* Cancel all delayed transmits */
+ while (!pj_list_empty(&tcp->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pj_ioqueue_op_key_t *op_key;
+
+ pending_tx = tcp->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ on_data_sent(tcp->asock, op_key, -status);
+ }
+
+ tcp_init_shutdown(tcp, status);
+ return PJ_FALSE;
+ }
+
+ PJ_LOG(4,(tcp->base.obj_name,
+ "TCP transport %.*s:%d is connected to %.*s:%d",
+ (int)tcp->base.local_name.host.slen,
+ tcp->base.local_name.host.ptr,
+ tcp->base.local_name.port,
+ (int)tcp->base.remote_name.host.slen,
+ tcp->base.remote_name.host.ptr,
+ tcp->base.remote_name.port));
+
+
+ /* Update (again) local address, just in case local address currently
+ * set is different now that the socket is connected (could happen
+ * on some systems, like old Win32 probably?).
+ */
+ addrlen = sizeof(pj_sockaddr_in);
+ if (pj_sock_getsockname(tcp->sock, &addr, &addrlen)==PJ_SUCCESS) {
+ pj_sockaddr_in *tp_addr = (pj_sockaddr_in*)&tcp->base.local_addr;
+
+ if (pj_sockaddr_has_addr(&addr) &&
+ tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr)
+ {
+ tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr;
+ tp_addr->sin_port = addr.sin_port;
+ sockaddr_to_host_port(tcp->base.pool, &tcp->base.local_name,
+ tp_addr);
+ }
+ }
+
+ /* Start pending read */
+ status = tcp_start_read(tcp);
+ if (status != PJ_SUCCESS) {
+ tcp_init_shutdown(tcp, status);
+ return PJ_FALSE;
+ }
+
+ /* Notify application of transport state connected */
+ state_cb = pjsip_tpmgr_get_state_cb(tcp->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+
+ pj_bzero(&state_info, sizeof(state_info));
+ (*state_cb)(&tcp->base, PJSIP_TP_STATE_CONNECTED, &state_info);
+ }
+
+ /* Flush all pending send operations */
+ tcp_flush_pending_tx(tcp);
+
+ /* Start keep-alive timer */
+ if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = { PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0 };
+ pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer,
+ &delay);
+ tcp->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tcp->last_activity);
+ }
+
+ return PJ_TRUE;
+}
+
+/* Transport keep-alive timer callback */
+static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e)
+{
+ struct tcp_transport *tcp = (struct tcp_transport*) e->user_data;
+ pj_time_val delay;
+ pj_time_val now;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(th);
+
+ tcp->ka_timer.id = PJ_TRUE;
+
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, tcp->last_activity);
+
+ if (now.sec > 0 && now.sec < PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
+ /* There has been activity, so don't send keep-alive */
+ delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL - now.sec;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer,
+ &delay);
+ tcp->ka_timer.id = PJ_TRUE;
+ return;
+ }
+
+ PJ_LOG(5,(tcp->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d",
+ (int)tcp->ka_pkt.slen, (int)tcp->base.remote_name.host.slen,
+ tcp->base.remote_name.host.ptr,
+ tcp->base.remote_name.port));
+
+ /* Send the data */
+ size = tcp->ka_pkt.slen;
+ status = pj_activesock_send(tcp->asock, &tcp->ka_op_key.key,
+ tcp->ka_pkt.ptr, &size, 0);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ tcp_perror(tcp->base.obj_name,
+ "Error sending keep-alive packet", status);
+ tcp_init_shutdown(tcp, status);
+ return;
+ }
+
+ /* Register next keep-alive */
+ delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tcp->base.endpt, &tcp->ka_timer,
+ &delay);
+ tcp->ka_timer.id = PJ_TRUE;
+}
+
+
+#endif /* PJ_HAS_TCP */
+
diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c
new file mode 100644
index 0000000..878b6db
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_tls.c
@@ -0,0 +1,1610 @@
+/* $Id: sip_transport_tls.c 4146 2012-05-30 06:35:59Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <pjsip/sip_transport_tls.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/compat/socket.h>
+#include <pj/addr_resolv.h>
+#include <pj/ssl_sock.h>
+#include <pj/assert.h>
+#include <pj/hash.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0
+
+#define THIS_FILE "sip_transport_tls.c"
+
+#define MAX_ASYNC_CNT 16
+#define POOL_LIS_INIT 512
+#define POOL_LIS_INC 512
+#define POOL_TP_INIT 512
+#define POOL_TP_INC 512
+
+struct tls_listener;
+struct tls_transport;
+
+/*
+ * Definition of TLS/SSL transport listener, and it's descendant of
+ * pjsip_tpfactory.
+ */
+struct tls_listener
+{
+ pjsip_tpfactory factory;
+ pj_bool_t is_registered;
+ pjsip_endpoint *endpt;
+ pjsip_tpmgr *tpmgr;
+ pj_ssl_sock_t *ssock;
+ pj_ssl_cert_t *cert;
+ pjsip_tls_setting tls_setting;
+};
+
+
+/*
+ * This structure is used to keep delayed transmit operation in a list.
+ * A delayed transmission occurs when application sends tx_data when
+ * the TLS connect/establishment is still in progress. These delayed
+ * transmission will be "flushed" once the socket is connected (either
+ * successfully or with errors).
+ */
+struct delayed_tdata
+{
+ PJ_DECL_LIST_MEMBER(struct delayed_tdata);
+ pjsip_tx_data_op_key *tdata_op_key;
+};
+
+
+/*
+ * TLS/SSL transport, and it's descendant of pjsip_transport.
+ */
+struct tls_transport
+{
+ pjsip_transport base;
+ pj_bool_t is_server;
+ pj_str_t remote_name;
+
+ pj_bool_t is_registered;
+ pj_bool_t is_closing;
+ pj_status_t close_reason;
+ pj_ssl_sock_t *ssock;
+ pj_bool_t has_pending_connect;
+ pj_bool_t verify_server;
+
+ /* Keep-alive timer. */
+ pj_timer_entry ka_timer;
+ pj_time_val last_activity;
+ pjsip_tx_data_op_key ka_op_key;
+ pj_str_t ka_pkt;
+
+ /* TLS transport can only have one rdata!
+ * Otherwise chunks of incoming PDU may be received on different
+ * buffer.
+ */
+ pjsip_rx_data rdata;
+
+ /* Pending transmission list. */
+ struct delayed_tdata delayed_list;
+};
+
+
+/****************************************************************************
+ * PROTOTYPES
+ */
+
+/* This callback is called when pending accept() operation completes. */
+static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock,
+ pj_ssl_sock_t *new_ssock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len);
+
+/* Callback on incoming data */
+static pj_bool_t on_data_read(pj_ssl_sock_t *ssock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder);
+
+/* Callback when packet is sent */
+static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock,
+ pj_ioqueue_op_key_t *send_key,
+ pj_ssize_t sent);
+
+/* This callback is called by transport manager to destroy listener */
+static pj_status_t lis_destroy(pjsip_tpfactory *factory);
+
+/* This callback is called by transport manager to create transport */
+static pj_status_t lis_create_transport(pjsip_tpfactory *factory,
+ pjsip_tpmgr *mgr,
+ pjsip_endpoint *endpt,
+ const pj_sockaddr *rem_addr,
+ int addr_len,
+ pjsip_tx_data *tdata,
+ pjsip_transport **transport);
+
+/* Common function to create and initialize transport */
+static pj_status_t tls_create(struct tls_listener *listener,
+ pj_pool_t *pool,
+ pj_ssl_sock_t *ssock,
+ pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ const pj_str_t *remote_name,
+ struct tls_transport **p_tls);
+
+
+static void tls_perror(const char *sender, const char *title,
+ pj_status_t status)
+{
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status));
+}
+
+
+static void sockaddr_to_host_port( pj_pool_t *pool,
+ pjsip_host_port *host_port,
+ const pj_sockaddr_in *addr )
+{
+ host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4);
+ pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 2);
+ host_port->host.slen = pj_ansi_strlen(host_port->host.ptr);
+ host_port->port = pj_sockaddr_get_port(addr);
+}
+
+
+static void tls_init_shutdown(struct tls_transport *tls, pj_status_t status)
+{
+ pjsip_tp_state_callback state_cb;
+
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = status;
+
+ if (tls->base.is_shutdown)
+ return;
+
+ /* Prevent immediate transport destroy by application, as transport
+ * state notification callback may be stacked and transport instance
+ * must remain valid at any point in the callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* Notify application of transport disconnected state */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pj_ssl_sock_info ssl_info;
+
+ /* Init transport state info */
+ pj_bzero(&state_info, sizeof(state_info));
+ state_info.status = tls->close_reason;
+
+ if (tls->ssock &&
+ pj_ssl_sock_get_info(tls->ssock, &ssl_info) == PJ_SUCCESS)
+ {
+ pj_bzero(&tls_info, sizeof(tls_info));
+ tls_info.ssl_sock_info = &ssl_info;
+ state_info.ext_info = &tls_info;
+ }
+
+ (*state_cb)(&tls->base, PJSIP_TP_STATE_DISCONNECTED, &state_info);
+ }
+
+ /* We can not destroy the transport since high level objects may
+ * still keep reference to this transport. So we can only
+ * instruct transport manager to gracefully start the shutdown
+ * procedure for this transport.
+ */
+ pjsip_transport_shutdown(&tls->base);
+
+ /* Now, it is ok to destroy the transport. */
+ pjsip_transport_dec_ref(&tls->base);
+}
+
+
+/****************************************************************************
+ * The TLS listener/transport factory.
+ */
+
+/*
+ * This is the public API to create, initialize, register, and start the
+ * TLS listener.
+ */
+PJ_DEF(pj_status_t) pjsip_tls_transport_start (pjsip_endpoint *endpt,
+ const pjsip_tls_setting *opt,
+ const pj_sockaddr_in *local,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_tpfactory **p_factory)
+{
+ pj_pool_t *pool;
+ struct tls_listener *listener;
+ pj_ssl_sock_param ssock_param;
+ pj_sockaddr_in *listener_addr;
+ pj_bool_t has_listener;
+ pj_status_t status;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL);
+
+ /* Verify that address given in a_name (if any) is valid */
+ if (a_name && a_name->host.slen) {
+ pj_sockaddr_in tmp;
+
+ status = pj_sockaddr_in_init(&tmp, &a_name->host,
+ (pj_uint16_t)a_name->port);
+ if (status != PJ_SUCCESS || tmp.sin_addr.s_addr == PJ_INADDR_ANY ||
+ tmp.sin_addr.s_addr == PJ_INADDR_NONE)
+ {
+ /* Invalid address */
+ return PJ_EINVAL;
+ }
+ }
+
+ pool = pjsip_endpt_create_pool(endpt, "tlslis", POOL_LIS_INIT,
+ POOL_LIS_INC);
+ PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+ listener = PJ_POOL_ZALLOC_T(pool, struct tls_listener);
+ listener->factory.pool = pool;
+ listener->factory.type = PJSIP_TRANSPORT_TLS;
+ listener->factory.type_name = "tls";
+ listener->factory.flag =
+ pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS);
+
+ pj_ansi_strcpy(listener->factory.obj_name, "tlslis");
+
+ if (opt)
+ pjsip_tls_setting_copy(pool, &listener->tls_setting, opt);
+ else
+ pjsip_tls_setting_default(&listener->tls_setting);
+
+ status = pj_lock_create_recursive_mutex(pool, "tlslis",
+ &listener->factory.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ if (async_cnt > MAX_ASYNC_CNT)
+ async_cnt = MAX_ASYNC_CNT;
+
+ /* Build SSL socket param */
+ pj_ssl_sock_param_default(&ssock_param);
+ ssock_param.cb.on_accept_complete = &on_accept_complete;
+ ssock_param.cb.on_data_read = &on_data_read;
+ ssock_param.cb.on_data_sent = &on_data_sent;
+ ssock_param.async_cnt = async_cnt;
+ ssock_param.ioqueue = pjsip_endpt_get_ioqueue(endpt);
+ ssock_param.require_client_cert = listener->tls_setting.require_client_cert;
+ ssock_param.timeout = listener->tls_setting.timeout;
+ ssock_param.user_data = listener;
+ ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket
+ * due to verification error */
+ if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN;
+ if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN;
+ ssock_param.ciphers_num = listener->tls_setting.ciphers_num;
+ ssock_param.ciphers = listener->tls_setting.ciphers;
+ ssock_param.qos_type = listener->tls_setting.qos_type;
+ ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error;
+ pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params,
+ sizeof(ssock_param.qos_params));
+
+ has_listener = PJ_FALSE;
+
+ switch(listener->tls_setting.method) {
+ case PJSIP_TLSV1_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1;
+ break;
+ case PJSIP_SSLV2_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2;
+ break;
+ case PJSIP_SSLV3_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3;
+ break;
+ case PJSIP_SSLV23_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23;
+ break;
+ default:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT;
+ break;
+ }
+
+ /* Create SSL socket */
+ status = pj_ssl_sock_create(pool, &ssock_param, &listener->ssock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ listener_addr = (pj_sockaddr_in*)&listener->factory.local_addr;
+ if (local) {
+ pj_sockaddr_cp((pj_sockaddr_t*)listener_addr,
+ (const pj_sockaddr_t*)local);
+ } else {
+ pj_sockaddr_in_init(listener_addr, NULL, 0);
+ }
+
+ /* Check if certificate/CA list for SSL socket is set */
+ if (listener->tls_setting.cert_file.slen ||
+ listener->tls_setting.ca_list_file.slen)
+ {
+ status = pj_ssl_cert_load_from_files(pool,
+ &listener->tls_setting.ca_list_file,
+ &listener->tls_setting.cert_file,
+ &listener->tls_setting.privkey_file,
+ &listener->tls_setting.password,
+ &listener->cert);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pj_ssl_sock_set_certificate(listener->ssock, pool,
+ listener->cert);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ /* Start accepting incoming connections. Note that some TLS/SSL backends
+ * may not support for SSL socket server.
+ */
+ has_listener = PJ_FALSE;
+
+ status = pj_ssl_sock_start_accept(listener->ssock, pool,
+ (pj_sockaddr_t*)listener_addr,
+ pj_sockaddr_get_len((pj_sockaddr_t*)listener_addr));
+ if (status == PJ_SUCCESS || status == PJ_EPENDING) {
+ pj_ssl_sock_info info;
+ has_listener = PJ_TRUE;
+
+ /* Retrieve the bound address */
+ status = pj_ssl_sock_get_info(listener->ssock, &info);
+ if (status == PJ_SUCCESS)
+ pj_sockaddr_cp(listener_addr, (pj_sockaddr_t*)&info.local_addr);
+ } else if (status != PJ_ENOTSUP) {
+ goto on_error;
+ }
+
+ /* If published host/IP is specified, then use that address as the
+ * listener advertised address.
+ */
+ if (a_name && a_name->host.slen) {
+ /* Copy the address */
+ listener->factory.addr_name = *a_name;
+ pj_strdup(listener->factory.pool, &listener->factory.addr_name.host,
+ &a_name->host);
+ listener->factory.addr_name.port = a_name->port;
+
+ } else {
+ /* No published address is given, use the bound address */
+
+ /* If the address returns 0.0.0.0, use the default
+ * interface address as the transport's address.
+ */
+ if (listener_addr->sin_addr.s_addr == 0) {
+ pj_sockaddr hostip;
+
+ status = pj_gethostip(pj_AF_INET(), &hostip);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ listener_addr->sin_addr.s_addr = hostip.ipv4.sin_addr.s_addr;
+ }
+
+ /* Save the address name */
+ sockaddr_to_host_port(listener->factory.pool,
+ &listener->factory.addr_name, listener_addr);
+ }
+
+ /* If port is zero, get the bound port */
+ if (listener->factory.addr_name.port == 0) {
+ listener->factory.addr_name.port = pj_ntohs(listener_addr->sin_port);
+ }
+
+ pj_ansi_snprintf(listener->factory.obj_name,
+ sizeof(listener->factory.obj_name),
+ "tlslis:%d", listener->factory.addr_name.port);
+
+ /* Register to transport manager */
+ listener->endpt = endpt;
+ listener->tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ listener->factory.create_transport2 = lis_create_transport;
+ listener->factory.destroy = lis_destroy;
+ listener->is_registered = PJ_TRUE;
+ status = pjsip_tpmgr_register_tpfactory(listener->tpmgr,
+ &listener->factory);
+ if (status != PJ_SUCCESS) {
+ listener->is_registered = PJ_FALSE;
+ goto on_error;
+ }
+
+ if (has_listener) {
+ PJ_LOG(4,(listener->factory.obj_name,
+ "SIP TLS listener is ready for incoming connections "
+ "at %.*s:%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port));
+ } else {
+ PJ_LOG(4,(listener->factory.obj_name, "SIP TLS is ready "
+ "(client only)"));
+ }
+
+ /* Return the pointer to user */
+ if (p_factory) *p_factory = &listener->factory;
+
+ return PJ_SUCCESS;
+
+on_error:
+ lis_destroy(&listener->factory);
+ return status;
+}
+
+
+/* This callback is called by transport manager to destroy listener */
+static pj_status_t lis_destroy(pjsip_tpfactory *factory)
+{
+ struct tls_listener *listener = (struct tls_listener *)factory;
+
+ if (listener->is_registered) {
+ pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory);
+ listener->is_registered = PJ_FALSE;
+ }
+
+ if (listener->ssock) {
+ pj_ssl_sock_close(listener->ssock);
+ listener->ssock = NULL;
+ }
+
+ if (listener->factory.lock) {
+ pj_lock_destroy(listener->factory.lock);
+ listener->factory.lock = NULL;
+ }
+
+ if (listener->factory.pool) {
+ pj_pool_t *pool = listener->factory.pool;
+
+ PJ_LOG(4,(listener->factory.obj_name, "SIP TLS listener destroyed"));
+
+ listener->factory.pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/***************************************************************************/
+/*
+ * TLS Transport
+ */
+
+/*
+ * Prototypes.
+ */
+/* Called by transport manager to send message */
+static pj_status_t tls_send_msg(pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback);
+
+/* Called by transport manager to shutdown */
+static pj_status_t tls_shutdown(pjsip_transport *transport);
+
+/* Called by transport manager to destroy transport */
+static pj_status_t tls_destroy_transport(pjsip_transport *transport);
+
+/* Utility to destroy transport */
+static pj_status_t tls_destroy(pjsip_transport *transport,
+ pj_status_t reason);
+
+/* Callback when connect completes */
+static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock,
+ pj_status_t status);
+
+/* TLS keep-alive timer callback */
+static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e);
+
+/*
+ * Common function to create TLS transport, called when pending accept() and
+ * pending connect() complete.
+ */
+static pj_status_t tls_create( struct tls_listener *listener,
+ pj_pool_t *pool,
+ pj_ssl_sock_t *ssock,
+ pj_bool_t is_server,
+ const pj_sockaddr_in *local,
+ const pj_sockaddr_in *remote,
+ const pj_str_t *remote_name,
+ struct tls_transport **p_tls)
+{
+ struct tls_transport *tls;
+ const pj_str_t ka_pkt = PJSIP_TLS_KEEP_ALIVE_DATA;
+ pj_status_t status;
+
+
+ PJ_ASSERT_RETURN(listener && ssock && local && remote && p_tls, PJ_EINVAL);
+
+
+ if (pool == NULL) {
+ pool = pjsip_endpt_create_pool(listener->endpt, "tls",
+ POOL_TP_INIT, POOL_TP_INC);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+ }
+
+ /*
+ * Create and initialize basic transport structure.
+ */
+ tls = PJ_POOL_ZALLOC_T(pool, struct tls_transport);
+ tls->is_server = is_server;
+ tls->verify_server = listener->tls_setting.verify_server;
+ pj_list_init(&tls->delayed_list);
+ tls->base.pool = pool;
+
+ pj_ansi_snprintf(tls->base.obj_name, PJ_MAX_OBJ_NAME,
+ (is_server ? "tlss%p" :"tlsc%p"), tls);
+
+ status = pj_atomic_create(pool, 0, &tls->base.ref_cnt);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ status = pj_lock_create_recursive_mutex(pool, "tls", &tls->base.lock);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ if (remote_name)
+ pj_strdup(pool, &tls->remote_name, remote_name);
+
+ tls->base.key.type = PJSIP_TRANSPORT_TLS;
+ pj_memcpy(&tls->base.key.rem_addr, remote, sizeof(pj_sockaddr_in));
+ tls->base.type_name = "tls";
+ tls->base.flag = pjsip_transport_get_flag_from_type(PJSIP_TRANSPORT_TLS);
+
+ tls->base.info = (char*) pj_pool_alloc(pool, 64);
+ pj_ansi_snprintf(tls->base.info, 64, "TLS to %s:%d",
+ pj_inet_ntoa(remote->sin_addr),
+ (int)pj_ntohs(remote->sin_port));
+
+ tls->base.addr_len = sizeof(pj_sockaddr_in);
+ tls->base.dir = is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING;
+
+ /* Set initial local address */
+ if (!pj_sockaddr_has_addr(local)) {
+ pj_sockaddr_cp(&tls->base.local_addr,
+ &listener->factory.local_addr);
+ } else {
+ pj_sockaddr_cp(&tls->base.local_addr, local);
+ }
+
+ sockaddr_to_host_port(pool, &tls->base.local_name,
+ (pj_sockaddr_in*)&tls->base.local_addr);
+ if (tls->remote_name.slen) {
+ tls->base.remote_name.host = tls->remote_name;
+ tls->base.remote_name.port = pj_sockaddr_in_get_port(remote);
+ } else {
+ sockaddr_to_host_port(pool, &tls->base.remote_name, remote);
+ }
+
+ tls->base.endpt = listener->endpt;
+ tls->base.tpmgr = listener->tpmgr;
+ tls->base.send_msg = &tls_send_msg;
+ tls->base.do_shutdown = &tls_shutdown;
+ tls->base.destroy = &tls_destroy_transport;
+
+ tls->ssock = ssock;
+
+ /* Register transport to transport manager */
+ status = pjsip_transport_register(listener->tpmgr, &tls->base);
+ if (status != PJ_SUCCESS) {
+ goto on_error;
+ }
+
+ tls->is_registered = PJ_TRUE;
+
+ /* Initialize keep-alive timer */
+ tls->ka_timer.user_data = (void*)tls;
+ tls->ka_timer.cb = &tls_keep_alive_timer;
+ pj_ioqueue_op_key_init(&tls->ka_op_key.key, sizeof(pj_ioqueue_op_key_t));
+ pj_strdup(tls->base.pool, &tls->ka_pkt, &ka_pkt);
+
+ /* Done setting up basic transport. */
+ *p_tls = tls;
+
+ PJ_LOG(4,(tls->base.obj_name, "TLS %s transport created",
+ (tls->is_server ? "server" : "client")));
+
+ return PJ_SUCCESS;
+
+on_error:
+ tls_destroy(&tls->base, status);
+ return status;
+}
+
+
+/* Flush all delayed transmision once the socket is connected. */
+static void tls_flush_pending_tx(struct tls_transport *tls)
+{
+ pj_lock_acquire(tls->base.lock);
+ while (!pj_list_empty(&tls->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pjsip_tx_data *tdata;
+ pj_ioqueue_op_key_t *op_key;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ pending_tx = tls->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ tdata = pending_tx->tdata_op_key->tdata;
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ /* send! */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_ssl_sock_send(tls->ssock, op_key, tdata->buf.start,
+ &size, 0);
+
+ if (status != PJ_EPENDING) {
+ on_data_sent(tls->ssock, op_key, size);
+ }
+ }
+ pj_lock_release(tls->base.lock);
+}
+
+
+/* Called by transport manager to destroy transport */
+static pj_status_t tls_destroy_transport(pjsip_transport *transport)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+
+ /* Transport would have been unregistered by now since this callback
+ * is called by transport manager.
+ */
+ tls->is_registered = PJ_FALSE;
+
+ return tls_destroy(transport, tls->close_reason);
+}
+
+
+/* Destroy TLS transport */
+static pj_status_t tls_destroy(pjsip_transport *transport,
+ pj_status_t reason)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+
+ if (tls->close_reason == 0)
+ tls->close_reason = reason;
+
+ if (tls->is_registered) {
+ tls->is_registered = PJ_FALSE;
+ pjsip_transport_destroy(transport);
+
+ /* pjsip_transport_destroy will recursively call this function
+ * again.
+ */
+ return PJ_SUCCESS;
+ }
+
+ /* Mark transport as closing */
+ tls->is_closing = PJ_TRUE;
+
+ /* Stop keep-alive timer. */
+ if (tls->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer);
+ tls->ka_timer.id = PJ_FALSE;
+ }
+
+ /* Cancel all delayed transmits */
+ while (!pj_list_empty(&tls->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pj_ioqueue_op_key_t *op_key;
+
+ pending_tx = tls->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ on_data_sent(tls->ssock, op_key, -reason);
+ }
+
+ if (tls->rdata.tp_info.pool) {
+ pj_pool_release(tls->rdata.tp_info.pool);
+ tls->rdata.tp_info.pool = NULL;
+ }
+
+ if (tls->ssock) {
+ pj_ssl_sock_close(tls->ssock);
+ tls->ssock = NULL;
+ }
+ if (tls->base.lock) {
+ pj_lock_destroy(tls->base.lock);
+ tls->base.lock = NULL;
+ }
+
+ if (tls->base.ref_cnt) {
+ pj_atomic_destroy(tls->base.ref_cnt);
+ tls->base.ref_cnt = NULL;
+ }
+
+ if (tls->base.pool) {
+ pj_pool_t *pool;
+
+ if (reason != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+
+ pj_strerror(reason, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS transport destroyed with reason %d: %s",
+ reason, errmsg));
+
+ } else {
+
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS transport destroyed normally"));
+
+ }
+
+ pool = tls->base.pool;
+ tls->base.pool = NULL;
+ pj_pool_release(pool);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This utility function creates receive data buffers and start
+ * asynchronous recv() operations from the socket. It is called after
+ * accept() or connect() operation complete.
+ */
+static pj_status_t tls_start_read(struct tls_transport *tls)
+{
+ pj_pool_t *pool;
+ pj_ssize_t size;
+ pj_sockaddr_in *rem_addr;
+ void *readbuf[1];
+ pj_status_t status;
+
+ /* Init rdata */
+ pool = pjsip_endpt_create_pool(tls->base.endpt,
+ "rtd%p",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC);
+ if (!pool) {
+ tls_perror(tls->base.obj_name, "Unable to create pool", PJ_ENOMEM);
+ return PJ_ENOMEM;
+ }
+
+ tls->rdata.tp_info.pool = pool;
+
+ tls->rdata.tp_info.transport = &tls->base;
+ tls->rdata.tp_info.tp_data = tls;
+ tls->rdata.tp_info.op_key.rdata = &tls->rdata;
+ pj_ioqueue_op_key_init(&tls->rdata.tp_info.op_key.op_key,
+ sizeof(pj_ioqueue_op_key_t));
+
+ tls->rdata.pkt_info.src_addr = tls->base.key.rem_addr;
+ tls->rdata.pkt_info.src_addr_len = sizeof(pj_sockaddr_in);
+ rem_addr = (pj_sockaddr_in*) &tls->base.key.rem_addr;
+ pj_ansi_strcpy(tls->rdata.pkt_info.src_name,
+ pj_inet_ntoa(rem_addr->sin_addr));
+ tls->rdata.pkt_info.src_port = pj_ntohs(rem_addr->sin_port);
+
+ size = sizeof(tls->rdata.pkt_info.packet);
+ readbuf[0] = tls->rdata.pkt_info.packet;
+ status = pj_ssl_sock_start_read2(tls->ssock, tls->base.pool, size,
+ readbuf, 0);
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ PJ_LOG(4, (tls->base.obj_name,
+ "pj_ssl_sock_start_read() error, status=%d",
+ status));
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/* This callback is called by transport manager for the TLS factory
+ * to create outgoing transport to the specified destination.
+ */
+static pj_status_t lis_create_transport(pjsip_tpfactory *factory,
+ pjsip_tpmgr *mgr,
+ pjsip_endpoint *endpt,
+ const pj_sockaddr *rem_addr,
+ int addr_len,
+ pjsip_tx_data *tdata,
+ pjsip_transport **p_transport)
+{
+ struct tls_listener *listener;
+ struct tls_transport *tls;
+ pj_pool_t *pool;
+ pj_ssl_sock_t *ssock;
+ pj_ssl_sock_param ssock_param;
+ pj_sockaddr_in local_addr;
+ pj_str_t remote_name;
+ pj_status_t status;
+
+ /* Sanity checks */
+ PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr &&
+ addr_len && p_transport, PJ_EINVAL);
+
+ /* Check that address is a sockaddr_in */
+ PJ_ASSERT_RETURN(rem_addr->addr.sa_family == pj_AF_INET() &&
+ addr_len == sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+ listener = (struct tls_listener*)factory;
+
+ pool = pjsip_endpt_create_pool(listener->endpt, "tls",
+ POOL_TP_INIT, POOL_TP_INC);
+ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
+
+ /* Get remote host name from tdata */
+ if (tdata)
+ remote_name = tdata->dest_info.name;
+ else
+ pj_bzero(&remote_name, sizeof(remote_name));
+
+ /* Build SSL socket param */
+ pj_ssl_sock_param_default(&ssock_param);
+ ssock_param.cb.on_connect_complete = &on_connect_complete;
+ ssock_param.cb.on_data_read = &on_data_read;
+ ssock_param.cb.on_data_sent = &on_data_sent;
+ ssock_param.async_cnt = 1;
+ ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt);
+ ssock_param.server_name = remote_name;
+ ssock_param.timeout = listener->tls_setting.timeout;
+ ssock_param.user_data = NULL; /* pending, must be set later */
+ ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket
+ * due to verification error */
+ if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN;
+ if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN)
+ ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN;
+ ssock_param.ciphers_num = listener->tls_setting.ciphers_num;
+ ssock_param.ciphers = listener->tls_setting.ciphers;
+ ssock_param.qos_type = listener->tls_setting.qos_type;
+ ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error;
+ pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params,
+ sizeof(ssock_param.qos_params));
+
+ switch(listener->tls_setting.method) {
+ case PJSIP_TLSV1_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1;
+ break;
+ case PJSIP_SSLV2_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2;
+ break;
+ case PJSIP_SSLV3_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3;
+ break;
+ case PJSIP_SSLV23_METHOD:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23;
+ break;
+ default:
+ ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT;
+ break;
+ }
+
+ status = pj_ssl_sock_create(pool, &ssock_param, &ssock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Apply SSL certificate */
+ if (listener->cert) {
+ status = pj_ssl_sock_set_certificate(ssock, pool, listener->cert);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* Initially set bind address to PJ_INADDR_ANY port 0 */
+ pj_sockaddr_in_init(&local_addr, NULL, 0);
+
+ /* Create the transport descriptor */
+ status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr,
+ (pj_sockaddr_in*)rem_addr, &remote_name, &tls);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set the "pending" SSL socket user data */
+ pj_ssl_sock_set_user_data(tls->ssock, tls);
+
+ /* Start asynchronous connect() operation */
+ tls->has_pending_connect = PJ_TRUE;
+ status = pj_ssl_sock_start_connect(tls->ssock, tls->base.pool,
+ (pj_sockaddr_t*)&local_addr,
+ (pj_sockaddr_t*)rem_addr,
+ addr_len);
+ if (status == PJ_SUCCESS) {
+ on_connect_complete(tls->ssock, PJ_SUCCESS);
+ } else if (status != PJ_EPENDING) {
+ tls_destroy(&tls->base, status);
+ return status;
+ }
+
+ if (tls->has_pending_connect) {
+ pj_ssl_sock_info info;
+
+ /* Update local address, just in case local address currently set is
+ * different now that asynchronous connect() is started.
+ */
+
+ /* Retrieve the bound address */
+ status = pj_ssl_sock_get_info(tls->ssock, &info);
+ if (status == PJ_SUCCESS) {
+ pj_uint16_t new_port;
+
+ new_port = pj_sockaddr_get_port((pj_sockaddr_t*)&info.local_addr);
+
+ if (pj_sockaddr_has_addr((pj_sockaddr_t*)&info.local_addr)) {
+ /* Update sockaddr */
+ pj_sockaddr_cp((pj_sockaddr_t*)&tls->base.local_addr,
+ (pj_sockaddr_t*)&info.local_addr);
+ } else if (new_port && new_port != pj_sockaddr_get_port(
+ (pj_sockaddr_t*)&tls->base.local_addr))
+ {
+ /* Update port only */
+ pj_sockaddr_set_port(&tls->base.local_addr,
+ new_port);
+ }
+
+ sockaddr_to_host_port(tls->base.pool, &tls->base.local_name,
+ (pj_sockaddr_in*)&tls->base.local_addr);
+ }
+
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS transport %.*s:%d is connecting to %.*s:%d...",
+ (int)tls->base.local_name.host.slen,
+ tls->base.local_name.host.ptr,
+ tls->base.local_name.port,
+ (int)tls->base.remote_name.host.slen,
+ tls->base.remote_name.host.ptr,
+ tls->base.remote_name.port));
+ }
+
+ /* Done */
+ *p_transport = &tls->base;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * This callback is called by SSL socket when pending accept() operation
+ * has completed.
+ */
+static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock,
+ pj_ssl_sock_t *new_ssock,
+ const pj_sockaddr_t *src_addr,
+ int src_addr_len)
+{
+ struct tls_listener *listener;
+ struct tls_transport *tls;
+ pj_ssl_sock_info ssl_info;
+ char addr[PJ_INET6_ADDRSTRLEN+10];
+ pjsip_tp_state_callback state_cb;
+ pj_bool_t is_shutdown;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(src_addr_len);
+
+ listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock);
+
+ PJ_ASSERT_RETURN(new_ssock, PJ_TRUE);
+
+ PJ_LOG(4,(listener->factory.obj_name,
+ "TLS listener %.*s:%d: got incoming TLS connection "
+ "from %s, sock=%d",
+ (int)listener->factory.addr_name.host.slen,
+ listener->factory.addr_name.host.ptr,
+ listener->factory.addr_name.port,
+ pj_sockaddr_print(src_addr, addr, sizeof(addr), 3),
+ new_ssock));
+
+ /* Retrieve SSL socket info, close the socket if this is failed
+ * as the SSL socket info availability is rather critical here.
+ */
+ status = pj_ssl_sock_get_info(new_ssock, &ssl_info);
+ if (status != PJ_SUCCESS) {
+ pj_ssl_sock_close(new_ssock);
+ return PJ_TRUE;
+ }
+
+ /*
+ * Incoming connection!
+ * Create TLS transport for the new socket.
+ */
+ status = tls_create( listener, NULL, new_ssock, PJ_TRUE,
+ (const pj_sockaddr_in*)&listener->factory.local_addr,
+ (const pj_sockaddr_in*)src_addr, NULL, &tls);
+
+ if (status != PJ_SUCCESS)
+ return PJ_TRUE;
+
+ /* Set the "pending" SSL socket user data */
+ pj_ssl_sock_set_user_data(new_ssock, tls);
+
+ /* Prevent immediate transport destroy as application may access it
+ * (getting info, etc) in transport state notification callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* If there is verification error and verification is mandatory, shutdown
+ * and destroy the transport.
+ */
+ if (ssl_info.verify_status && listener->tls_setting.verify_client) {
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = PJSIP_TLS_ECERTVERIF;
+ pjsip_transport_shutdown(&tls->base);
+ }
+
+ /* Notify transport state to application */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pjsip_transport_state tp_state;
+
+ /* Init transport state info */
+ pj_bzero(&tls_info, sizeof(tls_info));
+ pj_bzero(&state_info, sizeof(state_info));
+ tls_info.ssl_sock_info = &ssl_info;
+ state_info.ext_info = &tls_info;
+
+ /* Set transport state based on verification status */
+ if (ssl_info.verify_status && listener->tls_setting.verify_client)
+ {
+ tp_state = PJSIP_TP_STATE_DISCONNECTED;
+ state_info.status = PJSIP_TLS_ECERTVERIF;
+ } else {
+ tp_state = PJSIP_TP_STATE_CONNECTED;
+ state_info.status = PJ_SUCCESS;
+ }
+
+ (*state_cb)(&tls->base, tp_state, &state_info);
+ }
+
+ /* Release transport reference. If transport is shutting down, it may
+ * get destroyed here.
+ */
+ is_shutdown = tls->base.is_shutdown;
+ pjsip_transport_dec_ref(&tls->base);
+ if (is_shutdown)
+ return PJ_TRUE;
+
+
+ status = tls_start_read(tls);
+ if (status != PJ_SUCCESS) {
+ PJ_LOG(3,(tls->base.obj_name, "New transport cancelled"));
+ tls_init_shutdown(tls, status);
+ tls_destroy(&tls->base, status);
+ } else {
+ /* Start keep-alive timer */
+ if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = {PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0};
+ pjsip_endpt_schedule_timer(listener->endpt,
+ &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tls->last_activity);
+ }
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when packet is sent.
+ */
+static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_sent)
+{
+ struct tls_transport *tls = (struct tls_transport*)
+ pj_ssl_sock_get_user_data(ssock);
+ pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key;
+
+ /* Note that op_key may be the op_key from keep-alive, thus
+ * it will not have tdata etc.
+ */
+
+ tdata_op_key->tdata = NULL;
+
+ if (tdata_op_key->callback) {
+ /*
+ * Notify sip_transport.c that packet has been sent.
+ */
+ if (bytes_sent == 0)
+ bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tdata_op_key->callback(&tls->base, tdata_op_key->token, bytes_sent);
+
+ /* Mark last activity time */
+ pj_gettimeofday(&tls->last_activity);
+
+ }
+
+ /* Check for error/closure */
+ if (bytes_sent <= 0) {
+ pj_status_t status;
+
+ PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d",
+ bytes_sent));
+
+ status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) :
+ -bytes_sent;
+
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+ }
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * This callback is called by transport manager to send SIP message
+ */
+static pj_status_t tls_send_msg(pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+ pj_ssize_t size;
+ pj_bool_t delayed = PJ_FALSE;
+ pj_status_t status = PJ_SUCCESS;
+
+ /* Sanity check */
+ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL);
+
+ /* Check that there's no pending operation associated with the tdata */
+ PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX);
+
+ /* Check the address is supported */
+ PJ_ASSERT_RETURN(rem_addr && addr_len==sizeof(pj_sockaddr_in), PJ_EINVAL);
+
+
+
+ /* Init op key. */
+ tdata->op_key.tdata = tdata;
+ tdata->op_key.token = token;
+ tdata->op_key.callback = callback;
+
+ /* If asynchronous connect() has not completed yet, just put the
+ * transmit data in the pending transmission list since we can not
+ * use the socket yet.
+ */
+ if (tls->has_pending_connect) {
+
+ /*
+ * Looks like connect() is still in progress. Check again (this time
+ * with holding the lock) to be sure.
+ */
+ pj_lock_acquire(tls->base.lock);
+
+ if (tls->has_pending_connect) {
+ struct delayed_tdata *delayed_tdata;
+
+ /*
+ * connect() is still in progress. Put the transmit data to
+ * the delayed list.
+ */
+ delayed_tdata = PJ_POOL_ALLOC_T(tdata->pool,
+ struct delayed_tdata);
+ delayed_tdata->tdata_op_key = &tdata->op_key;
+
+ pj_list_push_back(&tls->delayed_list, delayed_tdata);
+ status = PJ_EPENDING;
+
+ /* Prevent pj_ioqueue_send() to be called below */
+ delayed = PJ_TRUE;
+ }
+
+ pj_lock_release(tls->base.lock);
+ }
+
+ if (!delayed) {
+ /*
+ * Transport is ready to go. Send the packet to ioqueue to be
+ * sent asynchronously.
+ */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_ssl_sock_send(tls->ssock,
+ (pj_ioqueue_op_key_t*)&tdata->op_key,
+ tdata->buf.start, &size, 0);
+
+ if (status != PJ_EPENDING) {
+ /* Not pending (could be immediate success or error) */
+ tdata->op_key.tdata = NULL;
+
+ /* Shutdown transport on closure/errors */
+ if (size <= 0) {
+
+ PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d",
+ size));
+
+ if (status == PJ_SUCCESS)
+ status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN);
+
+ tls_init_shutdown(tls, status);
+ }
+ }
+ }
+
+ return status;
+}
+
+
+/*
+ * This callback is called by transport manager to shutdown transport.
+ */
+static pj_status_t tls_shutdown(pjsip_transport *transport)
+{
+ struct tls_transport *tls = (struct tls_transport*)transport;
+
+ /* Stop keep-alive timer. */
+ if (tls->ka_timer.id) {
+ pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer);
+ tls->ka_timer.id = PJ_FALSE;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Callback from ioqueue that an incoming data is received from the socket.
+ */
+static pj_bool_t on_data_read(pj_ssl_sock_t *ssock,
+ void *data,
+ pj_size_t size,
+ pj_status_t status,
+ pj_size_t *remainder)
+{
+ enum { MAX_IMMEDIATE_PACKET = 10 };
+ struct tls_transport *tls;
+ pjsip_rx_data *rdata;
+
+ PJ_UNUSED_ARG(data);
+
+ tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock);
+ rdata = &tls->rdata;
+
+ /* Don't do anything if transport is closing. */
+ if (tls->is_closing) {
+ tls->is_closing++;
+ return PJ_FALSE;
+ }
+
+ /* Houston, we have packet! Report the packet to transport manager
+ * to be parsed.
+ */
+ if (status == PJ_SUCCESS) {
+ pj_size_t size_eaten;
+
+ /* Mark this as an activity */
+ pj_gettimeofday(&tls->last_activity);
+
+ pj_assert((void*)rdata->pkt_info.packet == data);
+
+ /* Init pkt_info part. */
+ rdata->pkt_info.len = size;
+ rdata->pkt_info.zero = 0;
+ pj_gettimeofday(&rdata->pkt_info.timestamp);
+
+ /* Report to transport manager.
+ * The transport manager will tell us how many bytes of the packet
+ * have been processed (as valid SIP message).
+ */
+ size_eaten =
+ pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr,
+ rdata);
+
+ pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len);
+
+ /* Move unprocessed data to the front of the buffer */
+ *remainder = size - size_eaten;
+ if (*remainder > 0 && *remainder != size) {
+ pj_memmove(rdata->pkt_info.packet,
+ rdata->pkt_info.packet + size_eaten,
+ *remainder);
+ }
+
+ } else {
+
+ /* Transport is closed */
+ PJ_LOG(4,(tls->base.obj_name, "TLS connection closed"));
+
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+
+ }
+
+ /* Reset pool. */
+ pj_pool_reset(rdata->tp_info.pool);
+
+ return PJ_TRUE;
+}
+
+
+/*
+ * Callback from ioqueue when asynchronous connect() operation completes.
+ */
+static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock,
+ pj_status_t status)
+{
+ struct tls_transport *tls;
+ pj_ssl_sock_info ssl_info;
+ pj_sockaddr_in addr, *tp_addr;
+ pjsip_tp_state_callback state_cb;
+ pj_bool_t is_shutdown;
+
+ tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock);
+
+ /* Check connect() status */
+ if (status != PJ_SUCCESS) {
+
+ tls_perror(tls->base.obj_name, "TLS connect() error", status);
+
+ /* Cancel all delayed transmits */
+ while (!pj_list_empty(&tls->delayed_list)) {
+ struct delayed_tdata *pending_tx;
+ pj_ioqueue_op_key_t *op_key;
+
+ pending_tx = tls->delayed_list.next;
+ pj_list_erase(pending_tx);
+
+ op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key;
+
+ on_data_sent(tls->ssock, op_key, -status);
+ }
+
+ goto on_error;
+ }
+
+ /* Retrieve SSL socket info, shutdown the transport if this is failed
+ * as the SSL socket info availability is rather critical here.
+ */
+ status = pj_ssl_sock_get_info(tls->ssock, &ssl_info);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Update (again) local address, just in case local address currently
+ * set is different now that the socket is connected (could happen
+ * on some systems, like old Win32 probably?).
+ */
+ tp_addr = (pj_sockaddr_in*)&tls->base.local_addr;
+ pj_sockaddr_cp((pj_sockaddr_t*)&addr,
+ (pj_sockaddr_t*)&ssl_info.local_addr);
+ if (tp_addr->sin_addr.s_addr != addr.sin_addr.s_addr) {
+ tp_addr->sin_addr.s_addr = addr.sin_addr.s_addr;
+ tp_addr->sin_port = addr.sin_port;
+ sockaddr_to_host_port(tls->base.pool, &tls->base.local_name,
+ tp_addr);
+ }
+
+ /* Server identity verification based on server certificate. */
+ if (ssl_info.remote_cert_info->version) {
+ pj_str_t *remote_name;
+ pj_ssl_cert_info *serv_cert = ssl_info.remote_cert_info;
+ pj_bool_t matched = PJ_FALSE;
+ unsigned i;
+
+ /* Remote name may be hostname or IP address */
+ if (tls->remote_name.slen)
+ remote_name = &tls->remote_name;
+ else
+ remote_name = &tls->base.remote_name.host;
+
+ /* Start matching remote name with SubjectAltName fields of
+ * server certificate.
+ */
+ for (i = 0; i < serv_cert->subj_alt_name.cnt && !matched; ++i) {
+ pj_str_t *cert_name = &serv_cert->subj_alt_name.entry[i].name;
+
+ switch (serv_cert->subj_alt_name.entry[i].type) {
+ case PJ_SSL_CERT_NAME_DNS:
+ case PJ_SSL_CERT_NAME_IP:
+ matched = !pj_stricmp(remote_name, cert_name);
+ break;
+ case PJ_SSL_CERT_NAME_URI:
+ if (pj_strnicmp2(cert_name, "sip:", 4) == 0 ||
+ pj_strnicmp2(cert_name, "sips:", 5) == 0)
+ {
+ pj_str_t host_part;
+ char *p;
+
+ p = pj_strchr(cert_name, ':') + 1;
+ pj_strset(&host_part, p, cert_name->slen -
+ (p - cert_name->ptr));
+ matched = !pj_stricmp(remote_name, &host_part);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* When still not matched or no SubjectAltName fields in server
+ * certificate, try with Common Name of Subject field.
+ */
+ if (!matched) {
+ matched = !pj_stricmp(remote_name, &serv_cert->subject.cn);
+ }
+
+ if (!matched)
+ ssl_info.verify_status |= PJ_SSL_CERT_EIDENTITY_NOT_MATCH;
+ }
+
+ /* Prevent immediate transport destroy as application may access it
+ * (getting info, etc) in transport state notification callback.
+ */
+ pjsip_transport_add_ref(&tls->base);
+
+ /* If there is verification error and verification is mandatory, shutdown
+ * and destroy the transport.
+ */
+ if (ssl_info.verify_status && tls->verify_server) {
+ if (tls->close_reason == PJ_SUCCESS)
+ tls->close_reason = PJSIP_TLS_ECERTVERIF;
+ pjsip_transport_shutdown(&tls->base);
+ }
+
+ /* Notify transport state to application */
+ state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr);
+ if (state_cb) {
+ pjsip_transport_state_info state_info;
+ pjsip_tls_state_info tls_info;
+ pjsip_transport_state tp_state;
+
+ /* Init transport state info */
+ pj_bzero(&state_info, sizeof(state_info));
+ pj_bzero(&tls_info, sizeof(tls_info));
+ state_info.ext_info = &tls_info;
+ tls_info.ssl_sock_info = &ssl_info;
+
+ /* Set transport state based on verification status */
+ if (ssl_info.verify_status && tls->verify_server)
+ {
+ tp_state = PJSIP_TP_STATE_DISCONNECTED;
+ state_info.status = PJSIP_TLS_ECERTVERIF;
+ } else {
+ tp_state = PJSIP_TP_STATE_CONNECTED;
+ state_info.status = PJ_SUCCESS;
+ }
+
+ (*state_cb)(&tls->base, tp_state, &state_info);
+ }
+
+ /* Release transport reference. If transport is shutting down, it may
+ * get destroyed here.
+ */
+ is_shutdown = tls->base.is_shutdown;
+ pjsip_transport_dec_ref(&tls->base);
+ if (is_shutdown)
+ return PJ_FALSE;
+
+
+ /* Mark that pending connect() operation has completed. */
+ tls->has_pending_connect = PJ_FALSE;
+
+ PJ_LOG(4,(tls->base.obj_name,
+ "TLS transport %.*s:%d is connected to %.*s:%d",
+ (int)tls->base.local_name.host.slen,
+ tls->base.local_name.host.ptr,
+ tls->base.local_name.port,
+ (int)tls->base.remote_name.host.slen,
+ tls->base.remote_name.host.ptr,
+ tls->base.remote_name.port));
+
+ /* Start pending read */
+ status = tls_start_read(tls);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Flush all pending send operations */
+ tls_flush_pending_tx(tls);
+
+ /* Start keep-alive timer */
+ if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ pj_time_val delay = { PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0 };
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ pj_gettimeofday(&tls->last_activity);
+ }
+
+ return PJ_TRUE;
+
+on_error:
+ tls_init_shutdown(tls, status);
+
+ return PJ_FALSE;
+}
+
+
+/* Transport keep-alive timer callback */
+static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e)
+{
+ struct tls_transport *tls = (struct tls_transport*) e->user_data;
+ pj_time_val delay;
+ pj_time_val now;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ PJ_UNUSED_ARG(th);
+
+ tls->ka_timer.id = PJ_TRUE;
+
+ pj_gettimeofday(&now);
+ PJ_TIME_VAL_SUB(now, tls->last_activity);
+
+ if (now.sec > 0 && now.sec < PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+ /* There has been activity, so don't send keep-alive */
+ delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL - now.sec;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+ return;
+ }
+
+ PJ_LOG(5,(tls->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d",
+ (int)tls->ka_pkt.slen, (int)tls->base.remote_name.host.slen,
+ tls->base.remote_name.host.ptr,
+ tls->base.remote_name.port));
+
+ /* Send the data */
+ size = tls->ka_pkt.slen;
+ status = pj_ssl_sock_send(tls->ssock, &tls->ka_op_key.key,
+ tls->ka_pkt.ptr, &size, 0);
+
+ if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+ tls_perror(tls->base.obj_name,
+ "Error sending keep-alive packet", status);
+
+ tls_init_shutdown(tls, status);
+ return;
+ }
+
+ /* Register next keep-alive */
+ delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL;
+ delay.msec = 0;
+
+ pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer,
+ &delay);
+ tls->ka_timer.id = PJ_TRUE;
+}
+
+#endif /* PJSIP_HAS_TLS_TRANSPORT */
diff --git a/pjsip/src/pjsip/sip_transport_udp.c b/pjsip/src/pjsip/sip_transport_udp.c
new file mode 100644
index 0000000..60b3175
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_udp.c
@@ -0,0 +1,1095 @@
+/* $Id: sip_transport_udp.c 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_transport_udp.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pj/addr_resolv.h>
+#include <pj/assert.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+#include <pj/sock.h>
+#include <pj/compat/socket.h>
+#include <pj/string.h>
+
+
+#define THIS_FILE "sip_transport_udp.c"
+
+/**
+ * These are the target values for socket send and receive buffer sizes,
+ * respectively. They will be applied to UDP socket with setsockopt().
+ * When transport failed to set these size, it will decrease it until
+ * sufficiently large number has been successfully set.
+ *
+ * The buffer size is important, especially in WinXP/2000 machines.
+ * Basicly the lower the size, the more packets will be lost (dropped?)
+ * when we're sending (receiving?) packets in large volumes.
+ *
+ * The figure here is taken based on my experiment on WinXP/2000 machine,
+ * and with this value, the rate of dropped packet is about 8% when
+ * sending 1800 requests simultaneously (percentage taken as average
+ * after 50K requests or so).
+ *
+ * More experiments are needed probably.
+ */
+/* 2010/01/14
+ * Too many people complained about seeing "Error setting SNDBUF" log,
+ * so lets just remove this. People who want to have SNDBUF set can
+ * still do so by declaring these two macros in config_site.h
+ */
+#ifndef PJSIP_UDP_SO_SNDBUF_SIZE
+/*# define PJSIP_UDP_SO_SNDBUF_SIZE (24*1024*1024)*/
+# define PJSIP_UDP_SO_SNDBUF_SIZE 0
+#endif
+
+#ifndef PJSIP_UDP_SO_RCVBUF_SIZE
+/*# define PJSIP_UDP_SO_RCVBUF_SIZE (24*1024*1024)*/
+# define PJSIP_UDP_SO_RCVBUF_SIZE 0
+#endif
+
+
+/* Struct udp_transport "inherits" struct pjsip_transport */
+struct udp_transport
+{
+ pjsip_transport base;
+ pj_sock_t sock;
+ pj_ioqueue_key_t *key;
+ int rdata_cnt;
+ pjsip_rx_data **rdata;
+ int is_closing;
+ pj_bool_t is_paused;
+};
+
+
+/*
+ * Initialize transport's receive buffer from the specified pool.
+ */
+static void init_rdata(struct udp_transport *tp, unsigned rdata_index,
+ pj_pool_t *pool, pjsip_rx_data **p_rdata)
+{
+ pjsip_rx_data *rdata;
+
+ /* Reset pool. */
+ //note: already done by caller
+ //pj_pool_reset(pool);
+
+ rdata = PJ_POOL_ZALLOC_T(pool, pjsip_rx_data);
+
+ /* Init tp_info part. */
+ rdata->tp_info.pool = pool;
+ rdata->tp_info.transport = &tp->base;
+ rdata->tp_info.tp_data = (void*)(long)rdata_index;
+ rdata->tp_info.op_key.rdata = rdata;
+ pj_ioqueue_op_key_init(&rdata->tp_info.op_key.op_key,
+ sizeof(pj_ioqueue_op_key_t));
+
+ tp->rdata[rdata_index] = rdata;
+
+ if (p_rdata)
+ *p_rdata = rdata;
+}
+
+
+/*
+ * udp_on_read_complete()
+ *
+ * This is callback notification from ioqueue that a pending recvfrom()
+ * operation has completed.
+ */
+static void udp_on_read_complete( pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_read)
+{
+ /* See https://trac.pjsip.org/repos/ticket/1197 */
+ enum { MAX_IMMEDIATE_PACKET = 50 };
+ pjsip_rx_data_op_key *rdata_op_key = (pjsip_rx_data_op_key*) op_key;
+ pjsip_rx_data *rdata = rdata_op_key->rdata;
+ struct udp_transport *tp = (struct udp_transport*)rdata->tp_info.transport;
+ int i;
+ pj_status_t status;
+
+ /* Don't do anything if transport is closing. */
+ if (tp->is_closing) {
+ tp->is_closing++;
+ return;
+ }
+
+ /* Don't do anything if transport is being paused. */
+ if (tp->is_paused)
+ return;
+
+ /*
+ * The idea of the loop is to process immediate data received by
+ * pj_ioqueue_recvfrom(), as long as i < MAX_IMMEDIATE_PACKET. When
+ * i is >= MAX_IMMEDIATE_PACKET, we force the recvfrom() operation to
+ * complete asynchronously, to allow other sockets to get their data.
+ */
+ for (i=0;; ++i) {
+ enum { MIN_SIZE = 32 };
+ pj_uint32_t flags;
+
+ /* Report the packet to transport manager. Only do so if packet size
+ * is relatively big enough for a SIP packet.
+ */
+ if (bytes_read > MIN_SIZE) {
+ pj_size_t size_eaten;
+ const pj_sockaddr *src_addr = &rdata->pkt_info.src_addr;
+
+ /* Init pkt_info part. */
+ rdata->pkt_info.len = bytes_read;
+ rdata->pkt_info.zero = 0;
+ pj_gettimeofday(&rdata->pkt_info.timestamp);
+ if (src_addr->addr.sa_family == pj_AF_INET()) {
+ pj_ansi_strcpy(rdata->pkt_info.src_name,
+ pj_inet_ntoa(src_addr->ipv4.sin_addr));
+ rdata->pkt_info.src_port = pj_ntohs(src_addr->ipv4.sin_port);
+ } else {
+ pj_inet_ntop(pj_AF_INET6(),
+ pj_sockaddr_get_addr(&rdata->pkt_info.src_addr),
+ rdata->pkt_info.src_name,
+ sizeof(rdata->pkt_info.src_name));
+ rdata->pkt_info.src_port = pj_ntohs(src_addr->ipv6.sin6_port);
+ }
+
+ size_eaten =
+ pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr,
+ rdata);
+
+ if (size_eaten < 0) {
+ pj_assert(!"It shouldn't happen!");
+ size_eaten = rdata->pkt_info.len;
+ }
+
+ /* Since this is UDP, the whole buffer is the message. */
+ rdata->pkt_info.len = 0;
+
+ } else if (bytes_read <= MIN_SIZE) {
+
+ /* TODO: */
+
+ } else if (-bytes_read != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) &&
+ -bytes_read != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) &&
+ -bytes_read != PJ_STATUS_FROM_OS(OSERR_ECONNRESET))
+ {
+
+ /* Report error to endpoint. */
+ PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
+ rdata->tp_info.transport->obj_name,
+ -bytes_read,
+ "Warning: pj_ioqueue_recvfrom()"
+ " callback error"));
+ }
+
+ if (i >= MAX_IMMEDIATE_PACKET) {
+ /* Force ioqueue_recvfrom() to return PJ_EPENDING */
+ flags = PJ_IOQUEUE_ALWAYS_ASYNC;
+ } else {
+ flags = 0;
+ }
+
+ /* Reset pool.
+ * Need to copy rdata fields to temp variable because they will
+ * be invalid after pj_pool_reset().
+ */
+ {
+ pj_pool_t *rdata_pool = rdata->tp_info.pool;
+ struct udp_transport *rdata_tp ;
+ unsigned rdata_index;
+
+ rdata_tp = (struct udp_transport*)rdata->tp_info.transport;
+ rdata_index = (unsigned)(unsigned long)rdata->tp_info.tp_data;
+
+ pj_pool_reset(rdata_pool);
+ init_rdata(rdata_tp, rdata_index, rdata_pool, &rdata);
+
+ /* Change some vars to point to new location after
+ * pool reset.
+ */
+ op_key = &rdata->tp_info.op_key.op_key;
+ }
+
+ /* Only read next packet if transport is not being paused. This
+ * check handles the case where transport is paused while endpoint
+ * is still processing a SIP message.
+ */
+ if (tp->is_paused)
+ return;
+
+ /* Read next packet. */
+ bytes_read = sizeof(rdata->pkt_info.packet);
+ rdata->pkt_info.src_addr_len = sizeof(rdata->pkt_info.src_addr);
+ status = pj_ioqueue_recvfrom(key, op_key,
+ rdata->pkt_info.packet,
+ &bytes_read, flags,
+ &rdata->pkt_info.src_addr,
+ &rdata->pkt_info.src_addr_len);
+
+ if (status == PJ_SUCCESS) {
+ /* Continue loop. */
+ pj_assert(i < MAX_IMMEDIATE_PACKET);
+
+ } else if (status == PJ_EPENDING) {
+ break;
+
+ } else {
+
+ if (i < MAX_IMMEDIATE_PACKET) {
+
+ /* Report error to endpoint if this is not EWOULDBLOCK error.*/
+ if (status != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK) &&
+ status != PJ_STATUS_FROM_OS(OSERR_EINPROGRESS) &&
+ status != PJ_STATUS_FROM_OS(OSERR_ECONNRESET))
+ {
+
+ PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
+ rdata->tp_info.transport->obj_name,
+ status,
+ "Warning: pj_ioqueue_recvfrom"));
+ }
+
+ /* Continue loop. */
+ bytes_read = 0;
+ } else {
+ /* This is fatal error.
+ * Ioqueue operation will stop for this transport!
+ */
+ PJSIP_ENDPT_LOG_ERROR((rdata->tp_info.transport->endpt,
+ rdata->tp_info.transport->obj_name,
+ status,
+ "FATAL: pj_ioqueue_recvfrom() error, "
+ "UDP transport stopping! Error"));
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * udp_on_write_complete()
+ *
+ * This is callback notification from ioqueue that a pending sendto()
+ * operation has completed.
+ */
+static void udp_on_write_complete( pj_ioqueue_key_t *key,
+ pj_ioqueue_op_key_t *op_key,
+ pj_ssize_t bytes_sent)
+{
+ struct udp_transport *tp = (struct udp_transport*)
+ pj_ioqueue_get_user_data(key);
+ pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key;
+
+ tdata_op_key->tdata = NULL;
+
+ if (tdata_op_key->callback) {
+ tdata_op_key->callback(&tp->base, tdata_op_key->token, bytes_sent);
+ }
+}
+
+/*
+ * udp_send_msg()
+ *
+ * This function is called by transport manager (by transport->send_msg())
+ * to send outgoing message.
+ */
+static pj_status_t udp_send_msg( pjsip_transport *transport,
+ pjsip_tx_data *tdata,
+ const pj_sockaddr_t *rem_addr,
+ int addr_len,
+ void *token,
+ pjsip_transport_callback callback)
+{
+ struct udp_transport *tp = (struct udp_transport*)transport;
+ pj_ssize_t size;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX);
+
+ /* Return error if transport is paused */
+ if (tp->is_paused)
+ return PJSIP_ETPNOTAVAIL;
+
+ /* Init op key. */
+ tdata->op_key.tdata = tdata;
+ tdata->op_key.token = token;
+ tdata->op_key.callback = callback;
+
+ /* Send to ioqueue! */
+ size = tdata->buf.cur - tdata->buf.start;
+ status = pj_ioqueue_sendto(tp->key, (pj_ioqueue_op_key_t*)&tdata->op_key,
+ tdata->buf.start, &size, 0,
+ rem_addr, addr_len);
+
+ if (status != PJ_EPENDING)
+ tdata->op_key.tdata = NULL;
+
+ return status;
+}
+
+/*
+ * udp_destroy()
+ *
+ * This function is called by transport manager (by transport->destroy()).
+ */
+static pj_status_t udp_destroy( pjsip_transport *transport )
+{
+ struct udp_transport *tp = (struct udp_transport*)transport;
+ int i;
+
+ /* Mark this transport as closing. */
+ tp->is_closing = 1;
+
+ /* Cancel all pending operations. */
+ /* blp: NO NO NO...
+ * No need to post queued completion as we poll the ioqueue until
+ * we've got events anyway. Posting completion will only cause
+ * callback to be called twice with IOCP: one for the post completion
+ * and another one for closing the socket.
+ *
+ for (i=0; i<tp->rdata_cnt; ++i) {
+ pj_ioqueue_post_completion(tp->key,
+ &tp->rdata[i]->tp_info.op_key.op_key, -1);
+ }
+ */
+
+ /* Unregister from ioqueue. */
+ if (tp->key) {
+ pj_ioqueue_unregister(tp->key);
+ tp->key = NULL;
+ } else {
+ /* Close socket. */
+ if (tp->sock && tp->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(tp->sock);
+ tp->sock = PJ_INVALID_SOCKET;
+ }
+ }
+
+ /* Must poll ioqueue because IOCP calls the callback when socket
+ * is closed. We poll the ioqueue until all pending callbacks
+ * have been called.
+ */
+ for (i=0; i<50 && tp->is_closing < 1+tp->rdata_cnt; ++i) {
+ int cnt;
+ pj_time_val timeout = {0, 1};
+
+ cnt = pj_ioqueue_poll(pjsip_endpt_get_ioqueue(transport->endpt),
+ &timeout);
+ if (cnt == 0)
+ break;
+ }
+
+ /* Destroy rdata */
+ for (i=0; i<tp->rdata_cnt; ++i) {
+ pj_pool_release(tp->rdata[i]->tp_info.pool);
+ }
+
+ /* Destroy reference counter. */
+ if (tp->base.ref_cnt)
+ pj_atomic_destroy(tp->base.ref_cnt);
+
+ /* Destroy lock */
+ if (tp->base.lock)
+ pj_lock_destroy(tp->base.lock);
+
+ /* Destroy pool. */
+ pjsip_endpt_release_pool(tp->base.endpt, tp->base.pool);
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * udp_shutdown()
+ *
+ * Start graceful UDP shutdown.
+ */
+static pj_status_t udp_shutdown(pjsip_transport *transport)
+{
+ return pjsip_transport_dec_ref(transport);
+}
+
+
+/* Create socket */
+static pj_status_t create_socket(int af, const pj_sockaddr_t *local_a,
+ int addr_len, pj_sock_t *p_sock)
+{
+ pj_sock_t sock;
+ pj_sockaddr_in tmp_addr;
+ pj_sockaddr_in6 tmp_addr6;
+ pj_status_t status;
+
+ status = pj_sock_socket(af, pj_SOCK_DGRAM(), 0, &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (local_a == NULL) {
+ if (af == pj_AF_INET6()) {
+ pj_bzero(&tmp_addr6, sizeof(tmp_addr6));
+ tmp_addr6.sin6_family = (pj_uint16_t)af;
+ local_a = &tmp_addr6;
+ addr_len = sizeof(tmp_addr6);
+ } else {
+ pj_sockaddr_in_init(&tmp_addr, NULL, 0);
+ local_a = &tmp_addr;
+ addr_len = sizeof(tmp_addr);
+ }
+ }
+
+ status = pj_sock_bind(sock, local_a, addr_len);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ *p_sock = sock;
+ return PJ_SUCCESS;
+}
+
+
+/* Generate transport's published address */
+static pj_status_t get_published_name(pj_sock_t sock,
+ char hostbuf[],
+ int hostbufsz,
+ pjsip_host_port *bound_name)
+{
+ pj_sockaddr tmp_addr;
+ int addr_len;
+ pj_status_t status;
+
+ addr_len = sizeof(tmp_addr);
+ status = pj_sock_getsockname(sock, &tmp_addr, &addr_len);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ bound_name->host.ptr = hostbuf;
+ if (tmp_addr.addr.sa_family == pj_AF_INET()) {
+ bound_name->port = pj_ntohs(tmp_addr.ipv4.sin_port);
+
+ /* If bound address specifies "0.0.0.0", get the IP address
+ * of local hostname.
+ */
+ if (tmp_addr.ipv4.sin_addr.s_addr == PJ_INADDR_ANY) {
+ pj_sockaddr hostip;
+
+ status = pj_gethostip(pj_AF_INET(), &hostip);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_strcpy2(&bound_name->host, pj_inet_ntoa(hostip.ipv4.sin_addr));
+ } else {
+ /* Otherwise use bound address. */
+ pj_strcpy2(&bound_name->host,
+ pj_inet_ntoa(tmp_addr.ipv4.sin_addr));
+ status = PJ_SUCCESS;
+ }
+
+ } else {
+ /* If bound address specifies "INADDR_ANY" (IPv6), get the
+ * IP address of local hostname
+ */
+ pj_uint32_t loop6[4] = { 0, 0, 0, 0};
+
+ bound_name->port = pj_ntohs(tmp_addr.ipv6.sin6_port);
+
+ if (pj_memcmp(&tmp_addr.ipv6.sin6_addr, loop6, sizeof(loop6))==0) {
+ status = pj_gethostip(tmp_addr.addr.sa_family, &tmp_addr);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ status = pj_inet_ntop(tmp_addr.addr.sa_family,
+ pj_sockaddr_get_addr(&tmp_addr),
+ hostbuf, hostbufsz);
+ if (status == PJ_SUCCESS) {
+ bound_name->host.slen = pj_ansi_strlen(hostbuf);
+ }
+ }
+
+
+ return status;
+}
+
+/* Set the published address of the transport */
+static void udp_set_pub_name(struct udp_transport *tp,
+ const pjsip_host_port *a_name)
+{
+ enum { INFO_LEN = 80 };
+ char local_addr[PJ_INET6_ADDRSTRLEN+10];
+
+ pj_assert(a_name->host.slen != 0);
+ pj_strdup_with_null(tp->base.pool, &tp->base.local_name.host,
+ &a_name->host);
+ tp->base.local_name.port = a_name->port;
+
+ /* Update transport info. */
+ if (tp->base.info == NULL) {
+ tp->base.info = (char*) pj_pool_alloc(tp->base.pool, INFO_LEN);
+ }
+
+ pj_sockaddr_print(&tp->base.local_addr, local_addr, sizeof(local_addr), 3);
+
+ pj_ansi_snprintf(
+ tp->base.info, INFO_LEN, "udp %s [published as %s:%d]",
+ local_addr,
+ tp->base.local_name.host.ptr,
+ tp->base.local_name.port);
+}
+
+/* Set the socket handle of the transport */
+static void udp_set_socket(struct udp_transport *tp,
+ pj_sock_t sock,
+ const pjsip_host_port *a_name)
+{
+#if PJSIP_UDP_SO_RCVBUF_SIZE || PJSIP_UDP_SO_SNDBUF_SIZE
+ long sobuf_size;
+ pj_status_t status;
+#endif
+
+ /* Adjust socket rcvbuf size */
+#if PJSIP_UDP_SO_RCVBUF_SIZE
+ sobuf_size = PJSIP_UDP_SO_RCVBUF_SIZE;
+ status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), pj_SO_RCVBUF(),
+ &sobuf_size, sizeof(sobuf_size));
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE, "Error setting SO_RCVBUF: %s [%d]", errmsg,
+ status));
+ }
+#endif
+
+ /* Adjust socket sndbuf size */
+#if PJSIP_UDP_SO_SNDBUF_SIZE
+ sobuf_size = PJSIP_UDP_SO_SNDBUF_SIZE;
+ status = pj_sock_setsockopt(sock, pj_SOL_SOCKET(), pj_SO_SNDBUF(),
+ &sobuf_size, sizeof(sobuf_size));
+ if (status != PJ_SUCCESS) {
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+ PJ_LOG(4,(THIS_FILE, "Error setting SO_SNDBUF: %s [%d]", errmsg,
+ status));
+ }
+#endif
+
+ /* Set the socket. */
+ tp->sock = sock;
+
+ /* Init address name (published address) */
+ udp_set_pub_name(tp, a_name);
+}
+
+/* Register socket to ioqueue */
+static pj_status_t register_to_ioqueue(struct udp_transport *tp)
+{
+ pj_ioqueue_t *ioqueue;
+ pj_ioqueue_callback ioqueue_cb;
+
+ /* Ignore if already registered */
+ if (tp->key != NULL)
+ return PJ_SUCCESS;
+
+ /* Register to ioqueue. */
+ ioqueue = pjsip_endpt_get_ioqueue(tp->base.endpt);
+ pj_memset(&ioqueue_cb, 0, sizeof(ioqueue_cb));
+ ioqueue_cb.on_read_complete = &udp_on_read_complete;
+ ioqueue_cb.on_write_complete = &udp_on_write_complete;
+
+ return pj_ioqueue_register_sock(tp->base.pool, ioqueue, tp->sock, tp,
+ &ioqueue_cb, &tp->key);
+}
+
+/* Start ioqueue asynchronous reading to all rdata */
+static pj_status_t start_async_read(struct udp_transport *tp)
+{
+ pj_ioqueue_t *ioqueue;
+ int i;
+ pj_status_t status;
+
+ ioqueue = pjsip_endpt_get_ioqueue(tp->base.endpt);
+
+ /* Start reading the ioqueue. */
+ for (i=0; i<tp->rdata_cnt; ++i) {
+ pj_ssize_t size;
+
+ size = sizeof(tp->rdata[i]->pkt_info.packet);
+ tp->rdata[i]->pkt_info.src_addr_len = sizeof(tp->rdata[i]->pkt_info.src_addr);
+ status = pj_ioqueue_recvfrom(tp->key,
+ &tp->rdata[i]->tp_info.op_key.op_key,
+ tp->rdata[i]->pkt_info.packet,
+ &size, PJ_IOQUEUE_ALWAYS_ASYNC,
+ &tp->rdata[i]->pkt_info.src_addr,
+ &tp->rdata[i]->pkt_info.src_addr_len);
+ if (status == PJ_SUCCESS) {
+ pj_assert(!"Shouldn't happen because PJ_IOQUEUE_ALWAYS_ASYNC!");
+ udp_on_read_complete(tp->key, &tp->rdata[i]->tp_info.op_key.op_key,
+ size);
+ } else if (status != PJ_EPENDING) {
+ /* Error! */
+ return status;
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * pjsip_udp_transport_attach()
+ *
+ * Attach UDP socket and start transport.
+ */
+static pj_status_t transport_attach( pjsip_endpoint *endpt,
+ pjsip_transport_type_e type,
+ pj_sock_t sock,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ pj_pool_t *pool;
+ struct udp_transport *tp;
+ const char *format, *ipv6_quoteb, *ipv6_quotee;
+ unsigned i;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && sock!=PJ_INVALID_SOCKET && a_name && async_cnt>0,
+ PJ_EINVAL);
+
+ /* Object name. */
+ if (type & PJSIP_TRANSPORT_IPV6) {
+ format = "udpv6%p";
+ ipv6_quoteb = "[";
+ ipv6_quotee = "]";
+ } else {
+ format = "udp%p";
+ ipv6_quoteb = ipv6_quotee = "";
+ }
+
+ /* Create pool. */
+ pool = pjsip_endpt_create_pool(endpt, format, PJSIP_POOL_LEN_TRANSPORT,
+ PJSIP_POOL_INC_TRANSPORT);
+ if (!pool)
+ return PJ_ENOMEM;
+
+ /* Create the UDP transport object. */
+ tp = PJ_POOL_ZALLOC_T(pool, struct udp_transport);
+
+ /* Save pool. */
+ tp->base.pool = pool;
+
+ pj_memcpy(tp->base.obj_name, pool->obj_name, PJ_MAX_OBJ_NAME);
+
+ /* Init reference counter. */
+ status = pj_atomic_create(pool, 0, &tp->base.ref_cnt);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init lock. */
+ status = pj_lock_create_recursive_mutex(pool, pool->obj_name,
+ &tp->base.lock);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Set type. */
+ tp->base.key.type = type;
+
+ /* Remote address is left zero (except the family) */
+ tp->base.key.rem_addr.addr.sa_family = (pj_uint16_t)
+ ((type & PJSIP_TRANSPORT_IPV6) ? pj_AF_INET6() : pj_AF_INET());
+
+ /* Type name. */
+ tp->base.type_name = "UDP";
+
+ /* Transport flag */
+ tp->base.flag = pjsip_transport_get_flag_from_type(type);
+
+
+ /* Length of addressess. */
+ tp->base.addr_len = sizeof(tp->base.local_addr);
+
+ /* Init local address. */
+ status = pj_sock_getsockname(sock, &tp->base.local_addr,
+ &tp->base.addr_len);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Init remote name. */
+ if (type == PJSIP_TRANSPORT_UDP)
+ tp->base.remote_name.host = pj_str("0.0.0.0");
+ else
+ tp->base.remote_name.host = pj_str("::0");
+ tp->base.remote_name.port = 0;
+
+ /* Init direction */
+ tp->base.dir = PJSIP_TP_DIR_NONE;
+
+ /* Set endpoint. */
+ tp->base.endpt = endpt;
+
+ /* Transport manager and timer will be initialized by tpmgr */
+
+ /* Attach socket and assign name. */
+ udp_set_socket(tp, sock, a_name);
+
+ /* Register to ioqueue */
+ status = register_to_ioqueue(tp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ /* Set functions. */
+ tp->base.send_msg = &udp_send_msg;
+ tp->base.do_shutdown = &udp_shutdown;
+ tp->base.destroy = &udp_destroy;
+
+ /* This is a permanent transport, so we initialize the ref count
+ * to one so that transport manager don't destroy this transport
+ * when there's no user!
+ */
+ pj_atomic_inc(tp->base.ref_cnt);
+
+ /* Register to transport manager. */
+ tp->base.tpmgr = pjsip_endpt_get_tpmgr(endpt);
+ status = pjsip_transport_register( tp->base.tpmgr, (pjsip_transport*)tp);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+
+ /* Create rdata and put it in the array. */
+ tp->rdata_cnt = 0;
+ tp->rdata = (pjsip_rx_data**)
+ pj_pool_calloc(tp->base.pool, async_cnt,
+ sizeof(pjsip_rx_data*));
+ for (i=0; i<async_cnt; ++i) {
+ pj_pool_t *rdata_pool = pjsip_endpt_create_pool(endpt, "rtd%p",
+ PJSIP_POOL_RDATA_LEN,
+ PJSIP_POOL_RDATA_INC);
+ if (!rdata_pool) {
+ pj_atomic_set(tp->base.ref_cnt, 0);
+ pjsip_transport_destroy(&tp->base);
+ return PJ_ENOMEM;
+ }
+
+ init_rdata(tp, i, rdata_pool, NULL);
+ tp->rdata_cnt++;
+ }
+
+ /* Start reading the ioqueue. */
+ status = start_async_read(tp);
+ if (status != PJ_SUCCESS) {
+ pjsip_transport_destroy(&tp->base);
+ return status;
+ }
+
+ /* Done. */
+ if (p_transport)
+ *p_transport = &tp->base;
+
+ PJ_LOG(4,(tp->base.obj_name,
+ "SIP %s started, published address is %s%.*s%s:%d",
+ pjsip_transport_get_type_desc((pjsip_transport_type_e)tp->base.key.type),
+ ipv6_quoteb,
+ (int)tp->base.local_name.host.slen,
+ tp->base.local_name.host.ptr,
+ ipv6_quotee,
+ tp->base.local_name.port));
+
+ return PJ_SUCCESS;
+
+on_error:
+ udp_destroy((pjsip_transport*)tp);
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_udp_transport_attach( pjsip_endpoint *endpt,
+ pj_sock_t sock,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ return transport_attach(endpt, PJSIP_TRANSPORT_UDP, sock, a_name,
+ async_cnt, p_transport);
+}
+
+PJ_DEF(pj_status_t) pjsip_udp_transport_attach2( pjsip_endpoint *endpt,
+ pjsip_transport_type_e type,
+ pj_sock_t sock,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ return transport_attach(endpt, type, sock, a_name,
+ async_cnt, p_transport);
+}
+
+/*
+ * pjsip_udp_transport_start()
+ *
+ * Create a UDP socket in the specified address and start a transport.
+ */
+PJ_DEF(pj_status_t) pjsip_udp_transport_start( pjsip_endpoint *endpt,
+ const pj_sockaddr_in *local_a,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ pj_sock_t sock;
+ pj_status_t status;
+ char addr_buf[PJ_INET6_ADDRSTRLEN];
+ pjsip_host_port bound_name;
+
+ PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL);
+
+ status = create_socket(pj_AF_INET(), local_a, sizeof(pj_sockaddr_in),
+ &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (a_name == NULL) {
+ /* Address name is not specified.
+ * Build a name based on bound address.
+ */
+ status = get_published_name(sock, addr_buf, sizeof(addr_buf),
+ &bound_name);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ a_name = &bound_name;
+ }
+
+ return pjsip_udp_transport_attach( endpt, sock, a_name, async_cnt,
+ p_transport );
+}
+
+
+/*
+ * pjsip_udp_transport_start()
+ *
+ * Create a UDP socket in the specified address and start a transport.
+ */
+PJ_DEF(pj_status_t) pjsip_udp_transport_start6(pjsip_endpoint *endpt,
+ const pj_sockaddr_in6 *local_a,
+ const pjsip_host_port *a_name,
+ unsigned async_cnt,
+ pjsip_transport **p_transport)
+{
+ pj_sock_t sock;
+ pj_status_t status;
+ char addr_buf[PJ_INET6_ADDRSTRLEN];
+ pjsip_host_port bound_name;
+
+ PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL);
+
+ status = create_socket(pj_AF_INET6(), local_a, sizeof(pj_sockaddr_in6),
+ &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (a_name == NULL) {
+ /* Address name is not specified.
+ * Build a name based on bound address.
+ */
+ status = get_published_name(sock, addr_buf, sizeof(addr_buf),
+ &bound_name);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ a_name = &bound_name;
+ }
+
+ return pjsip_udp_transport_attach2(endpt, PJSIP_TRANSPORT_UDP6,
+ sock, a_name, async_cnt, p_transport);
+}
+
+/*
+ * Retrieve the internal socket handle used by the UDP transport.
+ */
+PJ_DEF(pj_sock_t) pjsip_udp_transport_get_socket(pjsip_transport *transport)
+{
+ struct udp_transport *tp;
+
+ PJ_ASSERT_RETURN(transport != NULL, PJ_INVALID_SOCKET);
+
+ tp = (struct udp_transport*) transport;
+
+ return tp->sock;
+}
+
+
+/*
+ * Temporarily pause or shutdown the transport.
+ */
+PJ_DEF(pj_status_t) pjsip_udp_transport_pause(pjsip_transport *transport,
+ unsigned option)
+{
+ struct udp_transport *tp;
+ unsigned i;
+
+ PJ_ASSERT_RETURN(transport != NULL, PJ_EINVAL);
+
+ /* Flag must be specified */
+ PJ_ASSERT_RETURN((option & 0x03) != 0, PJ_EINVAL);
+
+ tp = (struct udp_transport*) transport;
+
+ /* Transport must not have been paused */
+ PJ_ASSERT_RETURN(tp->is_paused==0, PJ_EINVALIDOP);
+
+ /* Set transport to paused first, so that when the read callback is
+ * called by pj_ioqueue_post_completion() it will not try to
+ * re-register the rdata.
+ */
+ tp->is_paused = PJ_TRUE;
+
+ /* Cancel the ioqueue operation. */
+ for (i=0; i<(unsigned)tp->rdata_cnt; ++i) {
+ pj_ioqueue_post_completion(tp->key,
+ &tp->rdata[i]->tp_info.op_key.op_key, -1);
+ }
+
+ /* Destroy the socket? */
+ if (option & PJSIP_UDP_TRANSPORT_DESTROY_SOCKET) {
+ if (tp->key) {
+ /* This implicitly closes the socket */
+ pj_ioqueue_unregister(tp->key);
+ tp->key = NULL;
+ } else {
+ /* Close socket. */
+ if (tp->sock && tp->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(tp->sock);
+ tp->sock = PJ_INVALID_SOCKET;
+ }
+ }
+ tp->sock = PJ_INVALID_SOCKET;
+
+ }
+
+ PJ_LOG(4,(tp->base.obj_name, "SIP UDP transport paused"));
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Restart transport.
+ *
+ * If option is KEEP_SOCKET, just re-activate ioqueue operation.
+ *
+ * If option is DESTROY_SOCKET:
+ * - if socket is specified, replace.
+ * - if socket is not specified, create and replace.
+ */
+PJ_DEF(pj_status_t) pjsip_udp_transport_restart(pjsip_transport *transport,
+ unsigned option,
+ pj_sock_t sock,
+ const pj_sockaddr_in *local,
+ const pjsip_host_port *a_name)
+{
+ struct udp_transport *tp;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(transport != NULL, PJ_EINVAL);
+ /* Flag must be specified */
+ PJ_ASSERT_RETURN((option & 0x03) != 0, PJ_EINVAL);
+
+ tp = (struct udp_transport*) transport;
+
+ if (option & PJSIP_UDP_TRANSPORT_DESTROY_SOCKET) {
+ char addr_buf[PJ_INET6_ADDRSTRLEN];
+ pjsip_host_port bound_name;
+
+ /* Request to recreate transport */
+
+ /* Destroy existing socket, if any. */
+ if (tp->key) {
+ /* This implicitly closes the socket */
+ pj_ioqueue_unregister(tp->key);
+ tp->key = NULL;
+ } else {
+ /* Close socket. */
+ if (tp->sock && tp->sock != PJ_INVALID_SOCKET) {
+ pj_sock_close(tp->sock);
+ tp->sock = PJ_INVALID_SOCKET;
+ }
+ }
+ tp->sock = PJ_INVALID_SOCKET;
+
+ /* Create the socket if it's not specified */
+ if (sock == PJ_INVALID_SOCKET) {
+ status = create_socket(pj_AF_INET(), local,
+ sizeof(pj_sockaddr_in), &sock);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ /* If transport published name is not specified, calculate it
+ * from the bound address.
+ */
+ if (a_name == NULL) {
+ status = get_published_name(sock, addr_buf, sizeof(addr_buf),
+ &bound_name);
+ if (status != PJ_SUCCESS) {
+ pj_sock_close(sock);
+ return status;
+ }
+
+ a_name = &bound_name;
+ }
+
+ /* Assign the socket and published address to transport. */
+ udp_set_socket(tp, sock, a_name);
+
+ } else {
+
+ /* For KEEP_SOCKET, transport must have been paused before */
+ PJ_ASSERT_RETURN(tp->is_paused, PJ_EINVALIDOP);
+
+ /* If address name is specified, update it */
+ if (a_name != NULL)
+ udp_set_pub_name(tp, a_name);
+ }
+
+ /* Re-register new or existing socket to ioqueue. */
+ status = register_to_ioqueue(tp);
+ if (status != PJ_SUCCESS) {
+ return status;
+ }
+
+ /* Restart async read operation. */
+ status = start_async_read(tp);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Everything has been set up */
+ tp->is_paused = PJ_FALSE;
+
+ PJ_LOG(4,(tp->base.obj_name,
+ "SIP UDP transport restarted, published address is %.*s:%d",
+ (int)tp->base.local_name.host.slen,
+ tp->base.local_name.host.ptr,
+ tp->base.local_name.port));
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjsip/src/pjsip/sip_transport_wrap.cpp b/pjsip/src/pjsip/sip_transport_wrap.cpp
new file mode 100644
index 0000000..80d1e88
--- /dev/null
+++ b/pjsip/src/pjsip/sip_transport_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_transport_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_transport.c"
diff --git a/pjsip/src/pjsip/sip_ua_layer.c b/pjsip/src/pjsip/sip_ua_layer.c
new file mode 100644
index 0000000..7128891
--- /dev/null
+++ b/pjsip/src/pjsip/sip_ua_layer.c
@@ -0,0 +1,978 @@
+/* $Id: sip_ua_layer.c 3664 2011-07-19 03:42:28Z 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 <pjsip/sip_ua_layer.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_dialog.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/os.h>
+#include <pj/hash.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/log.h>
+
+
+#define THIS_FILE "sip_ua_layer.c"
+
+/*
+ * Static prototypes.
+ */
+static pj_status_t mod_ua_load(pjsip_endpoint *endpt);
+static pj_status_t mod_ua_unload(void);
+static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata);
+static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata);
+static void mod_ua_on_tsx_state(pjsip_transaction*, pjsip_event*);
+
+
+extern long pjsip_dlg_lock_tls_id; /* defined in sip_dialog.c */
+
+/* This struct is used to represent list of dialog inside a dialog set.
+ * We don't want to use pjsip_dialog for this purpose, to save some
+ * memory (about 100 bytes per dialog set).
+ */
+struct dlg_set_head
+{
+ PJ_DECL_LIST_MEMBER(pjsip_dialog);
+};
+
+/* This struct represents a dialog set.
+ * This is the value that will be put in the UA's hash table.
+ */
+struct dlg_set
+{
+ /* To put this node in free dlg_set nodes in UA. */
+ PJ_DECL_LIST_MEMBER(struct dlg_set);
+
+ /* This is the buffer to store this entry in the hash table. */
+ pj_hash_entry_buf ht_entry;
+
+ /* List of dialog in this dialog set. */
+ struct dlg_set_head dlg_list;
+};
+
+
+/*
+ * Module interface.
+ */
+static struct user_agent
+{
+ pjsip_module mod;
+ pj_pool_t *pool;
+ pjsip_endpoint *endpt;
+ pj_mutex_t *mutex;
+ pj_hash_table_t *dlg_table;
+ pjsip_ua_init_param param;
+ struct dlg_set free_dlgset_nodes;
+
+} mod_ua =
+{
+ {
+ NULL, NULL, /* prev, next. */
+ { "mod-ua", 6 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */
+ &mod_ua_load, /* load() */
+ NULL, /* start() */
+ NULL, /* stop() */
+ &mod_ua_unload, /* unload() */
+ &mod_ua_on_rx_request, /* on_rx_request() */
+ &mod_ua_on_rx_response, /* on_rx_response() */
+ NULL, /* on_tx_request. */
+ NULL, /* on_tx_response() */
+ &mod_ua_on_tsx_state, /* on_tsx_state() */
+ }
+};
+
+/*
+ * mod_ua_load()
+ *
+ * Called when module is being loaded by endpoint.
+ */
+static pj_status_t mod_ua_load(pjsip_endpoint *endpt)
+{
+ pj_status_t status;
+
+ /* Initialize the user agent. */
+ mod_ua.endpt = endpt;
+ mod_ua.pool = pjsip_endpt_create_pool( endpt, "ua%p", PJSIP_POOL_LEN_UA,
+ PJSIP_POOL_INC_UA);
+ if (mod_ua.pool == NULL)
+ return PJ_ENOMEM;
+
+ status = pj_mutex_create_recursive(mod_ua.pool, " ua%p", &mod_ua.mutex);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ mod_ua.dlg_table = pj_hash_create(mod_ua.pool, PJSIP_MAX_DIALOG_COUNT);
+ if (mod_ua.dlg_table == NULL)
+ return PJ_ENOMEM;
+
+ pj_list_init(&mod_ua.free_dlgset_nodes);
+
+ /* Initialize dialog lock. */
+ status = pj_thread_local_alloc(&pjsip_dlg_lock_tls_id);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL);
+
+ return PJ_SUCCESS;
+
+}
+
+/*
+ * mod_ua_unload()
+ *
+ * Called when module is being unloaded.
+ */
+static pj_status_t mod_ua_unload(void)
+{
+ pj_thread_local_free(pjsip_dlg_lock_tls_id);
+ pj_mutex_destroy(mod_ua.mutex);
+
+ /* Release pool */
+ if (mod_ua.pool) {
+ pjsip_endpt_release_pool( mod_ua.endpt, mod_ua.pool );
+ }
+ return PJ_SUCCESS;
+}
+
+/*
+ * mod_ua_on_tsx_stats()
+ *
+ * Called on changed on transaction state.
+ */
+static void mod_ua_on_tsx_state( pjsip_transaction *tsx, pjsip_event *e)
+{
+ pjsip_dialog *dlg;
+
+ /* Get the dialog where this transaction belongs. */
+ dlg = (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
+
+ /* If dialog instance has gone, it could mean that the dialog
+ * may has been destroyed.
+ */
+ if (dlg == NULL)
+ return;
+
+ /* Hand over the event to the dialog. */
+ pjsip_dlg_on_tsx_state(dlg, tsx, e);
+}
+
+
+/*
+ * Init user agent module and register it to the endpoint.
+ */
+PJ_DEF(pj_status_t) pjsip_ua_init_module( pjsip_endpoint *endpt,
+ const pjsip_ua_init_param *prm)
+{
+ pj_status_t status;
+
+ /* Check if module already registered. */
+ PJ_ASSERT_RETURN(mod_ua.mod.id == -1, PJ_EINVALIDOP);
+
+ /* Copy param, if exists. */
+ if (prm)
+ pj_memcpy(&mod_ua.param, prm, sizeof(pjsip_ua_init_param));
+
+ /* Register the module. */
+ status = pjsip_endpt_register_module(endpt, &mod_ua.mod);
+
+ return status;
+}
+
+/*
+ * Get the instance of the user agent.
+ *
+ */
+PJ_DEF(pjsip_user_agent*) pjsip_ua_instance(void)
+{
+ return &mod_ua.mod;
+}
+
+
+/*
+ * Get the endpoint where this UA is currently registered.
+ */
+PJ_DEF(pjsip_endpoint*) pjsip_ua_get_endpt(pjsip_user_agent *ua)
+{
+ PJ_UNUSED_ARG(ua);
+ pj_assert(ua == &mod_ua.mod);
+ return mod_ua.endpt;
+}
+
+
+/*
+ * Destroy the user agent layer.
+ */
+PJ_DEF(pj_status_t) pjsip_ua_destroy(void)
+{
+ /* Check if module already destroyed. */
+ PJ_ASSERT_RETURN(mod_ua.mod.id != -1, PJ_EINVALIDOP);
+
+ return pjsip_endpt_unregister_module(mod_ua.endpt, &mod_ua.mod);
+}
+
+
+
+/*
+ * Create key to identify dialog set.
+ */
+/*
+PJ_DEF(void) pjsip_ua_create_dlg_set_key( pj_pool_t *pool,
+ pj_str_t *set_key,
+ const pj_str_t *call_id,
+ const pj_str_t *local_tag)
+{
+ PJ_ASSERT_ON_FAIL(pool && set_key && call_id && local_tag, return;);
+
+ set_key->slen = call_id->slen + local_tag->slen + 1;
+ set_key->ptr = (char*) pj_pool_alloc(pool, set_key->slen);
+ pj_assert(set_key->ptr != NULL);
+
+ pj_memcpy(set_key->ptr, call_id->ptr, call_id->slen);
+ set_key->ptr[call_id->slen] = '$';
+ pj_memcpy(set_key->ptr + call_id->slen + 1,
+ local_tag->ptr, local_tag->slen);
+}
+*/
+
+/*
+ * Acquire one dlg_set node to be put in the hash table.
+ * This will first look in the free nodes list, then allocate
+ * a new one from UA's pool when one is not available.
+ */
+static struct dlg_set *alloc_dlgset_node(void)
+{
+ struct dlg_set *set;
+
+ if (!pj_list_empty(&mod_ua.free_dlgset_nodes)) {
+ set = mod_ua.free_dlgset_nodes.next;
+ pj_list_erase(set);
+ return set;
+ } else {
+ set = PJ_POOL_ALLOC_T(mod_ua.pool, struct dlg_set);
+ return set;
+ }
+}
+
+/*
+ * Register new dialog. Called by pjsip_dlg_create_uac() and
+ * pjsip_dlg_create_uas();
+ */
+PJ_DEF(pj_status_t) pjsip_ua_register_dlg( pjsip_user_agent *ua,
+ pjsip_dialog *dlg )
+{
+ /* Sanity check. */
+ PJ_ASSERT_RETURN(ua && dlg, PJ_EINVAL);
+
+ /* For all dialogs, local tag (inc hash) must has been initialized. */
+ PJ_ASSERT_RETURN(dlg->local.info && dlg->local.info->tag.slen &&
+ dlg->local.tag_hval != 0, PJ_EBUG);
+
+ /* For UAS dialog, remote tag (inc hash) must have been initialized. */
+ //PJ_ASSERT_RETURN(dlg->role==PJSIP_ROLE_UAC ||
+ // (dlg->role==PJSIP_ROLE_UAS && dlg->remote.info->tag.slen
+ // && dlg->remote.tag_hval != 0), PJ_EBUG);
+
+ /* Lock the user agent. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* For UAC, check if there is existing dialog in the same set. */
+ if (dlg->role == PJSIP_ROLE_UAC) {
+ struct dlg_set *dlg_set;
+
+ dlg_set = (struct dlg_set*)
+ pj_hash_get( mod_ua.dlg_table, dlg->local.info->tag.ptr,
+ dlg->local.info->tag.slen,
+ &dlg->local.tag_hval);
+
+ if (dlg_set) {
+ /* This is NOT the first dialog in the dialog set.
+ * Just add this dialog in the list.
+ */
+ pj_assert(dlg_set->dlg_list.next != (void*)&dlg_set->dlg_list);
+ pj_list_push_back(&dlg_set->dlg_list, dlg);
+
+ dlg->dlg_set = dlg_set;
+
+ } else {
+ /* This is the first dialog in the dialog set.
+ * Create the dialog set and add this dialog to it.
+ */
+ dlg_set = alloc_dlgset_node();
+ pj_list_init(&dlg_set->dlg_list);
+ pj_list_push_back(&dlg_set->dlg_list, dlg);
+
+ dlg->dlg_set = dlg_set;
+
+ /* Register the dialog set in the hash table. */
+ pj_hash_set_np(mod_ua.dlg_table,
+ dlg->local.info->tag.ptr, dlg->local.info->tag.slen,
+ dlg->local.tag_hval, dlg_set->ht_entry, dlg_set);
+ }
+
+ } else {
+ /* For UAS, create the dialog set with a single dialog as member. */
+ struct dlg_set *dlg_set;
+
+ dlg_set = alloc_dlgset_node();
+ pj_list_init(&dlg_set->dlg_list);
+ pj_list_push_back(&dlg_set->dlg_list, dlg);
+
+ dlg->dlg_set = dlg_set;
+
+ pj_hash_set_np(mod_ua.dlg_table,
+ dlg->local.info->tag.ptr, dlg->local.info->tag.slen,
+ dlg->local.tag_hval, dlg_set->ht_entry, dlg_set);
+ }
+
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_ua_unregister_dlg( pjsip_user_agent *ua,
+ pjsip_dialog *dlg )
+{
+ struct dlg_set *dlg_set;
+ pjsip_dialog *d;
+
+ /* Sanity-check arguments. */
+ PJ_ASSERT_RETURN(ua && dlg, PJ_EINVAL);
+
+ /* Check that dialog has been registered. */
+ PJ_ASSERT_RETURN(dlg->dlg_set, PJ_EINVALIDOP);
+
+ /* Lock user agent. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* Find this dialog from the dialog set. */
+ dlg_set = (struct dlg_set*) dlg->dlg_set;
+ d = dlg_set->dlg_list.next;
+ while (d != (pjsip_dialog*)&dlg_set->dlg_list && d != dlg) {
+ d = d->next;
+ }
+
+ if (d != dlg) {
+ pj_assert(!"Dialog is not registered!");
+ pj_mutex_unlock(mod_ua.mutex);
+ return PJ_EINVALIDOP;
+ }
+
+ /* Remove this dialog from the list. */
+ pj_list_erase(dlg);
+
+ /* If dialog list is empty, remove the dialog set from the hash table. */
+ if (pj_list_empty(&dlg_set->dlg_list)) {
+ pj_hash_set(NULL, mod_ua.dlg_table, dlg->local.info->tag.ptr,
+ dlg->local.info->tag.slen, dlg->local.tag_hval, NULL);
+
+ /* Return dlg_set to free nodes. */
+ pj_list_push_back(&mod_ua.free_dlgset_nodes, dlg_set);
+ }
+
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Done. */
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pjsip_dialog*) pjsip_rdata_get_dlg( pjsip_rx_data *rdata )
+{
+ return (pjsip_dialog*) rdata->endpt_info.mod_data[mod_ua.mod.id];
+}
+
+PJ_DEF(pjsip_dialog*) pjsip_tsx_get_dlg( pjsip_transaction *tsx )
+{
+ return (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
+}
+
+
+/*
+ * Retrieve the current number of dialog-set currently registered
+ * in the hash table.
+ */
+PJ_DEF(unsigned) pjsip_ua_get_dlg_set_count(void)
+{
+ unsigned count;
+
+ PJ_ASSERT_RETURN(mod_ua.endpt, 0);
+
+ pj_mutex_lock(mod_ua.mutex);
+ count = pj_hash_count(mod_ua.dlg_table);
+ pj_mutex_unlock(mod_ua.mutex);
+
+ return count;
+}
+
+
+/*
+ * Find a dialog.
+ */
+PJ_DEF(pjsip_dialog*) pjsip_ua_find_dialog(const pj_str_t *call_id,
+ const pj_str_t *local_tag,
+ const pj_str_t *remote_tag,
+ pj_bool_t lock_dialog)
+{
+ struct dlg_set *dlg_set;
+ pjsip_dialog *dlg;
+
+ PJ_ASSERT_RETURN(call_id && local_tag && remote_tag, NULL);
+
+ /* Lock user agent. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* Lookup the dialog set. */
+ dlg_set = (struct dlg_set*)
+ pj_hash_get(mod_ua.dlg_table, local_tag->ptr, local_tag->slen,
+ NULL);
+ if (dlg_set == NULL) {
+ /* Not found */
+ pj_mutex_unlock(mod_ua.mutex);
+ return NULL;
+ }
+
+ /* Dialog set is found, now find the matching dialog based on the
+ * remote tag.
+ */
+ dlg = dlg_set->dlg_list.next;
+ while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
+ if (pj_strcmp(&dlg->remote.info->tag, remote_tag) == 0)
+ break;
+ dlg = dlg->next;
+ }
+
+ if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
+ /* Not found */
+ pj_mutex_unlock(mod_ua.mutex);
+ return NULL;
+ }
+
+ /* Dialog has been found. It SHOULD have the right Call-ID!! */
+ PJ_ASSERT_ON_FAIL(pj_strcmp(&dlg->call_id->id, call_id)==0,
+ {pj_mutex_unlock(mod_ua.mutex); return NULL;});
+
+ if (lock_dialog) {
+ if (pjsip_dlg_try_inc_lock(dlg) != PJ_SUCCESS) {
+
+ /*
+ * Unable to acquire dialog's lock while holding the user
+ * agent's mutex. Release the UA mutex before retrying once
+ * more.
+ *
+ * THIS MAY CAUSE RACE CONDITION!
+ */
+
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+ /* Lock dialog */
+ pjsip_dlg_inc_lock(dlg);
+
+ } else {
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+ }
+
+ } else {
+ /* Unlock user agent. */
+ pj_mutex_unlock(mod_ua.mutex);
+ }
+
+ return dlg;
+}
+
+
+/*
+ * Find the first dialog in dialog set in hash table for an incoming message.
+ */
+static struct dlg_set *find_dlg_set_for_msg( pjsip_rx_data *rdata )
+{
+ /* CANCEL message doesn't have To tag, so we must lookup the dialog
+ * by finding the INVITE UAS transaction being cancelled.
+ */
+ if (rdata->msg_info.cseq->method.id == PJSIP_CANCEL_METHOD) {
+
+ pjsip_dialog *dlg;
+
+ /* Create key for the rdata, but this time, use INVITE as the
+ * method.
+ */
+ pj_str_t key;
+ pjsip_role_e role;
+ pjsip_transaction *tsx;
+
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG)
+ role = PJSIP_ROLE_UAS;
+ else
+ role = PJSIP_ROLE_UAC;
+
+ pjsip_tsx_create_key(rdata->tp_info.pool, &key, role,
+ pjsip_get_invite_method(), rdata);
+
+ /* Lookup the INVITE transaction */
+ tsx = pjsip_tsx_layer_find_tsx(&key, PJ_TRUE);
+
+ /* We should find the dialog attached to the INVITE transaction */
+ if (tsx) {
+ dlg = (pjsip_dialog*) tsx->mod_data[mod_ua.mod.id];
+ pj_mutex_unlock(tsx->mutex);
+
+ /* Dlg may be NULL on some extreme condition
+ * (e.g. during debugging where initially there is a dialog)
+ */
+ return dlg ? (struct dlg_set*) dlg->dlg_set : NULL;
+
+ } else {
+ return NULL;
+ }
+
+
+ } else {
+ pj_str_t *tag;
+ struct dlg_set *dlg_set;
+
+ if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG)
+ tag = &rdata->msg_info.to->tag;
+ else
+ tag = &rdata->msg_info.from->tag;
+
+ /* Lookup the dialog set. */
+ dlg_set = (struct dlg_set*)
+ pj_hash_get(mod_ua.dlg_table, tag->ptr, tag->slen, NULL);
+ return dlg_set;
+ }
+}
+
+/* On received requests. */
+static pj_bool_t mod_ua_on_rx_request(pjsip_rx_data *rdata)
+{
+ struct dlg_set *dlg_set;
+ pj_str_t *from_tag;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ /* Optimized path: bail out early if request is not CANCEL and it doesn't
+ * have To tag
+ */
+ if (rdata->msg_info.to->tag.slen == 0 &&
+ rdata->msg_info.msg->line.req.method.id != PJSIP_CANCEL_METHOD)
+ {
+ return PJ_FALSE;
+ }
+
+ /* Incoming REGISTER may have tags in it */
+ if (rdata->msg_info.msg->line.req.method.id == PJSIP_REGISTER_METHOD)
+ return PJ_FALSE;
+
+retry_on_deadlock:
+
+ /* Lock user agent before looking up the dialog hash table. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* Lookup the dialog set, based on the To tag header. */
+ dlg_set = find_dlg_set_for_msg(rdata);
+
+ /* If dialog is not found, respond with 481 (Call/Transaction
+ * Does Not Exist).
+ */
+ if (dlg_set == NULL) {
+ /* Unable to find dialog. */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ PJ_LOG(5,(THIS_FILE,
+ "Unable to find dialogset for %s, answering with 481",
+ pjsip_rx_data_get_info(rdata)));
+
+ /* Respond with 481 . */
+ pjsip_endpt_respond_stateless( mod_ua.endpt, rdata, 481, NULL,
+ NULL, NULL );
+ }
+ return PJ_TRUE;
+ }
+
+ /* Dialog set has been found.
+ * Find the dialog in the dialog set based on the content of the remote
+ * tag.
+ */
+ from_tag = &rdata->msg_info.from->tag;
+ dlg = dlg_set->dlg_list.next;
+ while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
+
+ if (pj_strcmp(&dlg->remote.info->tag, from_tag) == 0)
+ break;
+
+ dlg = dlg->next;
+ }
+
+ /* Dialog may not be found, e.g. in this case:
+ * - UAC sends SUBSCRIBE, then UAS sends NOTIFY before answering
+ * SUBSCRIBE request with 2xx.
+ *
+ * In this case, we can accept the request ONLY when the original
+ * dialog still has empty To tag.
+ */
+ if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
+
+ pjsip_dialog *first_dlg = dlg_set->dlg_list.next;
+
+ if (first_dlg->remote.info->tag.slen != 0) {
+ /* Not found. Mulfunction UAC? */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ if (rdata->msg_info.msg->line.req.method.id != PJSIP_ACK_METHOD) {
+ PJ_LOG(5,(THIS_FILE,
+ "Unable to find dialog for %s, answering with 481",
+ pjsip_rx_data_get_info(rdata)));
+
+ pjsip_endpt_respond_stateless(mod_ua.endpt, rdata,
+ PJSIP_SC_CALL_TSX_DOES_NOT_EXIST,
+ NULL, NULL, NULL);
+ } else {
+ PJ_LOG(5,(THIS_FILE,
+ "Unable to find dialog for %s",
+ pjsip_rx_data_get_info(rdata)));
+ }
+ return PJ_TRUE;
+ }
+
+ dlg = first_dlg;
+ }
+
+ /* Mark the dialog id of the request. */
+ rdata->endpt_info.mod_data[mod_ua.mod.id] = dlg;
+
+ /* Try to lock the dialog */
+ PJ_LOG(6,(dlg->obj_name, "UA layer acquiring dialog lock for request"));
+ status = pjsip_dlg_try_inc_lock(dlg);
+ if (status != PJ_SUCCESS) {
+ /* Failed to acquire dialog mutex immediately, this could be
+ * because of deadlock. Release UA mutex, yield, and retry
+ * the whole thing once again.
+ */
+ pj_mutex_unlock(mod_ua.mutex);
+ pj_thread_sleep(0);
+ goto retry_on_deadlock;
+ }
+
+ /* Done with processing in UA layer, release lock */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Pass to dialog. */
+ pjsip_dlg_on_rx_request(dlg, rdata);
+
+ /* Unlock the dialog. This may destroy the dialog */
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Report as handled. */
+ return PJ_TRUE;
+}
+
+
+/* On rx response notification.
+ */
+static pj_bool_t mod_ua_on_rx_response(pjsip_rx_data *rdata)
+{
+ pjsip_transaction *tsx;
+ struct dlg_set *dlg_set;
+ pjsip_dialog *dlg;
+ pj_status_t status;
+
+ /*
+ * Find the dialog instance for the response.
+ * All outgoing dialog requests are sent statefully, which means
+ * there will be an UAC transaction associated with this response,
+ * and the dialog instance will be recorded in that transaction.
+ *
+ * But even when transaction is found, there is possibility that
+ * the response is a forked response.
+ */
+
+retry_on_deadlock:
+
+ dlg = NULL;
+
+ /* Lock user agent dlg table before we're doing anything. */
+ pj_mutex_lock(mod_ua.mutex);
+
+ /* Check if transaction is present. */
+ tsx = pjsip_rdata_get_tsx(rdata);
+ if (tsx) {
+ /* Check if dialog is present in the transaction. */
+ dlg = pjsip_tsx_get_dlg(tsx);
+ if (!dlg) {
+ /* Unlock dialog hash table. */
+ pj_mutex_unlock(mod_ua.mutex);
+ return PJ_FALSE;
+ }
+
+ /* Get the dialog set. */
+ dlg_set = (struct dlg_set*) dlg->dlg_set;
+
+ /* Even if transaction is found and (candidate) dialog has been
+ * identified, it's possible that the request has forked.
+ */
+
+ } else {
+ /* Transaction is not present.
+ * Check if this is a 2xx/OK response to INVITE, which in this
+ * case the response will be handled directly by the
+ * dialog.
+ */
+ pjsip_cseq_hdr *cseq_hdr = rdata->msg_info.cseq;
+
+ if (cseq_hdr->method.id != PJSIP_INVITE_METHOD ||
+ rdata->msg_info.msg->line.status.code / 100 != 2)
+ {
+ /* Not a 2xx response to INVITE.
+ * This must be some stateless response sent by other modules,
+ * or a very late response.
+ */
+ /* Unlock dialog hash table. */
+ pj_mutex_unlock(mod_ua.mutex);
+ return PJ_FALSE;
+ }
+
+
+ /* Get the dialog set. */
+ dlg_set = (struct dlg_set*)
+ pj_hash_get(mod_ua.dlg_table,
+ rdata->msg_info.from->tag.ptr,
+ rdata->msg_info.from->tag.slen,
+ NULL);
+
+ if (!dlg_set) {
+ /* Unlock dialog hash table. */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Strayed 2xx response!! */
+ PJ_LOG(4,(THIS_FILE,
+ "Received strayed 2xx response (no dialog is found)"
+ " from %s:%d: %s",
+ rdata->pkt_info.src_name, rdata->pkt_info.src_port,
+ pjsip_rx_data_get_info(rdata)));
+
+ return PJ_TRUE;
+ }
+ }
+
+ /* At this point, we must have the dialog set, and the dialog set
+ * must have a dialog in the list.
+ */
+ pj_assert(dlg_set && !pj_list_empty(&dlg_set->dlg_list));
+
+ /* Check for forked response.
+ * Request will fork only for the initial INVITE request.
+ */
+
+ //This doesn't work when there is authentication challenge, since
+ //first_cseq evaluation will yield false.
+ //if (rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD &&
+ // rdata->msg_info.cseq->cseq == dlg_set->dlg_list.next->local.first_cseq)
+
+ if (rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) {
+
+ int st_code = rdata->msg_info.msg->line.status.code;
+ pj_str_t *to_tag = &rdata->msg_info.to->tag;
+
+ dlg = dlg_set->dlg_list.next;
+
+ while (dlg != (pjsip_dialog*)&dlg_set->dlg_list) {
+
+ /* If there is dialog with no remote tag (i.e. dialog has not
+ * been established yet), then send this response to that
+ * dialog.
+ */
+ if (dlg->remote.info->tag.slen == 0)
+ break;
+
+ /* Otherwise find the one with matching To tag. */
+ if (pj_strcmp(to_tag, &dlg->remote.info->tag) == 0)
+ break;
+
+ dlg = dlg->next;
+ }
+
+ /* If no dialog with matching remote tag is found, this must be
+ * a forked response. Respond to this ONLY when response is non-100
+ * provisional response OR a 2xx response.
+ */
+ if (dlg == (pjsip_dialog*)&dlg_set->dlg_list &&
+ ((st_code/100==1 && st_code!=100) || st_code/100==2))
+ {
+
+ PJ_LOG(5,(THIS_FILE,
+ "Received forked %s for existing dialog %s",
+ pjsip_rx_data_get_info(rdata),
+ dlg_set->dlg_list.next->obj_name));
+
+ /* Report to application about forked condition.
+ * Application can either create a dialog or ignore the response.
+ */
+ if (mod_ua.param.on_dlg_forked) {
+ dlg = (*mod_ua.param.on_dlg_forked)(dlg_set->dlg_list.next,
+ rdata);
+ if (dlg == NULL) {
+ pj_mutex_unlock(mod_ua.mutex);
+ return PJ_TRUE;
+ }
+ } else {
+ dlg = dlg_set->dlg_list.next;
+
+ PJ_LOG(4,(THIS_FILE,
+ "Unhandled forked %s from %s:%d, response will be "
+ "handed over to the first dialog",
+ pjsip_rx_data_get_info(rdata),
+ rdata->pkt_info.src_name, rdata->pkt_info.src_port));
+ }
+
+ } else if (dlg == (pjsip_dialog*)&dlg_set->dlg_list) {
+
+ /* For 100 or non-2xx response which has different To tag,
+ * pass the response to the first dialog.
+ */
+
+ dlg = dlg_set->dlg_list.next;
+
+ }
+
+ } else {
+ /* Either this is a non-INVITE response, or subsequent INVITE
+ * within dialog. The dialog should have been identified when
+ * the transaction was found.
+ */
+ pj_assert(tsx != NULL);
+ pj_assert(dlg != NULL);
+ }
+
+ /* The dialog must have been found. */
+ pj_assert(dlg != NULL);
+
+ /* Put the dialog instance in the rdata. */
+ rdata->endpt_info.mod_data[mod_ua.mod.id] = dlg;
+
+ /* Attempt to acquire lock to the dialog. */
+ PJ_LOG(6,(dlg->obj_name, "UA layer acquiring dialog lock for response"));
+ status = pjsip_dlg_try_inc_lock(dlg);
+ if (status != PJ_SUCCESS) {
+ /* Failed to acquire dialog mutex. This could indicate a deadlock
+ * situation, and for safety, try to avoid deadlock by releasing
+ * UA mutex, yield, and retry the whole processing once again.
+ */
+ pj_mutex_unlock(mod_ua.mutex);
+ pj_thread_sleep(0);
+ goto retry_on_deadlock;
+ }
+
+ /* We're done with processing in the UA layer, we can release the mutex */
+ pj_mutex_unlock(mod_ua.mutex);
+
+ /* Pass the response to the dialog. */
+ pjsip_dlg_on_rx_response(dlg, rdata);
+
+ /* Unlock the dialog. This may destroy the dialog. */
+ pjsip_dlg_dec_lock(dlg);
+
+ /* Done. */
+ return PJ_TRUE;
+}
+
+
+#if PJ_LOG_MAX_LEVEL >= 3
+static void print_dialog( const char *title,
+ pjsip_dialog *dlg, char *buf, pj_size_t size)
+{
+ int len;
+ char userinfo[128];
+
+ len = pjsip_hdr_print_on(dlg->remote.info, userinfo, sizeof(userinfo));
+ if (len < 0)
+ pj_ansi_strcpy(userinfo, "<--uri too long-->");
+ else
+ userinfo[len] = '\0';
+
+ len = pj_ansi_snprintf(buf, size, "%s[%s] %s",
+ title,
+ (dlg->state==PJSIP_DIALOG_STATE_NULL ? " - " :
+ "est"),
+ userinfo);
+ if (len < 1 || len >= (int)size) {
+ pj_ansi_strcpy(buf, "<--uri too long-->");
+ } else
+ buf[len] = '\0';
+}
+#endif
+
+/*
+ * Dump user agent contents (e.g. all dialogs).
+ */
+PJ_DEF(void) pjsip_ua_dump(pj_bool_t detail)
+{
+#if PJ_LOG_MAX_LEVEL >= 3
+ pj_hash_iterator_t itbuf, *it;
+ char dlginfo[128];
+
+ pj_mutex_lock(mod_ua.mutex);
+
+ PJ_LOG(3, (THIS_FILE, "Number of dialog sets: %u",
+ pj_hash_count(mod_ua.dlg_table)));
+
+ if (detail && pj_hash_count(mod_ua.dlg_table)) {
+ PJ_LOG(3, (THIS_FILE, "Dumping dialog sets:"));
+ it = pj_hash_first(mod_ua.dlg_table, &itbuf);
+ for (; it != NULL; it = pj_hash_next(mod_ua.dlg_table, it)) {
+ struct dlg_set *dlg_set;
+ pjsip_dialog *dlg;
+ const char *title;
+
+ dlg_set = (struct dlg_set*) pj_hash_this(mod_ua.dlg_table, it);
+ if (!dlg_set || pj_list_empty(&dlg_set->dlg_list)) continue;
+
+ /* First dialog in dialog set. */
+ dlg = dlg_set->dlg_list.next;
+ if (dlg->role == PJSIP_ROLE_UAC)
+ title = " [out] ";
+ else
+ title = " [in] ";
+
+ print_dialog(title, dlg, dlginfo, sizeof(dlginfo));
+ PJ_LOG(3,(THIS_FILE, "%s", dlginfo));
+
+ /* Next dialog in dialog set (forked) */
+ dlg = dlg->next;
+ while (dlg != (pjsip_dialog*) &dlg_set->dlg_list) {
+ print_dialog(" [forked] ", dlg, dlginfo, sizeof(dlginfo));
+ dlg = dlg->next;
+ }
+ }
+ }
+
+ pj_mutex_unlock(mod_ua.mutex);
+#endif
+}
+
diff --git a/pjsip/src/pjsip/sip_uri.c b/pjsip/src/pjsip/sip_uri.c
new file mode 100644
index 0000000..fba163c
--- /dev/null
+++ b/pjsip/src/pjsip/sip_uri.c
@@ -0,0 +1,729 @@
+/* $Id: sip_uri.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 <pjsip/sip_uri.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_parser.h>
+#include <pjsip/print_util.h>
+#include <pjsip/sip_errno.h>
+#include <pjlib-util/string.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/assert.h>
+
+/*
+ * Generic parameter manipulation.
+ */
+PJ_DEF(pjsip_param*) pjsip_param_find( const pjsip_param *param_list,
+ const pj_str_t *name )
+{
+ pjsip_param *p = (pjsip_param*)param_list->next;
+ while (p != param_list) {
+ if (pj_stricmp(&p->name, name)==0)
+ return p;
+ p = p->next;
+ }
+ return NULL;
+}
+
+PJ_DEF(int) pjsip_param_cmp( const pjsip_param *param_list1,
+ const pjsip_param *param_list2,
+ pj_bool_t ig_nf)
+{
+ const pjsip_param *p1;
+
+ if ((ig_nf & 1)==0 && pj_list_size(param_list1)!=pj_list_size(param_list2))
+ return 1;
+
+ p1 = param_list1->next;
+ while (p1 != param_list1) {
+ const pjsip_param *p2;
+ p2 = pjsip_param_find(param_list2, &p1->name);
+ if (p2 ) {
+ int rc = pj_stricmp(&p1->value, &p2->value);
+ if (rc != 0)
+ return rc;
+ } else if ((ig_nf & 1)==0)
+ return 1;
+
+ p1 = p1->next;
+ }
+
+ return 0;
+}
+
+PJ_DEF(void) pjsip_param_clone( pj_pool_t *pool, pjsip_param *dst_list,
+ const pjsip_param *src_list)
+{
+ const pjsip_param *p = src_list->next;
+
+ pj_list_init(dst_list);
+ while (p && p != src_list) {
+ pjsip_param *new_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ pj_strdup(pool, &new_param->name, &p->name);
+ pj_strdup(pool, &new_param->value, &p->value);
+ pj_list_insert_before(dst_list, new_param);
+ p = p->next;
+ }
+}
+
+
+PJ_DEF(void) pjsip_param_shallow_clone( pj_pool_t *pool,
+ pjsip_param *dst_list,
+ const pjsip_param *src_list)
+{
+ const pjsip_param *p = src_list->next;
+
+ pj_list_init(dst_list);
+ while (p != src_list) {
+ pjsip_param *new_param = PJ_POOL_ALLOC_T(pool, pjsip_param);
+ new_param->name = p->name;
+ new_param->value = p->value;
+ pj_list_insert_before(dst_list, new_param);
+ p = p->next;
+ }
+}
+
+PJ_DEF(pj_ssize_t) pjsip_param_print_on( const pjsip_param *param_list,
+ char *buf, pj_size_t size,
+ const pj_cis_t *pname_spec,
+ const pj_cis_t *pvalue_spec,
+ int sep)
+{
+ const pjsip_param *p;
+ char *startbuf;
+ char *endbuf;
+ int printed;
+
+ p = param_list->next;
+ if (p == NULL || p == param_list)
+ return 0;
+
+ startbuf = buf;
+ endbuf = buf + size;
+
+ PJ_UNUSED_ARG(pname_spec);
+
+ do {
+ *buf++ = (char)sep;
+ copy_advance_escape(buf, p->name, (*pname_spec));
+ if (p->value.slen) {
+ *buf++ = '=';
+ if (*p->value.ptr == '"')
+ copy_advance(buf, p->value);
+ else
+ copy_advance_escape(buf, p->value, (*pvalue_spec));
+ }
+ p = p->next;
+ if (sep == '?') sep = '&';
+ } while (p != param_list);
+
+ return buf-startbuf;
+}
+
+
+/*
+ * URI stuffs
+ */
+#define IS_SIPS(url) ((url)->vptr==&sips_url_vptr)
+
+static const pj_str_t *pjsip_url_get_scheme( const pjsip_sip_uri* );
+static const pj_str_t *pjsips_url_get_scheme( const pjsip_sip_uri* );
+static const pj_str_t *pjsip_name_addr_get_scheme( const pjsip_name_addr * );
+static void *pjsip_get_uri( pjsip_uri *uri );
+static void *pjsip_name_addr_get_uri( pjsip_name_addr *name );
+
+static pj_str_t sip_str = { "sip", 3 };
+static pj_str_t sips_str = { "sips", 4 };
+
+static pjsip_name_addr* pjsip_name_addr_clone( pj_pool_t *pool,
+ const pjsip_name_addr *rhs);
+static pj_ssize_t pjsip_name_addr_print(pjsip_uri_context_e context,
+ const pjsip_name_addr *name,
+ char *buf, pj_size_t size);
+static int pjsip_name_addr_compare( pjsip_uri_context_e context,
+ const pjsip_name_addr *naddr1,
+ const pjsip_name_addr *naddr2);
+static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context,
+ const pjsip_sip_uri *url,
+ char *buf, pj_size_t size);
+static int pjsip_url_compare( pjsip_uri_context_e context,
+ const pjsip_sip_uri *url1,
+ const pjsip_sip_uri *url2);
+static pjsip_sip_uri* pjsip_url_clone(pj_pool_t *pool,
+ const pjsip_sip_uri *rhs);
+
+typedef const pj_str_t* (*P_GET_SCHEME)(const void*);
+typedef void* (*P_GET_URI)(void*);
+typedef pj_ssize_t (*P_PRINT_URI)(pjsip_uri_context_e,const void *,
+ char*,pj_size_t);
+typedef int (*P_CMP_URI)(pjsip_uri_context_e, const void*,
+ const void*);
+typedef void* (*P_CLONE)(pj_pool_t*, const void*);
+
+
+static pjsip_uri_vptr sip_url_vptr =
+{
+ (P_GET_SCHEME) &pjsip_url_get_scheme,
+ (P_GET_URI) &pjsip_get_uri,
+ (P_PRINT_URI) &pjsip_url_print,
+ (P_CMP_URI) &pjsip_url_compare,
+ (P_CLONE) &pjsip_url_clone
+};
+
+static pjsip_uri_vptr sips_url_vptr =
+{
+ (P_GET_SCHEME) &pjsips_url_get_scheme,
+ (P_GET_URI) &pjsip_get_uri,
+ (P_PRINT_URI) &pjsip_url_print,
+ (P_CMP_URI) &pjsip_url_compare,
+ (P_CLONE) &pjsip_url_clone
+};
+
+static pjsip_uri_vptr name_addr_vptr =
+{
+ (P_GET_SCHEME) &pjsip_name_addr_get_scheme,
+ (P_GET_URI) &pjsip_name_addr_get_uri,
+ (P_PRINT_URI) &pjsip_name_addr_print,
+ (P_CMP_URI) &pjsip_name_addr_compare,
+ (P_CLONE) &pjsip_name_addr_clone
+};
+
+static const pj_str_t *pjsip_url_get_scheme(const pjsip_sip_uri *url)
+{
+ PJ_UNUSED_ARG(url);
+ return &sip_str;
+}
+
+static const pj_str_t *pjsips_url_get_scheme(const pjsip_sip_uri *url)
+{
+ PJ_UNUSED_ARG(url);
+ return &sips_str;
+}
+
+static void *pjsip_get_uri( pjsip_uri *uri )
+{
+ return uri;
+}
+
+static void *pjsip_name_addr_get_uri( pjsip_name_addr *name )
+{
+ return pjsip_uri_get_uri(name->uri);
+}
+
+PJ_DEF(void) pjsip_sip_uri_set_secure( pjsip_sip_uri *url,
+ pj_bool_t secure )
+{
+ url->vptr = secure ? &sips_url_vptr : &sip_url_vptr;
+}
+
+PJ_DEF(void) pjsip_sip_uri_init(pjsip_sip_uri *url, pj_bool_t secure)
+{
+ pj_bzero(url, sizeof(*url));
+ url->ttl_param = -1;
+ pjsip_sip_uri_set_secure(url, secure);
+ pj_list_init(&url->other_param);
+ pj_list_init(&url->header_param);
+}
+
+PJ_DEF(pjsip_sip_uri*) pjsip_sip_uri_create( pj_pool_t *pool,
+ pj_bool_t secure )
+{
+ pjsip_sip_uri *url = PJ_POOL_ALLOC_T(pool, pjsip_sip_uri);
+ pjsip_sip_uri_init(url, secure);
+ return url;
+}
+
+static pj_ssize_t pjsip_url_print( pjsip_uri_context_e context,
+ const pjsip_sip_uri *url,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf+size;
+ const pj_str_t *scheme;
+ const pjsip_parser_const_t *pc = pjsip_parser_const();
+
+ *buf = '\0';
+
+ /* Print scheme ("sip:" or "sips:") */
+ scheme = pjsip_uri_get_scheme(url);
+ copy_advance_check(buf, *scheme);
+ *buf++ = ':';
+
+ /* Print "user:password@", if any. */
+ if (url->user.slen) {
+ copy_advance_escape(buf, url->user, pc->pjsip_USER_SPEC);
+ if (url->passwd.slen) {
+ *buf++ = ':';
+ copy_advance_escape(buf, url->passwd, pc->pjsip_PASSWD_SPEC);
+ }
+
+ *buf++ = '@';
+ }
+
+ /* Print host. */
+ pj_assert(url->host.slen != 0);
+ /* Detect IPv6 IP address */
+ if (pj_memchr(url->host.ptr, ':', url->host.slen)) {
+ copy_advance_pair_quote_cond(buf, "", 0, url->host, '[', ']');
+ } else {
+ copy_advance_check(buf, url->host);
+ }
+
+ /* Only print port if it is explicitly specified.
+ * Port is not allowed in To and From header, see Table 1 in
+ * RFC 3261 Section 19.1.1
+ */
+ /* Note: ticket #1141 adds run-time setting to allow port number to
+ * appear in From/To header. Default is still false.
+ */
+ if (url->port &&
+ (context != PJSIP_URI_IN_FROMTO_HDR ||
+ pjsip_cfg()->endpt.allow_port_in_fromto_hdr))
+ {
+ if (endbuf - buf < 10)
+ return -1;
+
+ *buf++ = ':';
+ printed = pj_utoa(url->port, buf);
+ buf += printed;
+ }
+
+ /* User param is allowed in all contexes */
+ copy_advance_pair_check(buf, ";user=", 6, url->user_param);
+
+ /* Method param is only allowed in external/other context. */
+ if (context == PJSIP_URI_IN_OTHER) {
+ copy_advance_pair_escape(buf, ";method=", 8, url->method_param,
+ pc->pjsip_PARAM_CHAR_SPEC);
+ }
+
+ /* Transport is not allowed in From/To header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR) {
+ copy_advance_pair_escape(buf, ";transport=", 11, url->transport_param,
+ pc->pjsip_PARAM_CHAR_SPEC);
+ }
+
+ /* TTL param is not allowed in From, To, Route, and Record-Route header. */
+ if (url->ttl_param >= 0 && context != PJSIP_URI_IN_FROMTO_HDR &&
+ context != PJSIP_URI_IN_ROUTING_HDR)
+ {
+ if (endbuf - buf < 15)
+ return -1;
+ pj_memcpy(buf, ";ttl=", 5);
+ printed = pj_utoa(url->ttl_param, buf+5);
+ buf += printed + 5;
+ }
+
+ /* maddr param is not allowed in From and To header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR && url->maddr_param.slen) {
+ /* Detect IPv6 IP address */
+ if (pj_memchr(url->maddr_param.ptr, ':', url->maddr_param.slen)) {
+ copy_advance_pair_quote_cond(buf, ";maddr=", 7, url->maddr_param,
+ '[', ']');
+ } else {
+ copy_advance_pair_escape(buf, ";maddr=", 7, url->maddr_param,
+ pc->pjsip_PARAM_CHAR_SPEC);
+ }
+ }
+
+ /* lr param is not allowed in From, To, and Contact header. */
+ if (url->lr_param && context != PJSIP_URI_IN_FROMTO_HDR &&
+ context != PJSIP_URI_IN_CONTACT_HDR)
+ {
+ pj_str_t lr = { ";lr", 3 };
+ if (endbuf - buf < 3)
+ return -1;
+ copy_advance_check(buf, lr);
+ }
+
+ /* Other param. */
+ printed = pjsip_param_print_on(&url->other_param, buf, endbuf-buf,
+ &pc->pjsip_PARAM_CHAR_SPEC,
+ &pc->pjsip_PARAM_CHAR_SPEC, ';');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+
+ /* Header param.
+ * Header param is only allowed in these contexts:
+ * - PJSIP_URI_IN_CONTACT_HDR
+ * - PJSIP_URI_IN_OTHER
+ */
+ if (context == PJSIP_URI_IN_CONTACT_HDR || context == PJSIP_URI_IN_OTHER) {
+ printed = pjsip_param_print_on(&url->header_param, buf, endbuf-buf,
+ &pc->pjsip_HDR_CHAR_SPEC,
+ &pc->pjsip_HDR_CHAR_SPEC, '?');
+ if (printed < 0)
+ return -1;
+ buf += printed;
+ }
+
+ *buf = '\0';
+ return buf-startbuf;
+}
+
+static pj_status_t pjsip_url_compare( pjsip_uri_context_e context,
+ const pjsip_sip_uri *url1,
+ const pjsip_sip_uri *url2)
+{
+ const pjsip_param *p1;
+
+ /*
+ * Compare two SIP URL's according to Section 19.1.4 of RFC 3261.
+ */
+
+ /* SIP and SIPS URI are never equivalent.
+ * Note: just compare the vptr to avoid string comparison.
+ * Pretty neat huh!!
+ */
+ if (url1->vptr != url2->vptr)
+ return PJSIP_ECMPSCHEME;
+
+ /* Comparison of the userinfo of SIP and SIPS URIs is case-sensitive.
+ * This includes userinfo containing passwords or formatted as
+ * telephone-subscribers.
+ */
+ if (pj_strcmp(&url1->user, &url2->user) != 0)
+ return PJSIP_ECMPUSER;
+ if (pj_strcmp(&url1->passwd, &url2->passwd) != 0)
+ return PJSIP_ECMPPASSWD;
+
+ /* Comparison of all other components of the URI is
+ * case-insensitive unless explicitly defined otherwise.
+ */
+
+ /* The ordering of parameters and header fields is not significant
+ * in comparing SIP and SIPS URIs.
+ */
+
+ /* Characters other than those in the reserved set (see RFC 2396 [5])
+ * are equivalent to their encoding.
+ */
+
+ /* An IP address that is the result of a DNS lookup of a host name
+ * does not match that host name.
+ */
+ if (pj_stricmp(&url1->host, &url2->host) != 0)
+ return PJSIP_ECMPHOST;
+
+ /* A URI omitting any component with a default value will not match a URI
+ * explicitly containing that component with its default value.
+ * For instance, a URI omitting the optional port component will not match
+ * a URI explicitly declaring port 5060.
+ * The same is true for the transport-parameter, ttl-parameter,
+ * user-parameter, and method components.
+ */
+
+ /* Port is not allowed in To and From header.
+ */
+ if (context != PJSIP_URI_IN_FROMTO_HDR) {
+ if (url1->port != url2->port)
+ return PJSIP_ECMPPORT;
+ }
+ /* Transport is not allowed in From/To header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR) {
+ if (pj_stricmp(&url1->transport_param, &url2->transport_param) != 0)
+ return PJSIP_ECMPTRANSPORTPRM;
+ }
+ /* TTL param is not allowed in From, To, Route, and Record-Route header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR &&
+ context != PJSIP_URI_IN_ROUTING_HDR)
+ {
+ if (url1->ttl_param != url2->ttl_param)
+ return PJSIP_ECMPTTLPARAM;
+ }
+ /* User param is allowed in all contexes */
+ if (pj_stricmp(&url1->user_param, &url2->user_param) != 0)
+ return PJSIP_ECMPUSERPARAM;
+ /* Method param is only allowed in external/other context. */
+ if (context == PJSIP_URI_IN_OTHER) {
+ if (pj_stricmp(&url1->method_param, &url2->method_param) != 0)
+ return PJSIP_ECMPMETHODPARAM;
+ }
+ /* maddr param is not allowed in From and To header. */
+ if (context != PJSIP_URI_IN_FROMTO_HDR) {
+ if (pj_stricmp(&url1->maddr_param, &url2->maddr_param) != 0)
+ return PJSIP_ECMPMADDRPARAM;
+ }
+
+ /* lr parameter is ignored (?) */
+ /* lr param is not allowed in From, To, and Contact header. */
+
+
+ /* All other uri-parameters appearing in only one URI are ignored when
+ * comparing the URIs.
+ */
+ if (pjsip_param_cmp(&url1->other_param, &url2->other_param, 1)!=0)
+ return PJSIP_ECMPOTHERPARAM;
+
+ /* URI header components are never ignored. Any present header component
+ * MUST be present in both URIs and match for the URIs to match.
+ * The matching rules are defined for each header field in Section 20.
+ */
+ p1 = url1->header_param.next;
+ while (p1 != &url1->header_param) {
+ const pjsip_param *p2;
+ p2 = pjsip_param_find(&url2->header_param, &p1->name);
+ if (p2) {
+ /* It seems too much to compare two header params according to
+ * the rule of each header. We'll just compare them string to
+ * string..
+ */
+ if (pj_stricmp(&p1->value, &p2->value) != 0)
+ return PJSIP_ECMPHEADERPARAM;
+ } else {
+ return PJSIP_ECMPHEADERPARAM;
+ }
+ p1 = p1->next;
+ }
+
+ /* Equal!! Pheuww.. */
+ return PJ_SUCCESS;
+}
+
+
+PJ_DEF(void) pjsip_sip_uri_assign(pj_pool_t *pool, pjsip_sip_uri *url,
+ const pjsip_sip_uri *rhs)
+{
+ pj_strdup( pool, &url->user, &rhs->user);
+ pj_strdup( pool, &url->passwd, &rhs->passwd);
+ pj_strdup( pool, &url->host, &rhs->host);
+ url->port = rhs->port;
+ pj_strdup( pool, &url->user_param, &rhs->user_param);
+ pj_strdup( pool, &url->method_param, &rhs->method_param);
+ pj_strdup( pool, &url->transport_param, &rhs->transport_param);
+ url->ttl_param = rhs->ttl_param;
+ pj_strdup( pool, &url->maddr_param, &rhs->maddr_param);
+ pjsip_param_clone(pool, &url->other_param, &rhs->other_param);
+ pjsip_param_clone(pool, &url->header_param, &rhs->header_param);
+ url->lr_param = rhs->lr_param;
+}
+
+static pjsip_sip_uri* pjsip_url_clone(pj_pool_t *pool, const pjsip_sip_uri *rhs)
+{
+ pjsip_sip_uri *url = PJ_POOL_ALLOC_T(pool, pjsip_sip_uri);
+ if (!url)
+ return NULL;
+
+ pjsip_sip_uri_init(url, IS_SIPS(rhs));
+ pjsip_sip_uri_assign(pool, url, rhs);
+ return url;
+}
+
+static const pj_str_t *pjsip_name_addr_get_scheme(const pjsip_name_addr *name)
+{
+ pj_assert(name->uri != NULL);
+ return pjsip_uri_get_scheme(name->uri);
+}
+
+PJ_DEF(void) pjsip_name_addr_init(pjsip_name_addr *name)
+{
+ name->vptr = &name_addr_vptr;
+ name->uri = NULL;
+ name->display.slen = 0;
+ name->display.ptr = NULL;
+}
+
+PJ_DEF(pjsip_name_addr*) pjsip_name_addr_create(pj_pool_t *pool)
+{
+ pjsip_name_addr *name_addr = PJ_POOL_ALLOC_T(pool, pjsip_name_addr);
+ pjsip_name_addr_init(name_addr);
+ return name_addr;
+}
+
+static pj_ssize_t pjsip_name_addr_print(pjsip_uri_context_e context,
+ const pjsip_name_addr *name,
+ char *buf, pj_size_t size)
+{
+ int printed;
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+ pjsip_uri *uri;
+
+ uri = (pjsip_uri*) pjsip_uri_get_uri(name->uri);
+ pj_assert(uri != NULL);
+
+ if (context != PJSIP_URI_IN_REQ_URI) {
+ if (name->display.slen) {
+ if (endbuf-buf < 8) return -1;
+ *buf++ = '"';
+ copy_advance(buf, name->display);
+ *buf++ = '"';
+ *buf++ = ' ';
+ }
+ *buf++ = '<';
+ }
+
+ printed = pjsip_uri_print(context,uri, buf, size-(buf-startbuf));
+ if (printed < 1)
+ return -1;
+ buf += printed;
+
+ if (context != PJSIP_URI_IN_REQ_URI) {
+ *buf++ = '>';
+ }
+
+ *buf = '\0';
+ return buf-startbuf;
+}
+
+PJ_DEF(void) pjsip_name_addr_assign(pj_pool_t *pool, pjsip_name_addr *dst,
+ const pjsip_name_addr *src)
+{
+ pj_strdup( pool, &dst->display, &src->display);
+ dst->uri = (pjsip_uri*) pjsip_uri_clone(pool, src->uri);
+}
+
+static pjsip_name_addr* pjsip_name_addr_clone( pj_pool_t *pool,
+ const pjsip_name_addr *rhs)
+{
+ pjsip_name_addr *addr = PJ_POOL_ALLOC_T(pool, pjsip_name_addr);
+ if (!addr)
+ return NULL;
+
+ pjsip_name_addr_init(addr);
+ pjsip_name_addr_assign(pool, addr, rhs);
+ return addr;
+}
+
+static int pjsip_name_addr_compare( pjsip_uri_context_e context,
+ const pjsip_name_addr *naddr1,
+ const pjsip_name_addr *naddr2)
+{
+ int d;
+
+ /* Check that naddr2 is also a name_addr */
+ if (naddr1->vptr != naddr2->vptr)
+ return -1;
+
+ /* I'm not sure whether display name is included in the comparison. */
+ if (pj_strcmp(&naddr1->display, &naddr2->display) != 0) {
+ return -1;
+ }
+
+ pj_assert( naddr1->uri != NULL );
+ pj_assert( naddr2->uri != NULL );
+
+ /* Compare name-addr as URL */
+ d = pjsip_uri_cmp( context, naddr1->uri, naddr2->uri);
+ if (d)
+ return d;
+
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const pj_str_t *other_uri_get_scheme( const pjsip_other_uri*);
+static void *other_uri_get_uri( pjsip_other_uri*);
+static pj_ssize_t other_uri_print( pjsip_uri_context_e context,
+ const pjsip_other_uri *url,
+ char *buf, pj_size_t size);
+static int other_uri_cmp( pjsip_uri_context_e context,
+ const pjsip_other_uri *url1,
+ const pjsip_other_uri *url2);
+static pjsip_other_uri* other_uri_clone( pj_pool_t *pool,
+ const pjsip_other_uri *rhs);
+
+static pjsip_uri_vptr other_uri_vptr =
+{
+ (P_GET_SCHEME) &other_uri_get_scheme,
+ (P_GET_URI) &other_uri_get_uri,
+ (P_PRINT_URI) &other_uri_print,
+ (P_CMP_URI) &other_uri_cmp,
+ (P_CLONE) &other_uri_clone
+};
+
+
+PJ_DEF(pjsip_other_uri*) pjsip_other_uri_create(pj_pool_t *pool)
+{
+ pjsip_other_uri *uri = PJ_POOL_ZALLOC_T(pool, pjsip_other_uri);
+ uri->vptr = &other_uri_vptr;
+ return uri;
+}
+
+static const pj_str_t *other_uri_get_scheme( const pjsip_other_uri *uri )
+{
+ return &uri->scheme;
+}
+
+static void *other_uri_get_uri( pjsip_other_uri *uri )
+{
+ return uri;
+}
+
+static pj_ssize_t other_uri_print(pjsip_uri_context_e context,
+ const pjsip_other_uri *uri,
+ char *buf, pj_size_t size)
+{
+ char *startbuf = buf;
+ char *endbuf = buf + size;
+
+ PJ_UNUSED_ARG(context);
+
+ if (uri->scheme.slen + uri->content.slen + 1 > (int)size)
+ return -1;
+
+ /* Print scheme. */
+ copy_advance(buf, uri->scheme);
+ *buf++ = ':';
+
+ /* Print content. */
+ copy_advance(buf, uri->content);
+
+ return (buf - startbuf);
+}
+
+static int other_uri_cmp(pjsip_uri_context_e context,
+ const pjsip_other_uri *uri1,
+ const pjsip_other_uri *uri2)
+{
+ PJ_UNUSED_ARG(context);
+
+ /* Check that uri2 is also an other_uri */
+ if (uri1->vptr != uri2->vptr)
+ return -1;
+
+ /* Scheme must match. */
+ if (pj_stricmp(&uri1->scheme, &uri2->scheme) != 0) {
+ return PJSIP_ECMPSCHEME;
+ }
+
+ /* Content must match. */
+ if(pj_stricmp(&uri1->content, &uri2->content) != 0) {
+ return -1;
+ }
+
+ /* Equal. */
+ return 0;
+}
+
+/* Clone *: URI */
+static pjsip_other_uri* other_uri_clone(pj_pool_t *pool,
+ const pjsip_other_uri *rhs)
+{
+ pjsip_other_uri *uri = pjsip_other_uri_create(pool);
+ pj_strdup(pool, &uri->scheme, &rhs->scheme);
+ pj_strdup(pool, &uri->content, &rhs->content);
+
+ return uri;
+}
+
diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c
new file mode 100644
index 0000000..60f2568
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util.c
@@ -0,0 +1,1850 @@
+/* $Id: sip_util.c 4173 2012-06-20 10:39:05Z ming $ */
+/*
+ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjsip/sip_util.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_msg.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_errno.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/except.h>
+#include <pj/rand.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+
+#define THIS_FILE "endpoint"
+
+static const char *event_str[] =
+{
+ "UNIDENTIFIED",
+ "TIMER",
+ "TX_MSG",
+ "RX_MSG",
+ "TRANSPORT_ERROR",
+ "TSX_STATE",
+ "USER",
+};
+
+static pj_str_t str_TEXT = { "text", 4},
+ str_PLAIN = { "plain", 5 };
+
+/* Add URI to target-set */
+PJ_DEF(pj_status_t) pjsip_target_set_add_uri( pjsip_target_set *tset,
+ pj_pool_t *pool,
+ const pjsip_uri *uri,
+ int q1000)
+{
+ pjsip_target *t, *pos = NULL;
+
+ PJ_ASSERT_RETURN(tset && pool && uri, PJ_EINVAL);
+
+ /* Set q-value to 1 if it is not set */
+ if (q1000 <= 0)
+ q1000 = 1000;
+
+ /* Scan all the elements to see for duplicates, and at the same time
+ * get the position where the new element should be inserted to
+ * based on the q-value.
+ */
+ t = tset->head.next;
+ while (t != &tset->head) {
+ if (pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI, t->uri, uri)==PJ_SUCCESS)
+ return PJ_EEXISTS;
+ if (pos==NULL && t->q1000 < q1000)
+ pos = t;
+ t = t->next;
+ }
+
+ /* Create new element */
+ t = PJ_POOL_ZALLOC_T(pool, pjsip_target);
+ t->uri = (pjsip_uri*)pjsip_uri_clone(pool, uri);
+ t->q1000 = q1000;
+
+ /* Insert */
+ if (pos == NULL)
+ pj_list_push_back(&tset->head, t);
+ else
+ pj_list_insert_before(pos, t);
+
+ /* Set current target if this is the first URI */
+ if (tset->current == NULL)
+ tset->current = t;
+
+ return PJ_SUCCESS;
+}
+
+/* Add URI's in the Contact header in the message to target-set */
+PJ_DEF(pj_status_t) pjsip_target_set_add_from_msg( pjsip_target_set *tset,
+ pj_pool_t *pool,
+ const pjsip_msg *msg)
+{
+ const pjsip_hdr *hdr;
+ unsigned added = 0;
+
+ PJ_ASSERT_RETURN(tset && pool && msg, PJ_EINVAL);
+
+ /* Scan for Contact headers and add the URI */
+ hdr = msg->hdr.next;
+ while (hdr != &msg->hdr) {
+ if (hdr->type == PJSIP_H_CONTACT) {
+ const pjsip_contact_hdr *cn_hdr = (const pjsip_contact_hdr*)hdr;
+
+ if (!cn_hdr->star) {
+ pj_status_t rc;
+ rc = pjsip_target_set_add_uri(tset, pool, cn_hdr->uri,
+ cn_hdr->q1000);
+ if (rc == PJ_SUCCESS)
+ ++added;
+ }
+ }
+ hdr = hdr->next;
+ }
+
+ return added ? PJ_SUCCESS : PJ_EEXISTS;
+}
+
+
+/* Get next target, if any */
+PJ_DEF(pjsip_target*) pjsip_target_set_get_next(const pjsip_target_set *tset)
+{
+ const pjsip_target *t, *next = NULL;
+
+ t = tset->head.next;
+ while (t != &tset->head) {
+ if (PJSIP_IS_STATUS_IN_CLASS(t->code, 200)) {
+ /* No more target since one target has been successful */
+ return NULL;
+ }
+ if (PJSIP_IS_STATUS_IN_CLASS(t->code, 600)) {
+ /* No more target since one target returned global error */
+ return NULL;
+ }
+ if (t->code==0 && next==NULL) {
+ /* This would be the next target as long as we don't find
+ * targets with 2xx or 6xx status after this.
+ */
+ next = t;
+ }
+ t = t->next;
+ }
+
+ return (pjsip_target*)next;
+}
+
+
+/* Set current target */
+PJ_DEF(pj_status_t) pjsip_target_set_set_current( pjsip_target_set *tset,
+ pjsip_target *target)
+{
+ PJ_ASSERT_RETURN(tset && target, PJ_EINVAL);
+ PJ_ASSERT_RETURN(pj_list_find_node(tset, target) != NULL, PJ_ENOTFOUND);
+
+ tset->current = target;
+
+ return PJ_SUCCESS;
+}
+
+
+/* Assign status to a target */
+PJ_DEF(pj_status_t) pjsip_target_assign_status( pjsip_target *target,
+ pj_pool_t *pool,
+ int status_code,
+ const pj_str_t *reason)
+{
+ PJ_ASSERT_RETURN(target && pool && status_code && reason, PJ_EINVAL);
+
+ target->code = (pjsip_status_code)status_code;
+ pj_strdup(pool, &target->reason, reason);
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Initialize transmit data (msg) with the headers and optional body.
+ * This will just put the headers in the message as it is. Be carefull
+ * when calling this function because once a header is put in a message,
+ * it CAN NOT be put in other message until the first message is deleted,
+ * because the way the header is put in the list.
+ * That's why the session will shallow_clone it's headers before calling
+ * this function.
+ */
+static void init_request_throw( pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata,
+ pjsip_method *method,
+ pjsip_uri *param_target,
+ pjsip_from_hdr *param_from,
+ pjsip_to_hdr *param_to,
+ pjsip_contact_hdr *param_contact,
+ pjsip_cid_hdr *param_call_id,
+ pjsip_cseq_hdr *param_cseq,
+ const pj_str_t *param_text)
+{
+ pjsip_msg *msg;
+ pjsip_msg_body *body;
+ pjsip_via_hdr *via;
+ const pjsip_hdr *endpt_hdr;
+
+ /* Create the message. */
+ msg = tdata->msg = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG);
+
+ /* Init request URI. */
+ pj_memcpy(&msg->line.req.method, method, sizeof(*method));
+ msg->line.req.uri = param_target;
+
+ /* Add additional request headers from endpoint. */
+ endpt_hdr = pjsip_endpt_get_request_headers(endpt)->next;
+ while (endpt_hdr != pjsip_endpt_get_request_headers(endpt)) {
+ pjsip_hdr *hdr = (pjsip_hdr*)
+ pjsip_hdr_shallow_clone(tdata->pool, endpt_hdr);
+ pjsip_msg_add_hdr( tdata->msg, hdr );
+ endpt_hdr = endpt_hdr->next;
+ }
+
+ /* Add From header. */
+ if (param_from->tag.slen == 0)
+ pj_create_unique_string(tdata->pool, &param_from->tag);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_from);
+
+ /* Add To header. */
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_to);
+
+ /* Add Contact header. */
+ if (param_contact) {
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_contact);
+ }
+
+ /* Add Call-ID header. */
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_call_id);
+
+ /* Add CSeq header. */
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)param_cseq);
+
+ /* Add a blank Via header in the front of the message. */
+ via = pjsip_via_hdr_create(tdata->pool);
+ via->rport_param = pjsip_cfg()->endpt.disable_rport ? -1 : 0;
+ pjsip_msg_insert_first_hdr(msg, (pjsip_hdr*)via);
+
+ /* Add header params as request headers */
+ if (PJSIP_URI_SCHEME_IS_SIP(param_target) ||
+ PJSIP_URI_SCHEME_IS_SIPS(param_target))
+ {
+ pjsip_sip_uri *uri = (pjsip_sip_uri*) pjsip_uri_get_uri(param_target);
+ pjsip_param *hparam;
+
+ hparam = uri->header_param.next;
+ while (hparam != &uri->header_param) {
+ pjsip_generic_string_hdr *hdr;
+
+ hdr = pjsip_generic_string_hdr_create(tdata->pool,
+ &hparam->name,
+ &hparam->value);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*)hdr);
+ hparam = hparam->next;
+ }
+ }
+
+ /* Create message body. */
+ if (param_text) {
+ body = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_msg_body);
+ body->content_type.type = str_TEXT;
+ body->content_type.subtype = str_PLAIN;
+ body->data = pj_pool_alloc(tdata->pool, param_text->slen );
+ pj_memcpy(body->data, param_text->ptr, param_text->slen);
+ body->len = param_text->slen;
+ body->print_body = &pjsip_print_text_body;
+ msg->body = body;
+ }
+
+ PJ_LOG(5,(THIS_FILE, "%s created.",
+ pjsip_tx_data_get_info(tdata)));
+
+}
+
+/*
+ * Create arbitrary request.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_request( pjsip_endpoint *endpt,
+ const pjsip_method *method,
+ const pj_str_t *param_target,
+ const pj_str_t *param_from,
+ const pj_str_t *param_to,
+ const pj_str_t *param_contact,
+ const pj_str_t *param_call_id,
+ int param_cseq,
+ const pj_str_t *param_text,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_uri *target;
+ pjsip_tx_data *tdata;
+ pjsip_from_hdr *from;
+ pjsip_to_hdr *to;
+ pjsip_contact_hdr *contact;
+ pjsip_cseq_hdr *cseq = NULL; /* = NULL, warning in VC6 */
+ pjsip_cid_hdr *call_id;
+ pj_str_t tmp;
+ pj_status_t status;
+ const pj_str_t STR_CONTACT = { "Contact", 7 };
+ PJ_USE_EXCEPTION;
+
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Init reference counter to 1. */
+ pjsip_tx_data_add_ref(tdata);
+
+ PJ_TRY {
+ /* Request target. */
+ pj_strdup_with_null(tdata->pool, &tmp, param_target);
+ target = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen, 0);
+ if (target == NULL) {
+ status = PJSIP_EINVALIDREQURI;
+ goto on_error;
+ }
+
+ /* From */
+ from = pjsip_from_hdr_create(tdata->pool);
+ pj_strdup_with_null(tdata->pool, &tmp, param_from);
+ from->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (from->uri == NULL) {
+ status = PJSIP_EINVALIDHDR;
+ goto on_error;
+ }
+ pj_create_unique_string(tdata->pool, &from->tag);
+
+ /* To */
+ to = pjsip_to_hdr_create(tdata->pool);
+ pj_strdup_with_null(tdata->pool, &tmp, param_to);
+ to->uri = pjsip_parse_uri( tdata->pool, tmp.ptr, tmp.slen,
+ PJSIP_PARSE_URI_AS_NAMEADDR);
+ if (to->uri == NULL) {
+ status = PJSIP_EINVALIDHDR;
+ goto on_error;
+ }
+
+ /* Contact. */
+ if (param_contact) {
+ pj_strdup_with_null(tdata->pool, &tmp, param_contact);
+ contact = (pjsip_contact_hdr*)
+ pjsip_parse_hdr(tdata->pool, &STR_CONTACT, tmp.ptr,
+ tmp.slen, NULL);
+ if (contact == NULL) {
+ status = PJSIP_EINVALIDHDR;
+ goto on_error;
+ }
+ } else {
+ contact = NULL;
+ }
+
+ /* Call-ID */
+ call_id = pjsip_cid_hdr_create(tdata->pool);
+ if (param_call_id != NULL && param_call_id->slen)
+ pj_strdup(tdata->pool, &call_id->id, param_call_id);
+ else
+ pj_create_unique_string(tdata->pool, &call_id->id);
+
+ /* CSeq */
+ cseq = pjsip_cseq_hdr_create(tdata->pool);
+ if (param_cseq >= 0)
+ cseq->cseq = param_cseq;
+ else
+ cseq->cseq = pj_rand() & 0xFFFF;
+
+ /* Method */
+ pjsip_method_copy(tdata->pool, &cseq->method, method);
+
+ /* Create the request. */
+ init_request_throw( endpt, tdata, &cseq->method, target, from, to,
+ contact, call_id, cseq, param_text);
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ PJ_END
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+
+on_error:
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+}
+
+PJ_DEF(pj_status_t) pjsip_endpt_create_request_from_hdr( pjsip_endpoint *endpt,
+ const pjsip_method *method,
+ const pjsip_uri *param_target,
+ const pjsip_from_hdr *param_from,
+ const pjsip_to_hdr *param_to,
+ const pjsip_contact_hdr *param_contact,
+ const pjsip_cid_hdr *param_call_id,
+ int param_cseq,
+ const pj_str_t *param_text,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_uri *target;
+ pjsip_tx_data *tdata;
+ pjsip_from_hdr *from;
+ pjsip_to_hdr *to;
+ pjsip_contact_hdr *contact;
+ pjsip_cid_hdr *call_id;
+ pjsip_cseq_hdr *cseq = NULL; /* The NULL because warning in VC6 */
+ pj_status_t status;
+ PJ_USE_EXCEPTION;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt && method && param_target && param_from &&
+ param_to && p_tdata, PJ_EINVAL);
+
+ /* Create new transmit data. */
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set initial reference counter to 1. */
+ pjsip_tx_data_add_ref(tdata);
+
+ PJ_TRY {
+ /* Duplicate target URI and headers. */
+ target = (pjsip_uri*) pjsip_uri_clone(tdata->pool, param_target);
+ from = (pjsip_from_hdr*) pjsip_hdr_clone(tdata->pool, param_from);
+ pjsip_fromto_hdr_set_from(from);
+ to = (pjsip_to_hdr*) pjsip_hdr_clone(tdata->pool, param_to);
+ pjsip_fromto_hdr_set_to(to);
+ if (param_contact) {
+ contact = (pjsip_contact_hdr*)
+ pjsip_hdr_clone(tdata->pool, param_contact);
+ } else {
+ contact = NULL;
+ }
+ call_id = pjsip_cid_hdr_create(tdata->pool);
+ if (param_call_id != NULL && param_call_id->id.slen)
+ pj_strdup(tdata->pool, &call_id->id, &param_call_id->id);
+ else
+ pj_create_unique_string(tdata->pool, &call_id->id);
+
+ cseq = pjsip_cseq_hdr_create(tdata->pool);
+ if (param_cseq >= 0)
+ cseq->cseq = param_cseq;
+ else
+ cseq->cseq = pj_rand() % 0xFFFF;
+ pjsip_method_copy(tdata->pool, &cseq->method, method);
+
+ /* Copy headers to the request. */
+ init_request_throw(endpt, tdata, &cseq->method, target, from, to,
+ contact, call_id, cseq, param_text);
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ PJ_END;
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+
+on_error:
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+}
+
+/*
+ * Construct a minimal response message for the received request.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt,
+ const pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pjsip_msg *msg, *req_msg;
+ pjsip_hdr *hdr;
+ pjsip_to_hdr *to_hdr;
+ pjsip_via_hdr *top_via = NULL, *via;
+ pjsip_rr_hdr *rr;
+ pj_status_t status;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL);
+
+ /* Check status code. */
+ PJ_ASSERT_RETURN(st_code >= 100 && st_code <= 699, PJ_EINVAL);
+
+ /* rdata must be a request message. */
+ req_msg = rdata->msg_info.msg;
+ pj_assert(req_msg->type == PJSIP_REQUEST_MSG);
+
+ /* Request MUST NOT be ACK request! */
+ PJ_ASSERT_RETURN(req_msg->line.req.method.id != PJSIP_ACK_METHOD,
+ PJ_EINVALIDOP);
+
+ /* Create a new transmit buffer. */
+ status = pjsip_endpt_create_tdata( endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Set initial reference count to 1. */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Create new response message. */
+ tdata->msg = msg = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG);
+
+ /* Set status code and reason text. */
+ msg->line.status.code = st_code;
+ if (st_text)
+ pj_strdup(tdata->pool, &msg->line.status.reason, st_text);
+ else
+ msg->line.status.reason = *pjsip_get_status_text(st_code);
+
+ /* Set TX data attributes. */
+ tdata->rx_timestamp = rdata->pkt_info.timestamp;
+
+ /* Copy all the via headers, in order. */
+ via = rdata->msg_info.via;
+ while (via) {
+ pjsip_via_hdr *new_via;
+
+ new_via = (pjsip_via_hdr*)pjsip_hdr_clone(tdata->pool, via);
+ if (top_via == NULL)
+ top_via = new_via;
+
+ pjsip_msg_add_hdr( msg, (pjsip_hdr*)new_via);
+ via = via->next;
+ if (via != (void*)&req_msg->hdr)
+ via = (pjsip_via_hdr*)
+ pjsip_msg_find_hdr(req_msg, PJSIP_H_VIA, via);
+ else
+ break;
+ }
+
+ /* Copy all Record-Route headers, in order. */
+ rr = (pjsip_rr_hdr*)
+ pjsip_msg_find_hdr(req_msg, PJSIP_H_RECORD_ROUTE, NULL);
+ while (rr) {
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rr));
+ rr = rr->next;
+ if (rr != (void*)&req_msg->hdr)
+ rr = (pjsip_rr_hdr*) pjsip_msg_find_hdr(req_msg,
+ PJSIP_H_RECORD_ROUTE, rr);
+ else
+ break;
+ }
+
+ /* Copy Call-ID header. */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr( req_msg, PJSIP_H_CALL_ID, NULL);
+ pjsip_msg_add_hdr(msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr));
+
+ /* Copy From header. */
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.from);
+ pjsip_msg_add_hdr( msg, hdr);
+
+ /* Copy To header. */
+ to_hdr = (pjsip_to_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.to);
+ pjsip_msg_add_hdr( msg, (pjsip_hdr*)to_hdr);
+
+ /* Must add To tag in the response (Section 8.2.6.2), except if this is
+ * 100 (Trying) response. Same tag must be created for the same request
+ * (e.g. same tag in provisional and final response). The easiest way
+ * to do this is to derive the tag from Via branch parameter (or to
+ * use it directly).
+ */
+ if (to_hdr->tag.slen==0 && st_code > 100 && top_via) {
+ to_hdr->tag = top_via->branch_param;
+ }
+
+ /* Copy CSeq header. */
+ hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, rdata->msg_info.cseq);
+ pjsip_msg_add_hdr( msg, hdr);
+
+ /* All done. */
+ *p_tdata = tdata;
+
+ PJ_LOG(5,(THIS_FILE, "%s created", pjsip_tx_data_get_info(tdata)));
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Construct ACK for 3xx-6xx final response (according to chapter 17.1.1 of
+ * RFC3261). Note that the generation of ACK for 2xx response is different,
+ * and one must not use this function to generate such ACK.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_ack( pjsip_endpoint *endpt,
+ const pjsip_tx_data *tdata,
+ const pjsip_rx_data *rdata,
+ pjsip_tx_data **ack_tdata)
+{
+ pjsip_tx_data *ack = NULL;
+ const pjsip_msg *invite_msg;
+ const pjsip_from_hdr *from_hdr;
+ const pjsip_to_hdr *to_hdr;
+ const pjsip_cid_hdr *cid_hdr;
+ const pjsip_cseq_hdr *cseq_hdr;
+ const pjsip_hdr *hdr;
+ pjsip_hdr *via;
+ pjsip_to_hdr *to;
+ pj_status_t status;
+
+ /* rdata must be a non-2xx final response. */
+ pj_assert(rdata->msg_info.msg->type==PJSIP_RESPONSE_MSG &&
+ rdata->msg_info.msg->line.status.code >= 300);
+
+ /* Initialize return value to NULL. */
+ *ack_tdata = NULL;
+
+ /* The original INVITE message. */
+ invite_msg = tdata->msg;
+
+ /* Get the headers from original INVITE request. */
+# define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL)
+
+ from_hdr = (const pjsip_from_hdr*) FIND_HDR(invite_msg, FROM);
+ PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr);
+
+ to_hdr = (const pjsip_to_hdr*) FIND_HDR(invite_msg, TO);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+ cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(invite_msg, CALL_ID);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+ cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(invite_msg, CSEQ);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+# undef FIND_HDR
+
+ /* Create new request message from the headers. */
+ status = pjsip_endpt_create_request_from_hdr(endpt,
+ pjsip_get_ack_method(),
+ tdata->msg->line.req.uri,
+ from_hdr, to_hdr,
+ NULL, cid_hdr,
+ cseq_hdr->cseq, NULL,
+ &ack);
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Update tag in To header with the one from the response (if any). */
+ to = (pjsip_to_hdr*) pjsip_msg_find_hdr(ack->msg, PJSIP_H_TO, NULL);
+ pj_strdup(ack->pool, &to->tag, &rdata->msg_info.to->tag);
+
+
+ /* Clear Via headers in the new request. */
+ while ((via=(pjsip_hdr*)pjsip_msg_find_hdr(ack->msg, PJSIP_H_VIA, NULL)) != NULL)
+ pj_list_erase(via);
+
+ /* Must contain single Via, just as the original INVITE. */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_VIA, NULL);
+ pjsip_msg_insert_first_hdr( ack->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(ack->pool,hdr) );
+
+ /* If the original INVITE has Route headers, those header fields MUST
+ * appear in the ACK.
+ */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, NULL);
+ while (hdr != NULL) {
+ pjsip_msg_add_hdr( ack->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(ack->pool, hdr) );
+ hdr = hdr->next;
+ if (hdr == &invite_msg->hdr)
+ break;
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr( invite_msg, PJSIP_H_ROUTE, hdr);
+ }
+
+ /* We're done.
+ * "tdata" parameter now contains the ACK message.
+ */
+ *ack_tdata = ack;
+ return PJ_SUCCESS;
+
+on_missing_hdr:
+ if (ack)
+ pjsip_tx_data_dec_ref(ack);
+ return PJSIP_EMISSINGHDR;
+}
+
+
+/*
+ * Construct CANCEL request for the previously sent request, according to
+ * chapter 9.1 of RFC3261.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_cancel( pjsip_endpoint *endpt,
+ const pjsip_tx_data *req_tdata,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *cancel_tdata = NULL;
+ const pjsip_from_hdr *from_hdr;
+ const pjsip_to_hdr *to_hdr;
+ const pjsip_cid_hdr *cid_hdr;
+ const pjsip_cseq_hdr *cseq_hdr;
+ const pjsip_hdr *hdr;
+ pjsip_hdr *via;
+ pj_status_t status;
+
+ /* The transmit buffer must INVITE request. */
+ PJ_ASSERT_RETURN(req_tdata->msg->type == PJSIP_REQUEST_MSG &&
+ req_tdata->msg->line.req.method.id == PJSIP_INVITE_METHOD,
+ PJ_EINVAL);
+
+ /* Get the headers from original INVITE request. */
+# define FIND_HDR(m,HNAME) pjsip_msg_find_hdr(m, PJSIP_H_##HNAME, NULL)
+
+ from_hdr = (const pjsip_from_hdr*) FIND_HDR(req_tdata->msg, FROM);
+ PJ_ASSERT_ON_FAIL(from_hdr != NULL, goto on_missing_hdr);
+
+ to_hdr = (const pjsip_to_hdr*) FIND_HDR(req_tdata->msg, TO);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+ cid_hdr = (const pjsip_cid_hdr*) FIND_HDR(req_tdata->msg, CALL_ID);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+ cseq_hdr = (const pjsip_cseq_hdr*) FIND_HDR(req_tdata->msg, CSEQ);
+ PJ_ASSERT_ON_FAIL(to_hdr != NULL, goto on_missing_hdr);
+
+# undef FIND_HDR
+
+ /* Create new request message from the headers. */
+ status = pjsip_endpt_create_request_from_hdr(endpt,
+ pjsip_get_cancel_method(),
+ req_tdata->msg->line.req.uri,
+ from_hdr, to_hdr,
+ NULL, cid_hdr,
+ cseq_hdr->cseq, NULL,
+ &cancel_tdata);
+
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Clear Via headers in the new request. */
+ while ((via=(pjsip_hdr*)pjsip_msg_find_hdr(cancel_tdata->msg, PJSIP_H_VIA, NULL)) != NULL)
+ pj_list_erase(via);
+
+
+ /* Must only have single Via which matches the top-most Via in the
+ * request being cancelled.
+ */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_VIA, NULL);
+ if (hdr) {
+ pjsip_msg_insert_first_hdr(cancel_tdata->msg,
+ (pjsip_hdr*)pjsip_hdr_clone(cancel_tdata->pool, hdr));
+ }
+
+ /* If the original request has Route header, the CANCEL request must also
+ * has exactly the same.
+ * Copy "Route" header from the request.
+ */
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg, PJSIP_H_ROUTE, NULL);
+ while (hdr != NULL) {
+ pjsip_msg_add_hdr(cancel_tdata->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(cancel_tdata->pool, hdr));
+ hdr = hdr->next;
+ if (hdr != &req_tdata->msg->hdr)
+ hdr = (pjsip_hdr*) pjsip_msg_find_hdr(req_tdata->msg,
+ PJSIP_H_ROUTE, hdr);
+ else
+ break;
+ }
+
+ /* Must also copy the saved strict route header, otherwise CANCEL will be
+ * sent with swapped Route and request URI!
+ */
+ if (req_tdata->saved_strict_route) {
+ cancel_tdata->saved_strict_route = (pjsip_route_hdr*)
+ pjsip_hdr_clone(cancel_tdata->pool, req_tdata->saved_strict_route);
+ }
+
+ /* Copy the destination host name from the original request */
+ pj_strdup(cancel_tdata->pool, &cancel_tdata->dest_info.name,
+ &req_tdata->dest_info.name);
+
+ /* Finally copy the destination info from the original request */
+ pj_memcpy(&cancel_tdata->dest_info, &req_tdata->dest_info,
+ sizeof(req_tdata->dest_info));
+
+ /* Done.
+ * Return the transmit buffer containing the CANCEL request.
+ */
+ *p_tdata = cancel_tdata;
+ return PJ_SUCCESS;
+
+on_missing_hdr:
+ if (cancel_tdata)
+ pjsip_tx_data_dec_ref(cancel_tdata);
+ return PJSIP_EMISSINGHDR;
+}
+
+
+/* Fill-up destination information from a target URI */
+static pj_status_t get_dest_info(const pjsip_uri *target_uri,
+ pj_pool_t *pool,
+ pjsip_host_info *dest_info)
+{
+ /* The target URI must be a SIP/SIPS URL so we can resolve it's address.
+ * Otherwise we're in trouble (i.e. there's no host part in tel: URL).
+ */
+ pj_bzero(dest_info, sizeof(*dest_info));
+
+ if (PJSIP_URI_SCHEME_IS_SIPS(target_uri)) {
+ pjsip_uri *uri = (pjsip_uri*) target_uri;
+ const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+ unsigned flag;
+
+ dest_info->flag |= (PJSIP_TRANSPORT_SECURE | PJSIP_TRANSPORT_RELIABLE);
+ if (url->maddr_param.slen)
+ pj_strdup(pool, &dest_info->addr.host, &url->maddr_param);
+ else
+ pj_strdup(pool, &dest_info->addr.host, &url->host);
+ dest_info->addr.port = url->port;
+ dest_info->type =
+ pjsip_transport_get_type_from_name(&url->transport_param);
+ /* Double-check that the transport parameter match.
+ * Sample case: sips:host;transport=tcp
+ * See https://trac.pjsip.org/repos/ticket/1319
+ */
+ flag = pjsip_transport_get_flag_from_type(dest_info->type);
+ if ((flag & dest_info->flag) != dest_info->flag) {
+ pjsip_transport_type_e t;
+
+ t = pjsip_transport_get_type_from_flag(dest_info->flag);
+ if (t != PJSIP_TRANSPORT_UNSPECIFIED)
+ dest_info->type = t;
+ }
+
+ } else if (PJSIP_URI_SCHEME_IS_SIP(target_uri)) {
+ pjsip_uri *uri = (pjsip_uri*) target_uri;
+ const pjsip_sip_uri *url=(const pjsip_sip_uri*)pjsip_uri_get_uri(uri);
+ if (url->maddr_param.slen)
+ pj_strdup(pool, &dest_info->addr.host, &url->maddr_param);
+ else
+ pj_strdup(pool, &dest_info->addr.host, &url->host);
+ dest_info->addr.port = url->port;
+ dest_info->type =
+ pjsip_transport_get_type_from_name(&url->transport_param);
+ dest_info->flag =
+ pjsip_transport_get_flag_from_type(dest_info->type);
+ } else {
+ /* Should have never reached here; app should have configured route
+ * set when sending to tel: URI
+ pj_assert(!"Unsupported URI scheme!");
+ */
+ PJ_TODO(SUPPORT_REQUEST_ADDR_RESOLUTION_FOR_TEL_URI);
+ return PJSIP_ENOROUTESET;
+ }
+
+ /* Handle IPv6 (http://trac.pjsip.org/repos/ticket/861) */
+ if (dest_info->type != PJSIP_TRANSPORT_UNSPECIFIED &&
+ pj_strchr(&dest_info->addr.host, ':'))
+ {
+ dest_info->type = (pjsip_transport_type_e)
+ ((int)dest_info->type | PJSIP_TRANSPORT_IPV6);
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Find which destination to be used to send the request message, based
+ * on the request URI and Route headers in the message. The procedure
+ * used here follows the guidelines on sending the request in RFC 3261
+ * chapter 8.1.2.
+ */
+PJ_DEF(pj_status_t) pjsip_get_request_dest(const pjsip_tx_data *tdata,
+ pjsip_host_info *dest_info )
+{
+ const pjsip_uri *target_uri;
+ const pjsip_route_hdr *first_route_hdr;
+
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+ PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL);
+
+ /* Get the first "Route" header from the message.
+ */
+ first_route_hdr = (const pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+ if (first_route_hdr) {
+ target_uri = first_route_hdr->name_addr.uri;
+ } else {
+ target_uri = tdata->msg->line.req.uri;
+ }
+
+ return get_dest_info(target_uri, (pj_pool_t*)tdata->pool, dest_info);
+}
+
+
+/*
+ * Process route-set found in the request and calculate
+ * the destination to be used to send the request message, based
+ * on the request URI and Route headers in the message. The procedure
+ * used here follows the guidelines on sending the request in RFC 3261
+ * chapter 8.1.2.
+ */
+PJ_DEF(pj_status_t) pjsip_process_route_set(pjsip_tx_data *tdata,
+ pjsip_host_info *dest_info )
+{
+ const pjsip_uri *new_request_uri, *target_uri;
+ const pjsip_name_addr *topmost_route_uri;
+ pjsip_route_hdr *first_route_hdr, *last_route_hdr;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+ PJ_ASSERT_RETURN(dest_info != NULL, PJ_EINVAL);
+
+ /* If the request contains strict route, check that the strict route
+ * has been restored to its original values before processing the
+ * route set. The strict route is restored to the original values
+ * with pjsip_restore_strict_route_set(). If caller did not restore
+ * the strict route before calling this function, we need to call it
+ * here, or otherwise the strict-route and Request-URI will be swapped
+ * twice!
+ */
+ if (tdata->saved_strict_route != NULL) {
+ pjsip_restore_strict_route_set(tdata);
+ }
+ PJ_ASSERT_RETURN(tdata->saved_strict_route==NULL, PJ_EBUG);
+
+ /* Find the first and last "Route" headers from the message. */
+ last_route_hdr = first_route_hdr = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+ if (first_route_hdr) {
+ topmost_route_uri = &first_route_hdr->name_addr;
+ while (last_route_hdr->next != (void*)&tdata->msg->hdr) {
+ pjsip_route_hdr *hdr;
+ hdr = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE,
+ last_route_hdr->next);
+ if (!hdr)
+ break;
+ last_route_hdr = hdr;
+ }
+ } else {
+ topmost_route_uri = NULL;
+ }
+
+ /* If Route headers exist, and the first element indicates loose-route,
+ * the URI is taken from the Request-URI, and we keep all existing Route
+ * headers intact.
+ * If Route headers exist, and the first element DOESN'T indicate loose
+ * route, the URI is taken from the first Route header, and remove the
+ * first Route header from the message.
+ * Otherwise if there's no Route headers, the URI is taken from the
+ * Request-URI.
+ */
+ if (topmost_route_uri) {
+ pj_bool_t has_lr_param;
+
+ if (PJSIP_URI_SCHEME_IS_SIP(topmost_route_uri) ||
+ PJSIP_URI_SCHEME_IS_SIPS(topmost_route_uri))
+ {
+ const pjsip_sip_uri *url = (const pjsip_sip_uri*)
+ pjsip_uri_get_uri((const void*)topmost_route_uri);
+ has_lr_param = url->lr_param;
+ } else {
+ has_lr_param = 0;
+ }
+
+ if (has_lr_param) {
+ new_request_uri = tdata->msg->line.req.uri;
+ /* We shouldn't need to delete topmost Route if it has lr param.
+ * But seems like it breaks some proxy implementation, so we
+ * delete it anyway.
+ */
+ /*
+ pj_list_erase(first_route_hdr);
+ if (first_route_hdr == last_route_hdr)
+ last_route_hdr = NULL;
+ */
+ } else {
+ new_request_uri = (const pjsip_uri*)
+ pjsip_uri_get_uri((pjsip_uri*)topmost_route_uri);
+ pj_list_erase(first_route_hdr);
+ tdata->saved_strict_route = first_route_hdr;
+ if (first_route_hdr == last_route_hdr)
+ first_route_hdr = last_route_hdr = NULL;
+ }
+
+ target_uri = (pjsip_uri*)topmost_route_uri;
+
+ } else {
+ target_uri = new_request_uri = tdata->msg->line.req.uri;
+ }
+
+ /* Fill up the destination host/port from the URI. */
+ status = get_dest_info(target_uri, tdata->pool, dest_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* If target URI is different than request URI, replace
+ * request URI add put the original URI in the last Route header.
+ */
+ if (new_request_uri && new_request_uri!=tdata->msg->line.req.uri) {
+ pjsip_route_hdr *route = pjsip_route_hdr_create(tdata->pool);
+ route->name_addr.uri = (pjsip_uri*)
+ pjsip_uri_get_uri(tdata->msg->line.req.uri);
+ if (last_route_hdr)
+ pj_list_insert_after(last_route_hdr, route);
+ else
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)route);
+ tdata->msg->line.req.uri = (pjsip_uri*)new_request_uri;
+ }
+
+ /* Success. */
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Swap the request URI and strict route back to the original position
+ * before #pjsip_process_route_set() function is called. This function
+ * should only used internally by PJSIP client authentication module.
+ */
+PJ_DEF(void) pjsip_restore_strict_route_set(pjsip_tx_data *tdata)
+{
+ pjsip_route_hdr *first_route_hdr, *last_route_hdr;
+
+ /* Check if we have found strict route before */
+ if (tdata->saved_strict_route == NULL) {
+ /* This request doesn't contain strict route */
+ return;
+ }
+
+ /* Find the first "Route" headers from the message. */
+ first_route_hdr = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE, NULL);
+
+ if (first_route_hdr == NULL) {
+ /* User has modified message route? We don't expect this! */
+ pj_assert(!"Message route was modified?");
+ tdata->saved_strict_route = NULL;
+ return;
+ }
+
+ /* Find last Route header */
+ last_route_hdr = first_route_hdr;
+ while (last_route_hdr->next != (void*)&tdata->msg->hdr) {
+ pjsip_route_hdr *hdr;
+ hdr = (pjsip_route_hdr*)
+ pjsip_msg_find_hdr(tdata->msg, PJSIP_H_ROUTE,
+ last_route_hdr->next);
+ if (!hdr)
+ break;
+ last_route_hdr = hdr;
+ }
+
+ /* Put the last Route header as request URI, delete last Route
+ * header, and insert the saved strict route as the first Route.
+ */
+ tdata->msg->line.req.uri = last_route_hdr->name_addr.uri;
+ pj_list_insert_before(first_route_hdr, tdata->saved_strict_route);
+ pj_list_erase(last_route_hdr);
+
+ /* Reset */
+ tdata->saved_strict_route = NULL;
+}
+
+
+/* Transport callback for sending stateless request.
+ * This is one of the most bizzare function in pjsip, so
+ * good luck if you happen to debug this function!!
+ */
+static void stateless_send_transport_cb( void *token,
+ pjsip_tx_data *tdata,
+ pj_ssize_t sent )
+{
+ pjsip_send_state *stateless_data = (pjsip_send_state*) token;
+
+ PJ_UNUSED_ARG(tdata);
+ pj_assert(tdata == stateless_data->tdata);
+
+ for (;;) {
+ pj_status_t status;
+ pj_bool_t cont;
+
+ pj_sockaddr_t *cur_addr;
+ pjsip_transport_type_e cur_addr_type;
+ int cur_addr_len;
+
+ pjsip_via_hdr *via;
+
+ if (sent == -PJ_EPENDING) {
+ /* This is the initial process.
+ * When the process started, this function will be called by
+ * stateless_send_resolver_callback() with sent argument set to
+ * -PJ_EPENDING.
+ */
+ cont = PJ_TRUE;
+ } else {
+ /* There are two conditions here:
+ * (1) Message is sent (i.e. sent > 0),
+ * (2) Failure (i.e. sent <= 0)
+ */
+ cont = (sent > 0) ? PJ_FALSE :
+ (tdata->dest_info.cur_addr<tdata->dest_info.addr.count-1);
+ if (stateless_data->app_cb) {
+ (*stateless_data->app_cb)(stateless_data, sent, &cont);
+ } else {
+ /* Doesn't have application callback.
+ * Terminate the process.
+ */
+ cont = PJ_FALSE;
+ }
+ }
+
+ /* Finished with this transport. */
+ if (stateless_data->cur_transport) {
+ pjsip_transport_dec_ref(stateless_data->cur_transport);
+ stateless_data->cur_transport = NULL;
+ }
+
+ /* Done if application doesn't want to continue. */
+ if (sent > 0 || !cont) {
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Try next address, if any, and only when this is not the
+ * first invocation.
+ */
+ if (sent != -PJ_EPENDING) {
+ tdata->dest_info.cur_addr++;
+ }
+
+ /* Have next address? */
+ if (tdata->dest_info.cur_addr >= tdata->dest_info.addr.count) {
+ /* This only happens when a rather buggy application has
+ * sent 'cont' to PJ_TRUE when the initial value was PJ_FALSE.
+ * In this case just stop the processing; we don't need to
+ * call the callback again as application has been informed
+ * before.
+ */
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Keep current server address information handy. */
+ cur_addr = &tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr;
+ cur_addr_type = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].type;
+ cur_addr_len = tdata->dest_info.addr.entry[tdata->dest_info.cur_addr].addr_len;
+
+ /* Acquire transport. */
+ status = pjsip_endpt_acquire_transport2(stateless_data->endpt,
+ cur_addr_type,
+ cur_addr,
+ cur_addr_len,
+ &tdata->tp_sel,
+ tdata,
+ &stateless_data->cur_transport);
+ if (status != PJ_SUCCESS) {
+ sent = -status;
+ continue;
+ }
+
+ /* Modify Via header. */
+ via = (pjsip_via_hdr*) pjsip_msg_find_hdr( tdata->msg,
+ PJSIP_H_VIA, NULL);
+ if (!via) {
+ /* Shouldn't happen if request was created with PJSIP API!
+ * But we handle the case anyway for robustness.
+ */
+ pj_assert(!"Via header not found!");
+ via = pjsip_via_hdr_create(tdata->pool);
+ pjsip_msg_insert_first_hdr(tdata->msg, (pjsip_hdr*)via);
+ }
+
+ if (via->branch_param.slen == 0) {
+ pj_str_t tmp;
+ via->branch_param.ptr = (char*)pj_pool_alloc(tdata->pool,
+ PJSIP_MAX_BRANCH_LEN);
+ via->branch_param.slen = PJSIP_MAX_BRANCH_LEN;
+ pj_memcpy(via->branch_param.ptr, PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN);
+ tmp.ptr = via->branch_param.ptr + PJSIP_RFC3261_BRANCH_LEN + 2;
+ *(tmp.ptr-2) = 80; *(tmp.ptr-1) = 106;
+ pj_generate_unique_string(&tmp);
+ }
+
+ via->transport = pj_str(stateless_data->cur_transport->type_name);
+ if (tdata->via_addr.host.slen > 0 &&
+ tdata->via_tp == (void *)stateless_data->cur_transport)
+ {
+ via->sent_by = tdata->via_addr;
+ } else {
+ via->sent_by = stateless_data->cur_transport->local_name;
+ }
+ via->rport_param = pjsip_cfg()->endpt.disable_rport ? -1 : 0;
+
+ pjsip_tx_data_invalidate_msg(tdata);
+
+ /* Send message using this transport. */
+ status = pjsip_transport_send( stateless_data->cur_transport,
+ tdata,
+ cur_addr,
+ cur_addr_len,
+ stateless_data,
+ &stateless_send_transport_cb);
+ if (status == PJ_SUCCESS) {
+ /* Recursively call this function. */
+ sent = tdata->buf.cur - tdata->buf.start;
+ stateless_send_transport_cb( stateless_data, tdata, sent );
+ return;
+ } else if (status == PJ_EPENDING) {
+ /* This callback will be called later. */
+ return;
+ } else {
+ /* Recursively call this function. */
+ sent = -status;
+ stateless_send_transport_cb( stateless_data, tdata, sent );
+ return;
+ }
+ }
+
+}
+
+/* Resolver callback for sending stateless request. */
+static void
+stateless_send_resolver_callback( pj_status_t status,
+ void *token,
+ const struct pjsip_server_addresses *addr)
+{
+ pjsip_send_state *stateless_data = (pjsip_send_state*) token;
+ pjsip_tx_data *tdata = stateless_data->tdata;
+
+ /* Fail on server resolution. */
+ if (status != PJ_SUCCESS) {
+ if (stateless_data->app_cb) {
+ pj_bool_t cont = PJ_FALSE;
+ (*stateless_data->app_cb)(stateless_data, -status, &cont);
+ }
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Copy server addresses */
+ if (addr && addr != &tdata->dest_info.addr) {
+ pj_memcpy( &tdata->dest_info.addr, addr,
+ sizeof(pjsip_server_addresses));
+ }
+ pj_assert(tdata->dest_info.addr.count != 0);
+
+ /* RFC 3261 section 18.1.1:
+ * If a request is within 200 bytes of the path MTU, or if it is larger
+ * than 1300 bytes and the path MTU is unknown, the request MUST be sent
+ * using an RFC 2914 [43] congestion controlled transport protocol, such
+ * as TCP.
+ */
+ if (pjsip_cfg()->endpt.disable_tcp_switch==0 &&
+ tdata->msg->type == PJSIP_REQUEST_MSG &&
+ tdata->dest_info.addr.count > 0 &&
+ tdata->dest_info.addr.entry[0].type == PJSIP_TRANSPORT_UDP)
+ {
+ int len;
+
+ /* Encode the request */
+ status = pjsip_tx_data_encode(tdata);
+ if (status != PJ_SUCCESS) {
+ if (stateless_data->app_cb) {
+ pj_bool_t cont = PJ_FALSE;
+ (*stateless_data->app_cb)(stateless_data, -status, &cont);
+ }
+ pjsip_tx_data_dec_ref(tdata);
+ return;
+ }
+
+ /* Check if request message is larger than 1300 bytes. */
+ len = tdata->buf.cur - tdata->buf.start;
+ if (len >= PJSIP_UDP_SIZE_THRESHOLD) {
+ int i;
+ int count = tdata->dest_info.addr.count;
+
+ PJ_LOG(5,(THIS_FILE, "%s exceeds UDP size threshold (%u), "
+ "sending with TCP",
+ pjsip_tx_data_get_info(tdata),
+ PJSIP_UDP_SIZE_THRESHOLD));
+
+ /* Insert "TCP version" of resolved UDP addresses at the
+ * beginning.
+ */
+ if (count * 2 > PJSIP_MAX_RESOLVED_ADDRESSES)
+ count = PJSIP_MAX_RESOLVED_ADDRESSES / 2;
+ for (i = 0; i < count; ++i) {
+ pj_memcpy(&tdata->dest_info.addr.entry[i+count],
+ &tdata->dest_info.addr.entry[i],
+ sizeof(tdata->dest_info.addr.entry[0]));
+ tdata->dest_info.addr.entry[i].type = PJSIP_TRANSPORT_TCP;
+ }
+ tdata->dest_info.addr.count = count * 2;
+ }
+ }
+
+ /* Process the addresses. */
+ stateless_send_transport_cb( stateless_data, tdata, -PJ_EPENDING);
+}
+
+/*
+ * Send stateless request.
+ * The sending process consists of several stages:
+ * - determine which host to contact (#pjsip_get_request_addr).
+ * - resolve the host (#pjsip_endpt_resolve)
+ * - establish transport (#pjsip_endpt_acquire_transport)
+ * - send the message (#pjsip_transport_send)
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_request_stateless(pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata,
+ void *token,
+ pjsip_send_callback cb)
+{
+ pjsip_host_info dest_info;
+ pjsip_send_state *stateless_data;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && tdata, PJ_EINVAL);
+
+ /* Get destination name to contact. */
+ status = pjsip_process_route_set(tdata, &dest_info);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Keep stateless data. */
+ stateless_data = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_send_state);
+ stateless_data->token = token;
+ stateless_data->endpt = endpt;
+ stateless_data->tdata = tdata;
+ stateless_data->app_cb = cb;
+
+ /* If destination info has not been initialized (this applies for most
+ * all requests except CANCEL), resolve destination host. The processing
+ * then resumed when the resolving callback is called. For CANCEL, the
+ * destination info must have been copied from the original INVITE so
+ * proceed to sending the request directly.
+ */
+ if (tdata->dest_info.addr.count == 0) {
+ /* Copy the destination host name to TX data */
+ pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
+
+ pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, stateless_data,
+ &stateless_send_resolver_callback);
+ } else {
+ PJ_LOG(5,(THIS_FILE, "%s: skipping target resolution because "
+ "address is already set",
+ pjsip_tx_data_get_info(tdata)));
+ stateless_send_resolver_callback(PJ_SUCCESS, stateless_data,
+ &tdata->dest_info.addr);
+ }
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Send raw data to a destination.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_raw( pjsip_endpoint *endpt,
+ pjsip_transport_type_e tp_type,
+ const pjsip_tpselector *sel,
+ const void *raw_data,
+ pj_size_t data_len,
+ const pj_sockaddr_t *addr,
+ int addr_len,
+ void *token,
+ pjsip_tp_send_callback cb)
+{
+ return pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(endpt), tp_type, sel,
+ NULL, raw_data, data_len, addr, addr_len,
+ token, cb);
+}
+
+
+/* Callback data for sending raw data */
+struct send_raw_data
+{
+ pjsip_endpoint *endpt;
+ pjsip_tx_data *tdata;
+ pjsip_tpselector *sel;
+ void *app_token;
+ pjsip_tp_send_callback app_cb;
+};
+
+
+/* Resolver callback for sending raw data. */
+static void send_raw_resolver_callback( pj_status_t status,
+ void *token,
+ const pjsip_server_addresses *addr)
+{
+ struct send_raw_data *sraw_data = (struct send_raw_data*) token;
+
+ if (status != PJ_SUCCESS) {
+ if (sraw_data->app_cb) {
+ (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
+ -status);
+ }
+ } else {
+ pj_size_t data_len;
+
+ pj_assert(addr->count != 0);
+
+ /* Avoid tdata destroyed by pjsip_tpmgr_send_raw(). */
+ pjsip_tx_data_add_ref(sraw_data->tdata);
+
+ data_len = sraw_data->tdata->buf.cur - sraw_data->tdata->buf.start;
+ status = pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(sraw_data->endpt),
+ addr->entry[0].type,
+ sraw_data->sel, sraw_data->tdata,
+ sraw_data->tdata->buf.start, data_len,
+ &addr->entry[0].addr,
+ addr->entry[0].addr_len,
+ sraw_data->app_token,
+ sraw_data->app_cb);
+ if (status == PJ_SUCCESS) {
+ (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
+ data_len);
+ } else if (status != PJ_EPENDING) {
+ (*sraw_data->app_cb)(sraw_data->app_token, sraw_data->tdata,
+ -status);
+ }
+ }
+
+ if (sraw_data->sel) {
+ pjsip_tpselector_dec_ref(sraw_data->sel);
+ }
+ pjsip_tx_data_dec_ref(sraw_data->tdata);
+}
+
+
+/*
+ * Send raw data to the specified destination URI.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_raw_to_uri(pjsip_endpoint *endpt,
+ const pj_str_t *p_dst_uri,
+ const pjsip_tpselector *sel,
+ const void *raw_data,
+ pj_size_t data_len,
+ void *token,
+ pjsip_tp_send_callback cb)
+{
+ pjsip_tx_data *tdata;
+ struct send_raw_data *sraw_data;
+ pj_str_t dst_uri;
+ pjsip_uri *uri;
+ pjsip_host_info dest_info;
+ pj_status_t status;
+
+ /* Allocate buffer */
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Duplicate URI since parser requires URI to be NULL terminated */
+ pj_strdup_with_null(tdata->pool, &dst_uri, p_dst_uri);
+
+ /* Parse URI */
+ uri = pjsip_parse_uri(tdata->pool, dst_uri.ptr, dst_uri.slen, 0);
+ if (uri == NULL) {
+ pjsip_tx_data_dec_ref(tdata);
+ return PJSIP_EINVALIDURI;
+ }
+
+ /* Build destination info. */
+ status = get_dest_info(uri, tdata->pool, &dest_info);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ /* Copy data (note: data_len may be zero!) */
+ tdata->buf.start = (char*) pj_pool_alloc(tdata->pool, data_len+1);
+ tdata->buf.end = tdata->buf.start + data_len + 1;
+ if (data_len)
+ pj_memcpy(tdata->buf.start, raw_data, data_len);
+ tdata->buf.cur = tdata->buf.start + data_len;
+
+ /* Init send_raw_data */
+ sraw_data = PJ_POOL_ZALLOC_T(tdata->pool, struct send_raw_data);
+ sraw_data->endpt = endpt;
+ sraw_data->tdata = tdata;
+ sraw_data->app_token = token;
+ sraw_data->app_cb = cb;
+
+ if (sel) {
+ sraw_data->sel = PJ_POOL_ALLOC_T(tdata->pool, pjsip_tpselector);
+ pj_memcpy(sraw_data->sel, sel, sizeof(pjsip_tpselector));
+ pjsip_tpselector_add_ref(sraw_data->sel);
+ }
+
+ /* Copy the destination host name to TX data */
+ pj_strdup(tdata->pool, &tdata->dest_info.name, &dest_info.addr.host);
+
+ /* Resolve destination host.
+ * The processing then resumed when the resolving callback is called.
+ */
+ pjsip_endpt_resolve( endpt, tdata->pool, &dest_info, sraw_data,
+ &send_raw_resolver_callback);
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Determine which address (and transport) to use to send response message
+ * based on the received request. This function follows the specification
+ * in section 18.2.2 of RFC 3261 and RFC 3581 for calculating the destination
+ * address and transport.
+ */
+PJ_DEF(pj_status_t) pjsip_get_response_addr( pj_pool_t *pool,
+ pjsip_rx_data *rdata,
+ pjsip_response_addr *res_addr )
+{
+ pjsip_transport *src_transport = rdata->tp_info.transport;
+
+ /* Check arguments. */
+ PJ_ASSERT_RETURN(pool && rdata && res_addr, PJ_EINVAL);
+
+ /* rdata must be a request message! */
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJ_EINVAL);
+
+ /* All requests must have "received" parameter.
+ * This must always be done in transport layer.
+ */
+ pj_assert(rdata->msg_info.via->recvd_param.slen != 0);
+
+ /* Do the calculation based on RFC 3261 Section 18.2.2 and RFC 3581 */
+
+ if (PJSIP_TRANSPORT_IS_RELIABLE(src_transport)) {
+ /* For reliable protocol such as TCP or SCTP, or TLS over those, the
+ * response MUST be sent using the existing connection to the source
+ * of the original request that created the transaction, if that
+ * connection is still open.
+ * If that connection is no longer open, the server SHOULD open a
+ * connection to the IP address in the received parameter, if present,
+ * using the port in the sent-by value, or the default port for that
+ * transport, if no port is specified.
+ * If that connection attempt fails, the server SHOULD use the
+ * procedures in [4] for servers in order to determine the IP address
+ * and port to open the connection and send the response to.
+ */
+ res_addr->transport = rdata->tp_info.transport;
+ pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr,
+ rdata->pkt_info.src_addr_len);
+ res_addr->addr_len = rdata->pkt_info.src_addr_len;
+ res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
+ res_addr->dst_host.flag = src_transport->flag;
+ pj_strdup( pool, &res_addr->dst_host.addr.host,
+ &rdata->msg_info.via->recvd_param);
+ res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
+ if (res_addr->dst_host.addr.port == 0) {
+ res_addr->dst_host.addr.port =
+ pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
+ }
+
+ } else if (rdata->msg_info.via->maddr_param.slen) {
+ /* Otherwise, if the Via header field value contains a maddr parameter,
+ * the response MUST be forwarded to the address listed there, using
+ * the port indicated in sent-by, or port 5060 if none is present.
+ * If the address is a multicast address, the response SHOULD be sent
+ * using the TTL indicated in the ttl parameter, or with a TTL of 1 if
+ * that parameter is not present.
+ */
+ res_addr->transport = NULL;
+ res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
+ res_addr->dst_host.flag = src_transport->flag;
+ pj_strdup( pool, &res_addr->dst_host.addr.host,
+ &rdata->msg_info.via->maddr_param);
+ res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
+ if (res_addr->dst_host.addr.port == 0)
+ res_addr->dst_host.addr.port = 5060;
+
+ } else if (rdata->msg_info.via->rport_param >= 0) {
+ /* There is both a "received" parameter and an "rport" parameter,
+ * the response MUST be sent to the IP address listed in the "received"
+ * parameter, and the port in the "rport" parameter.
+ * The response MUST be sent from the same address and port that the
+ * corresponding request was received on.
+ */
+ res_addr->transport = rdata->tp_info.transport;
+ pj_memcpy(&res_addr->addr, &rdata->pkt_info.src_addr,
+ rdata->pkt_info.src_addr_len);
+ res_addr->addr_len = rdata->pkt_info.src_addr_len;
+ res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
+ res_addr->dst_host.flag = src_transport->flag;
+ pj_strdup( pool, &res_addr->dst_host.addr.host,
+ &rdata->msg_info.via->recvd_param);
+ res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
+ if (res_addr->dst_host.addr.port == 0) {
+ res_addr->dst_host.addr.port =
+ pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
+ }
+
+ } else {
+ res_addr->transport = NULL;
+ res_addr->dst_host.type=(pjsip_transport_type_e)src_transport->key.type;
+ res_addr->dst_host.flag = src_transport->flag;
+ pj_strdup( pool, &res_addr->dst_host.addr.host,
+ &rdata->msg_info.via->recvd_param);
+ res_addr->dst_host.addr.port = rdata->msg_info.via->sent_by.port;
+ if (res_addr->dst_host.addr.port == 0) {
+ res_addr->dst_host.addr.port =
+ pjsip_transport_get_default_port_for_type(res_addr->dst_host.type);
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Callback called by transport during send_response.
+ */
+static void send_response_transport_cb(void *token, pjsip_tx_data *tdata,
+ pj_ssize_t sent)
+{
+ pjsip_send_state *send_state = (pjsip_send_state*) token;
+ pj_bool_t cont = PJ_FALSE;
+
+ /* Call callback, if any. */
+ if (send_state->app_cb)
+ (*send_state->app_cb)(send_state, sent, &cont);
+
+ /* Decrement transport reference counter. */
+ pjsip_transport_dec_ref(send_state->cur_transport);
+
+ /* Decrement transmit data ref counter. */
+ pjsip_tx_data_dec_ref(tdata);
+}
+
+/*
+ * Resolver calback during send_response.
+ */
+static void send_response_resolver_cb( pj_status_t status, void *token,
+ const pjsip_server_addresses *addr )
+{
+ pjsip_send_state *send_state = (pjsip_send_state*) token;
+
+ if (status != PJ_SUCCESS) {
+ if (send_state->app_cb) {
+ pj_bool_t cont = PJ_FALSE;
+ (*send_state->app_cb)(send_state, -status, &cont);
+ }
+ pjsip_tx_data_dec_ref(send_state->tdata);
+ return;
+ }
+
+ /* Only handle the first address resolved. */
+
+ /* Acquire transport. */
+ status = pjsip_endpt_acquire_transport2(send_state->endpt,
+ addr->entry[0].type,
+ &addr->entry[0].addr,
+ addr->entry[0].addr_len,
+ &send_state->tdata->tp_sel,
+ send_state->tdata,
+ &send_state->cur_transport);
+ if (status != PJ_SUCCESS) {
+ if (send_state->app_cb) {
+ pj_bool_t cont = PJ_FALSE;
+ (*send_state->app_cb)(send_state, -status, &cont);
+ }
+ pjsip_tx_data_dec_ref(send_state->tdata);
+ return;
+ }
+
+ /* Update address in send_state. */
+ pj_memcpy(&send_state->tdata->dest_info.addr, addr, sizeof(*addr));
+
+ /* Send response using the transoprt. */
+ status = pjsip_transport_send( send_state->cur_transport,
+ send_state->tdata,
+ &addr->entry[0].addr,
+ addr->entry[0].addr_len,
+ send_state,
+ &send_response_transport_cb);
+ if (status == PJ_SUCCESS) {
+ pj_ssize_t sent = send_state->tdata->buf.cur -
+ send_state->tdata->buf.start;
+ send_response_transport_cb(send_state, send_state->tdata, sent);
+
+ } else if (status == PJ_EPENDING) {
+ /* Transport callback will be called later. */
+ } else {
+ send_response_transport_cb(send_state, send_state->tdata, -status);
+ }
+}
+
+/*
+ * Send response.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt,
+ pjsip_response_addr *res_addr,
+ pjsip_tx_data *tdata,
+ void *token,
+ pjsip_send_callback cb)
+{
+ /* Determine which transports and addresses to send the response,
+ * based on Section 18.2.2 of RFC 3261.
+ */
+ pjsip_send_state *send_state;
+ pj_status_t status;
+
+ /* Create structure to keep the sending state. */
+ send_state = PJ_POOL_ZALLOC_T(tdata->pool, pjsip_send_state);
+ send_state->endpt = endpt;
+ send_state->tdata = tdata;
+ send_state->token = token;
+ send_state->app_cb = cb;
+
+ if (res_addr->transport != NULL) {
+ send_state->cur_transport = res_addr->transport;
+ pjsip_transport_add_ref(send_state->cur_transport);
+
+ status = pjsip_transport_send( send_state->cur_transport, tdata,
+ &res_addr->addr,
+ res_addr->addr_len,
+ send_state,
+ &send_response_transport_cb );
+ if (status == PJ_SUCCESS) {
+ pj_ssize_t sent = tdata->buf.cur - tdata->buf.start;
+ send_response_transport_cb(send_state, tdata, sent);
+ return PJ_SUCCESS;
+ } else if (status == PJ_EPENDING) {
+ /* Callback will be called later. */
+ return PJ_SUCCESS;
+ } else {
+ pjsip_transport_dec_ref(send_state->cur_transport);
+ return status;
+ }
+ } else {
+ /* Copy the destination host name to TX data */
+ pj_strdup(tdata->pool, &tdata->dest_info.name,
+ &res_addr->dst_host.addr.host);
+
+ pjsip_endpt_resolve(endpt, tdata->pool, &res_addr->dst_host,
+ send_state, &send_response_resolver_cb);
+ return PJ_SUCCESS;
+ }
+}
+
+/*
+ * Send response combo
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_send_response2( pjsip_endpoint *endpt,
+ pjsip_rx_data *rdata,
+ pjsip_tx_data *tdata,
+ void *token,
+ pjsip_send_callback cb)
+{
+ pjsip_response_addr res_addr;
+ pj_status_t status;
+
+ status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return PJ_SUCCESS;
+ }
+
+ status = pjsip_endpt_send_response(endpt, &res_addr, tdata, token, cb);
+ return status;
+}
+
+
+/*
+ * Send response
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_respond_stateless( pjsip_endpoint *endpt,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjsip_hdr *hdr_list,
+ const pjsip_msg_body *body)
+{
+ pj_status_t status;
+ pjsip_response_addr res_addr;
+ pjsip_tx_data *tdata;
+
+ /* Verify arguments. */
+ PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ /* Check that no UAS transaction has been created for this request.
+ * If UAS transaction has been created for this request, application
+ * MUST send the response statefully using that transaction.
+ */
+ PJ_ASSERT_RETURN(pjsip_rdata_get_tsx(rdata)==NULL, PJ_EINVALIDOP);
+
+ /* Create response message */
+ status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add the message headers, if any */
+ if (hdr_list) {
+ const pjsip_hdr *hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pjsip_msg_add_hdr(tdata->msg,
+ (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr) );
+ hdr = hdr->next;
+ }
+ }
+
+ /* Add the message body, if any. */
+ if (body) {
+ tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body );
+ if (tdata->msg->body == NULL) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+ }
+
+ /* Get where to send request. */
+ status = pjsip_get_response_addr( tdata->pool, rdata, &res_addr );
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ /* Send! */
+ status = pjsip_endpt_send_response( endpt, &res_addr, tdata, NULL, NULL );
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the event string from the event ID.
+ */
+PJ_DEF(const char *) pjsip_event_str(pjsip_event_id_e e)
+{
+ return event_str[e];
+}
+
diff --git a/pjsip/src/pjsip/sip_util_proxy.c b/pjsip/src/pjsip/sip_util_proxy.c
new file mode 100644
index 0000000..7a34534
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util_proxy.c
@@ -0,0 +1,389 @@
+/* $Id: sip_util_proxy.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 <pjsip/sip_util.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_msg.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/except.h>
+#include <pj/guid.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pjlib-util/md5.h>
+
+
+/**
+ * Clone the incoming SIP request or response message. A forwarding proxy
+ * typically would need to clone the incoming SIP message before processing
+ * the message.
+ *
+ * Once a transmit data is created, the reference counter is initialized to 1.
+ *
+ * @param endpt The endpoint instance.
+ * @param rdata The incoming SIP message.
+ * @param p_tdata Pointer to receive the transmit data containing
+ * the duplicated message.
+ *
+ * @return PJ_SUCCESS on success.
+ */
+/*
+PJ_DEF(pj_status_t) pjsip_endpt_clone_msg( pjsip_endpoint *endpt,
+ const pjsip_rx_data *rdata,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ tdata->msg = pjsip_msg_clone(tdata->pool, rdata->msg_info.msg);
+
+ pjsip_tx_data_add_ref(tdata);
+
+ *p_tdata = tdata;
+
+ return PJ_SUCCESS;
+}
+*/
+
+
+/*
+ * Create new request message to be forwarded upstream to new destination URI
+ * in uri.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_create_request_fwd(pjsip_endpoint *endpt,
+ pjsip_rx_data *rdata,
+ const pjsip_uri *uri,
+ const pj_str_t *branch,
+ unsigned options,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+ PJ_USE_EXCEPTION;
+
+
+ PJ_ASSERT_RETURN(endpt && rdata && p_tdata, PJ_EINVAL);
+ PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG,
+ PJSIP_ENOTREQUESTMSG);
+
+ PJ_UNUSED_ARG(options);
+
+
+ /* Request forwarding rule in RFC 3261 section 16.6:
+ *
+ * For each target, the proxy forwards the request following these
+ * steps:
+ *
+ * 1. Make a copy of the received request
+ * 2. Update the Request-URI
+ * 3. Update the Max-Forwards header field
+ * 4. Optionally add a Record-route header field value
+ * 5. Optionally add additional header fields
+ * 6. Postprocess routing information
+ * 7. Determine the next-hop address, port, and transport
+ * 8. Add a Via header field value
+ * 9. Add a Content-Length header field if necessary
+ * 10. Forward the new request
+ *
+ * Of these steps, we only do step 1-3, since the later will be
+ * done by application.
+ */
+
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Always increment ref counter to 1 */
+ pjsip_tx_data_add_ref(tdata);
+
+ /* Duplicate the request */
+ PJ_TRY {
+ pjsip_msg *dst;
+ const pjsip_msg *src = rdata->msg_info.msg;
+ const pjsip_hdr *hsrc;
+
+ /* Create the request */
+ tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_REQUEST_MSG);
+
+ /* Duplicate request method */
+ pjsip_method_copy(tdata->pool, &tdata->msg->line.req.method,
+ &src->line.req.method);
+
+ /* Set request URI */
+ if (uri) {
+ dst->line.req.uri = (pjsip_uri*)
+ pjsip_uri_clone(tdata->pool, uri);
+ } else {
+ dst->line.req.uri= (pjsip_uri*)
+ pjsip_uri_clone(tdata->pool, src->line.req.uri);
+ }
+
+ /* Clone ALL headers */
+ hsrc = src->hdr.next;
+ while (hsrc != &src->hdr) {
+
+ pjsip_hdr *hdst;
+
+ /* If this is the top-most Via header, insert our own before
+ * cloning the header.
+ */
+ if (hsrc == (pjsip_hdr*)rdata->msg_info.via) {
+ pjsip_via_hdr *hvia;
+ hvia = pjsip_via_hdr_create(tdata->pool);
+ if (branch)
+ pj_strdup(tdata->pool, &hvia->branch_param, branch);
+ else {
+ pj_str_t new_branch = pjsip_calculate_branch_id(rdata);
+ pj_strdup(tdata->pool, &hvia->branch_param, &new_branch);
+ }
+ pjsip_msg_add_hdr(dst, (pjsip_hdr*)hvia);
+
+ }
+ /* Skip Content-Type and Content-Length as these would be
+ * generated when the the message is printed.
+ */
+ else if (hsrc->type == PJSIP_H_CONTENT_LENGTH ||
+ hsrc->type == PJSIP_H_CONTENT_TYPE) {
+
+ hsrc = hsrc->next;
+ continue;
+
+ }
+#if 0
+ /* If this is the top-most Route header and it indicates loose
+ * route, remove the header.
+ */
+ else if (hsrc == (pjsip_hdr*)rdata->msg_info.route) {
+
+ const pjsip_route_hdr *hroute = (const pjsip_route_hdr*) hsrc;
+ const pjsip_sip_uri *sip_uri;
+
+ if (!PJSIP_URI_SCHEME_IS_SIP(hroute->name_addr.uri) &&
+ !PJSIP_URI_SCHEME_IS_SIPS(hroute->name_addr.uri))
+ {
+ /* This is a bad request! */
+ status = PJSIP_EINVALIDHDR;
+ goto on_error;
+ }
+
+ sip_uri = (pjsip_sip_uri*) hroute->name_addr.uri;
+
+ if (sip_uri->lr_param) {
+ /* Yes lr param is present, skip this Route header */
+ hsrc = hsrc->next;
+ continue;
+ }
+ }
+#endif
+
+ /* Clone the header */
+ hdst = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hsrc);
+
+ /* If this is Max-Forward header, decrement the value */
+ if (hdst->type == PJSIP_H_MAX_FORWARDS) {
+ pjsip_max_fwd_hdr *hmaxfwd = (pjsip_max_fwd_hdr*)hdst;
+ --hmaxfwd->ivalue;
+ }
+
+ /* Append header to new request */
+ pjsip_msg_add_hdr(dst, hdst);
+
+
+ hsrc = hsrc->next;
+ }
+
+ /* 16.6.3:
+ * If the copy does not contain a Max-Forwards header field, the
+ * proxy MUST add one with a field value, which SHOULD be 70.
+ */
+ if (rdata->msg_info.max_fwd == NULL) {
+ pjsip_max_fwd_hdr *hmaxfwd =
+ pjsip_max_fwd_hdr_create(tdata->pool, 70);
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hmaxfwd);
+ }
+
+ /* Clone request body */
+ if (src->body) {
+ dst->body = pjsip_msg_body_clone(tdata->pool, src->body);
+ }
+
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ PJ_END
+
+
+ /* Done */
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+
+on_error:
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+}
+
+
+PJ_DEF(pj_status_t) pjsip_endpt_create_response_fwd( pjsip_endpoint *endpt,
+ pjsip_rx_data *rdata,
+ unsigned options,
+ pjsip_tx_data **p_tdata)
+{
+ pjsip_tx_data *tdata;
+ pj_status_t status;
+ PJ_USE_EXCEPTION;
+
+ PJ_UNUSED_ARG(options);
+
+ status = pjsip_endpt_create_tdata(endpt, &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ pjsip_tx_data_add_ref(tdata);
+
+ PJ_TRY {
+ pjsip_msg *dst;
+ const pjsip_msg *src = rdata->msg_info.msg;
+ const pjsip_hdr *hsrc;
+
+ /* Create the request */
+ tdata->msg = dst = pjsip_msg_create(tdata->pool, PJSIP_RESPONSE_MSG);
+
+ /* Clone the status line */
+ dst->line.status.code = src->line.status.code;
+ pj_strdup(tdata->pool, &dst->line.status.reason,
+ &src->line.status.reason);
+
+ /* Duplicate all headers */
+ hsrc = src->hdr.next;
+ while (hsrc != &src->hdr) {
+
+ /* Skip Content-Type and Content-Length as these would be
+ * generated when the the message is printed.
+ */
+ if (hsrc->type == PJSIP_H_CONTENT_LENGTH ||
+ hsrc->type == PJSIP_H_CONTENT_TYPE) {
+
+ hsrc = hsrc->next;
+ continue;
+
+ }
+ /* Remove the first Via header */
+ else if (hsrc == (pjsip_hdr*) rdata->msg_info.via) {
+
+ hsrc = hsrc->next;
+ continue;
+ }
+
+ pjsip_msg_add_hdr(dst,
+ (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hsrc));
+
+ hsrc = hsrc->next;
+ }
+
+ /* Clone message body */
+ if (src->body)
+ dst->body = pjsip_msg_body_clone(tdata->pool, src->body);
+
+
+ }
+ PJ_CATCH_ANY {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ PJ_END;
+
+ *p_tdata = tdata;
+ return PJ_SUCCESS;
+
+on_error:
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+}
+
+
+static void digest2str(const unsigned char digest[], char *output)
+{
+ int i;
+ for (i = 0; i<16; ++i) {
+ pj_val_to_hex_digit(digest[i], output);
+ output += 2;
+ }
+}
+
+
+PJ_DEF(pj_str_t) pjsip_calculate_branch_id( pjsip_rx_data *rdata )
+{
+ pj_md5_context ctx;
+ pj_uint8_t digest[16];
+ pj_str_t branch;
+ pj_str_t rfc3261_branch = {PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN};
+
+ /* If incoming request does not have RFC 3261 branch value, create
+ * a branch value from GUID .
+ */
+ if (pj_strncmp(&rdata->msg_info.via->branch_param,
+ &rfc3261_branch, PJSIP_RFC3261_BRANCH_LEN) != 0 )
+ {
+ pj_str_t tmp;
+
+ branch.ptr = (char*)
+ pj_pool_alloc(rdata->tp_info.pool, PJSIP_MAX_BRANCH_LEN);
+ branch.slen = PJSIP_RFC3261_BRANCH_LEN;
+ pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID,
+ PJSIP_RFC3261_BRANCH_LEN);
+
+ tmp.ptr = branch.ptr + PJSIP_RFC3261_BRANCH_LEN + 2;
+ *(tmp.ptr-2) = (pj_int8_t)(branch.slen+73);
+ *(tmp.ptr-1) = (pj_int8_t)(branch.slen+99);
+ pj_generate_unique_string( &tmp );
+
+ branch.slen = PJSIP_MAX_BRANCH_LEN;
+ return branch;
+ }
+
+ /* Create branch ID for new request by calculating MD5 hash
+ * of the branch parameter in top-most Via header.
+ */
+ pj_md5_init(&ctx);
+ pj_md5_update(&ctx, (pj_uint8_t*)rdata->msg_info.via->branch_param.ptr,
+ rdata->msg_info.via->branch_param.slen);
+ pj_md5_final(&ctx, digest);
+
+ branch.ptr = (char*)
+ pj_pool_alloc(rdata->tp_info.pool,
+ 34 + PJSIP_RFC3261_BRANCH_LEN);
+ pj_memcpy(branch.ptr, PJSIP_RFC3261_BRANCH_ID, PJSIP_RFC3261_BRANCH_LEN);
+ branch.slen = PJSIP_RFC3261_BRANCH_LEN;
+ *(branch.ptr+PJSIP_RFC3261_BRANCH_LEN) = (pj_int8_t)(branch.slen+73);
+ *(branch.ptr+PJSIP_RFC3261_BRANCH_LEN+1) = (pj_int8_t)(branch.slen+99);
+ digest2str(digest, branch.ptr+PJSIP_RFC3261_BRANCH_LEN+2);
+ branch.slen = 34 + PJSIP_RFC3261_BRANCH_LEN;
+
+ return branch;
+}
+
+
diff --git a/pjsip/src/pjsip/sip_util_proxy_wrap.cpp b/pjsip/src/pjsip/sip_util_proxy_wrap.cpp
new file mode 100644
index 0000000..e77786f
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util_proxy_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_util_proxy_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_util_proxy.c"
diff --git a/pjsip/src/pjsip/sip_util_statefull.c b/pjsip/src/pjsip/sip_util_statefull.c
new file mode 100644
index 0000000..3212cab
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util_statefull.c
@@ -0,0 +1,192 @@
+/* $Id: sip_util_statefull.c 4169 2012-06-18 09:19:58Z 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 <pjsip/sip_util.h>
+#include <pjsip/sip_module.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_transaction.h>
+#include <pjsip/sip_event.h>
+#include <pjsip/sip_errno.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+struct tsx_data
+{
+ void *token;
+ void (*cb)(void*, pjsip_event*);
+};
+
+static void mod_util_on_tsx_state(pjsip_transaction*, pjsip_event*);
+
+/* This module will be registered in pjsip_endpt.c */
+
+pjsip_module mod_stateful_util =
+{
+ NULL, NULL, /* prev, next. */
+ { "mod-stateful-util", 17 }, /* Name. */
+ -1, /* Id */
+ PJSIP_MOD_PRIORITY_APPLICATION, /* 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() */
+ &mod_util_on_tsx_state, /* on_tsx_state() */
+};
+
+static void mod_util_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
+{
+ struct tsx_data *tsx_data;
+
+ /* Check if the module has been unregistered (see ticket #1535) and also
+ * verify the event type.
+ */
+ if (mod_stateful_util.id < 0 || event->type != PJSIP_EVENT_TSX_STATE)
+ return;
+
+ tsx_data = (struct tsx_data*) tsx->mod_data[mod_stateful_util.id];
+ if (tsx_data == NULL)
+ return;
+
+ if (tsx->status_code < 200)
+ return;
+
+ /* Call the callback, if any, and prevent the callback to be called again
+ * by clearing the transaction's module_data.
+ */
+ tsx->mod_data[mod_stateful_util.id] = NULL;
+
+ if (tsx_data->cb) {
+ (*tsx_data->cb)(tsx_data->token, event);
+ }
+}
+
+
+PJ_DEF(pj_status_t) pjsip_endpt_send_request( pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata,
+ pj_int32_t timeout,
+ void *token,
+ pjsip_endpt_send_callback cb)
+{
+ pjsip_transaction *tsx;
+ struct tsx_data *tsx_data;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt && tdata && (timeout==-1 || timeout>0), PJ_EINVAL);
+
+ /* Check that transaction layer module is registered to endpoint */
+ PJ_ASSERT_RETURN(mod_stateful_util.id != -1, PJ_EINVALIDOP);
+
+ PJ_UNUSED_ARG(timeout);
+
+ status = pjsip_tsx_create_uac(&mod_stateful_util, tdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ pjsip_tsx_set_transport(tsx, &tdata->tp_sel);
+
+ tsx_data = PJ_POOL_ALLOC_T(tsx->pool, struct tsx_data);
+ tsx_data->token = token;
+ tsx_data->cb = cb;
+
+ tsx->mod_data[mod_stateful_util.id] = tsx_data;
+
+ status = pjsip_tsx_send_msg(tsx, NULL);
+ if (status != PJ_SUCCESS)
+ pjsip_tx_data_dec_ref(tdata);
+
+ return status;
+}
+
+
+/*
+ * Send response statefully.
+ */
+PJ_DEF(pj_status_t) pjsip_endpt_respond( pjsip_endpoint *endpt,
+ pjsip_module *tsx_user,
+ pjsip_rx_data *rdata,
+ int st_code,
+ const pj_str_t *st_text,
+ const pjsip_hdr *hdr_list,
+ const pjsip_msg_body *body,
+ pjsip_transaction **p_tsx )
+{
+ pj_status_t status;
+ pjsip_tx_data *tdata;
+ pjsip_transaction *tsx;
+
+ /* Validate arguments. */
+ PJ_ASSERT_RETURN(endpt && rdata, PJ_EINVAL);
+
+ if (p_tsx) *p_tsx = NULL;
+
+ /* Create response message */
+ status = pjsip_endpt_create_response( endpt, rdata, st_code, st_text,
+ &tdata);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ /* Add the message headers, if any */
+ if (hdr_list) {
+ const pjsip_hdr *hdr = hdr_list->next;
+ while (hdr != hdr_list) {
+ pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)
+ pjsip_hdr_clone(tdata->pool, hdr) );
+ hdr = hdr->next;
+ }
+ }
+
+ /* Add the message body, if any. */
+ if (body) {
+ tdata->msg->body = pjsip_msg_body_clone( tdata->pool, body );
+ if (tdata->msg->body == NULL) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+ }
+
+ /* Create UAS transaction. */
+ status = pjsip_tsx_create_uas(tsx_user, rdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ return status;
+ }
+
+ /* Feed the request to the transaction. */
+ pjsip_tsx_recv_msg(tsx, rdata);
+
+ /* Send the message. */
+ status = pjsip_tsx_send_msg(tsx, tdata);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ } else if (p_tsx) {
+ *p_tsx = tsx;
+ }
+
+ return status;
+}
+
+
diff --git a/pjsip/src/pjsip/sip_util_wrap.cpp b/pjsip/src/pjsip/sip_util_wrap.cpp
new file mode 100644
index 0000000..140e907
--- /dev/null
+++ b/pjsip/src/pjsip/sip_util_wrap.cpp
@@ -0,0 +1,24 @@
+/* $Id: sip_util_wrap.cpp 3553 2011-05-05 06:14:19Z nanang $ */
+/*
+ * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * This file is a C++ wrapper, see ticket #886 for details.
+ */
+
+#include "sip_util.c"