diff options
Diffstat (limited to 'pjsip/src/pjsip')
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, ¬ify)==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, ¶m->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, ¶m->name, ¶m->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, ¶m->name, ¶m->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, ¶m_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, ¶m_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" |