diff options
Diffstat (limited to 'pjsip/src')
-rw-r--r-- | pjsip/src/pjsip-ua/sip_ua.c | 137 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_auth_client.c (renamed from pjsip/src/pjsip/sip_auth.c) | 565 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_auth_server.c | 234 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_errno.c | 32 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_parser.c | 2 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_transaction.c | 20 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_transport.c | 2 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_transport_loop.c | 5 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_util.c | 6 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/transport_test.c | 12 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/tsx_uac_test.c | 16 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/tsx_uas_test.c | 369 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/txdata_test.c | 2 |
13 files changed, 943 insertions, 459 deletions
diff --git a/pjsip/src/pjsip-ua/sip_ua.c b/pjsip/src/pjsip-ua/sip_ua.c index 094be07f..ac0980f5 100644 --- a/pjsip/src/pjsip-ua/sip_ua.c +++ b/pjsip/src/pjsip-ua/sip_ua.c @@ -41,10 +41,8 @@ /* * Static prototypes. */ -static pj_status_t ua_init( pjsip_endpoint *endpt, - struct pjsip_module *mod, pj_uint32_t id ); -static pj_status_t ua_start( struct pjsip_module *mod ); -static pj_status_t ua_deinit( struct pjsip_module *mod ); +static pj_status_t ua_load(pjsip_endpoint *endpt); +static pj_status_t ua_unload(void); static void ua_tsx_handler( struct pjsip_module *mod, pjsip_event *evt ); static pjsip_dlg *find_dialog( pjsip_user_agent *ua, pjsip_rx_data *rdata ); @@ -53,65 +51,63 @@ static pj_status_t ua_register_dialog( pjsip_user_agent *ua, pjsip_dlg *dlg, PJ_DECL(void) pjsip_on_dialog_destroyed( pjsip_dlg *dlg ); /* - * Default UA instance. - */ -static pjsip_user_agent ua_instance; - -/* * Module interface. */ -static struct pjsip_module mod_ua = +static struct user_agent { - { "User-Agent", 10 }, /* Name. */ - 0, /* Flag */ - 128, /* Priority */ - NULL, /* User agent instance, initialized by APP. */ - 0, /* Number of methods supported (will be initialized later). */ - { 0 }, /* Array of methods (will be initialized later) */ - &ua_init, /* init_module() */ - &ua_start, /* start_module() */ - &ua_deinit, /* deinit_module() */ - &ua_tsx_handler, /* tsx_handler() */ + pjsip_module mod; + pj_pool_t *pool; + pjsip_endpoint *endpt; + pj_mutex_t *mutex; + pj_hash_table_t *dlg_table; + pjsip_dialog dlg_list; + +} mod_ua = +{ + { + NULL, NULL, /* prev, next. */ + { "mod-ua", 6 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_UA_PROXY_LAYER, /* Priority */ + NULL, /* User data. */ + 0, /* Number of methods supported. */ + { 0 }, /* Array of methods */ + &ua_load, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + &ua_unload, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + } }; /* * Initialize user agent instance. */ -static pj_status_t ua_init( pjsip_endpoint *endpt, - struct pjsip_module *mod, pj_uint32_t id ) +static pj_status_t ua_load( pjsip_endpoint *endpt ) { - static pjsip_method m_invite, m_ack, m_cancel, m_bye; - pjsip_user_agent *ua = mod->mod_data; extern int pjsip_dlg_lock_tls_id; /* defined in sip_dialog.c */ + pj_status_t status; - pjsip_method_set( &m_invite, PJSIP_INVITE_METHOD ); - pjsip_method_set( &m_ack, PJSIP_ACK_METHOD ); - pjsip_method_set( &m_cancel, PJSIP_CANCEL_METHOD ); - pjsip_method_set( &m_bye, PJSIP_BYE_METHOD ); + /* Initialize the user agent. */ + mod_ua.endpt = endpt; + status = pjsip_endpt_create_pool( endpt, "pua%p", PJSIP_POOL_LEN_UA, + PJSIP_POOL_INC_UA, &mod_ua.pool); + if (status != PJ_SUCCESS) + return status; - mod->method_cnt = 4; - mod->methods[0] = &m_invite; - mod->methods[1] = &m_ack; - mod->methods[2] = &m_cancel; - mod->methods[3] = &m_bye; + status = pj_mutex_create_recursive(mod_ua.pool, " ua%p", &mod_ua.mutex); + if (status != PJ_SUCCESS) + return status; - /* Initialize the user agent. */ - ua->endpt = endpt; - ua->pool = pjsip_endpt_create_pool(endpt, "pua%p", PJSIP_POOL_LEN_UA, - PJSIP_POOL_INC_UA); - if (!ua->pool) { - return -1; - } - ua->mod_id = id; - ua->mutex = pj_mutex_create(ua->pool, " ua%p", 0); - if (!ua->mutex) { - return -1; - } - ua->dlg_table = pj_hash_create(ua->pool, PJSIP_MAX_DIALOG_COUNT); - if (ua->dlg_table == NULL) { - return -1; - } - pj_list_init(&ua->dlg_list); + mod_ua.dlg_table = pj_hash_create(mod_ua.pool, PJSIP_MAX_DIALOG_COUNT); + if (ua->dlg_table == NULL) + return PJ_ENOMEM; + + pj_list_init(&mod_ua.dlg_list); /* Initialize dialog lock. */ pjsip_dlg_lock_tls_id = pj_thread_local_alloc(); @@ -120,50 +116,21 @@ static pj_status_t ua_init( pjsip_endpoint *endpt, } pj_thread_local_set(pjsip_dlg_lock_tls_id, NULL); - return 0; -} - -/* - * Start user agent instance. - */ -static pj_status_t ua_start( struct pjsip_module *mod ) -{ - PJ_UNUSED_ARG(mod) - return 0; + return PJ_SUCCESS; } /* * Destroy user agent. */ -static pj_status_t ua_deinit( struct pjsip_module *mod ) +static pj_status_t ua_unload() { - pjsip_user_agent *ua = mod->mod_data; - - pj_mutex_unlock(ua->mutex); + pj_mutex_unlock(mod_ua.mutex); /* Release pool */ - if (ua->pool) { - pjsip_endpt_destroy_pool( ua->endpt, ua->pool ); + if (mod_ua.pool) { + pjsip_endpt_destroy_pool( mod_ua.endpt, mod_ua.pool ); } - return 0; -} - -/* - * Get the module interface for the UA module. - */ -PJ_DEF(pjsip_module*) pjsip_ua_get_module(void) -{ - mod_ua.mod_data = &ua_instance; - return &mod_ua; -} - -/* - * Register callback to receive dialog notifications. - */ -PJ_DEF(void) pjsip_ua_set_dialog_callback( pjsip_user_agent *ua, - pjsip_dlg_callback *cb ) -{ - ua->dlg_cb = cb; + return PJ_SUCCESS; } /* diff --git a/pjsip/src/pjsip/sip_auth.c b/pjsip/src/pjsip/sip_auth_client.c index 9b7c52bb..523b35a9 100644 --- a/pjsip/src/pjsip/sip_auth.c +++ b/pjsip/src/pjsip/sip_auth_client.c @@ -16,10 +16,12 @@ * 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_transport.h> #include <pjsip/sip_endpoint.h> +#include <pjsip/sip_errno.h> #include <pjlib-util/md5.h> #include <pj/log.h> #include <pj/string.h> @@ -28,61 +30,53 @@ #include <pj/assert.h> #include <pj/ctype.h> -/* Length of digest string. */ -#define MD5STRLEN 32 -/* Maximum stack size we use for storing username+realm+password etc. */ -#define MAX_TEMP 128 /* 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.c" +#define THIS_FILE "sip_auth_client.c" #if 0 # define AUTH_TRACE_(expr) PJ_LOG(3, expr) #else # define AUTH_TRACE_(expr) #endif -static const char hex[] = "0123456789abcdef"; - /* Transform digest to string. - * output must be at least MD5STRLEN+1 bytes. + * 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) { - char *p = output; int i; - for (i = 0; i<16; ++i) { - int val = digest[i]; - *p++ = hex[val >> 4]; - *p++ = hex[val & 0x0F]; + pj_val_to_hex_digit(digest[i], output); + output += 2; } } + /* * Create response digest based on the parameters and store the * digest ASCII in 'result'. */ -static void 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 pjsip_cred_info *cred_info, - const pj_str_t *method) +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 pjsip_cred_info *cred_info, + const pj_str_t *method) { - char ha1[MD5STRLEN]; - char ha2[MD5STRLEN]; + char ha1[PJSIP_MD5STRLEN]; + char ha2[PJSIP_MD5STRLEN]; unsigned char digest[16]; pj_md5_context pms; - pj_assert(result->slen >= MD5STRLEN); + pj_assert(result->slen >= PJSIP_MD5STRLEN); AUTH_TRACE_((THIS_FILE, "Begin creating digest")); @@ -127,7 +121,7 @@ static void create_digest( pj_str_t *result, *** response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2) ***/ pj_md5_init(&pms); - MD5_APPEND( &pms, ha1, MD5STRLEN); + MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN); MD5_APPEND( &pms, ":", 1); MD5_APPEND( &pms, nonce->ptr, nonce->slen); if (qop && qop->slen != 0) { @@ -139,13 +133,13 @@ static void create_digest( pj_str_t *result, MD5_APPEND( &pms, qop->ptr, qop->slen); } MD5_APPEND( &pms, ":", 1); - MD5_APPEND( &pms, ha2, MD5STRLEN); + 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 = MD5STRLEN; + result->slen = PJSIP_MD5STRLEN; digest2str(digest, result->ptr); AUTH_TRACE_((THIS_FILE, " digest=%.32s", result->ptr)); @@ -206,7 +200,7 @@ static pj_status_t respond_digest( pj_pool_t *pool, { PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"", chal->algorithm.slen, chal->algorithm.ptr)); - return -1; + return PJSIP_EINVALIDALGORITHM; } /* Build digest credential from arguments. */ @@ -218,15 +212,15 @@ static pj_status_t respond_digest( pj_pool_t *pool, pj_strdup(pool, &cred->opaque, &chal->opaque); /* Allocate memory. */ - cred->response.ptr = pj_pool_alloc(pool, MD5STRLEN); - cred->response.slen = MD5STRLEN; + cred->response.ptr = pj_pool_alloc(pool, PJSIP_MD5STRLEN); + cred->response.slen = PJSIP_MD5STRLEN; if (chal->qop.slen == 0) { /* Server doesn't require quality of protection. */ /* Convert digest to string and store in chal->response. */ - create_digest( &cred->response, &cred->nonce, NULL, NULL, NULL, - uri, cred_info, method); + pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL, NULL, + NULL, uri, cred_info, method); } else if (has_auth_qop(pool, &chal->qop)) { /* Server requires quality of protection. @@ -243,118 +237,166 @@ static pj_status_t respond_digest( pj_pool_t *pool, pj_strdup(pool, &cred->cnonce, &dummy_cnonce); } - create_digest( &cred->response, &cred->nonce, &cred->nc, cnonce, - &pjsip_AUTH_STR, uri, cred_info, method ); + pjsip_auth_create_digest( &cred->response, &cred->nonce, &cred->nc, + cnonce, &pjsip_AUTH_STR, uri, 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 -1; + return PJSIP_EINVALIDQOP; } - return 0; + return PJ_SUCCESS; } -#if PJSIP_AUTH_QOP_SUPPORT +#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_auth_session *auth_sess, + pjsip_cached_auth *cached_auth, const pjsip_www_authenticate_hdr *hdr ) { if (hdr->challenge.digest.qop.slen == 0) return; /* Initialize cnonce and qop if not present. */ - if (auth_sess->cnonce.slen == 0) { + if (cached_auth->cnonce.slen == 0) { /* Save the whole challenge */ - auth_sess->last_chal = pjsip_hdr_clone(ses_pool, hdr); + cached_auth->last_chal = pjsip_hdr_clone(ses_pool, hdr); /* Create cnonce */ - pj_create_unique_string( ses_pool, &auth_sess->cnonce ); + pj_create_unique_string( ses_pool, &cached_auth->cnonce ); /* Initialize nonce-count */ - auth_sess->nc = 1; + cached_auth->nc = 1; /* Save realm. */ - pj_assert(auth_sess->realm.slen != 0); - if (auth_sess->realm.slen == 0) { - pj_strdup(ses_pool, &auth_sess->realm, + 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, - &auth_sess->last_chal->challenge.digest.nonce)) + &cached_auth->last_chal->challenge.digest.nonce)) { /* Same nonce, increment nonce-count */ - ++auth_sess->nc; + ++cached_auth->nc; } else { /* Server gives new nonce. */ - pj_strdup(ses_pool, &auth_sess->last_chal->challenge.digest.nonce, + pj_strdup(ses_pool, &cached_auth->last_chal->challenge.digest.nonce, &hdr->challenge.digest.nonce); /* Has the opaque changed? */ - if (pj_strcmp(&auth_sess->last_chal->challenge.digest.opaque, + if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque, &hdr->challenge.digest.opaque)) { pj_strdup(ses_pool, - &auth_sess->last_chal->challenge.digest.opaque, + &cached_auth->last_chal->challenge.digest.opaque, &hdr->challenge.digest.opaque); } - auth_sess->nc = 1; + cached_auth->nc = 1; } } } #endif /* PJSIP_AUTH_QOP_SUPPORT */ -/* Find authentication session in the list. */ -static pjsip_auth_session *find_session( pjsip_auth_session *sess_list, - const pj_str_t *realm ) +/* 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_auth_session *sess = sess_list->next; - while (sess != sess_list) { - if (pj_stricmp(&sess->realm, realm) == 0) - return sess; - sess = sess->next; + 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; + 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]; + } + 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; +} + + +/* 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 && cred_cnt && c, PJ_EINVAL); + + sess->cred_info = pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c)); + pj_memcpy(sess->cred_info, c, cred_cnt * sizeof(*c)); + sess->cred_cnt = cred_cnt; + + return PJ_SUCCESS; +} + + /* * Create Authorization/Proxy-Authorization response header based on the challege * in WWW-Authenticate/Proxy-Authenticate header. */ -PJ_DEF(pjsip_authorization_hdr*) -pjsip_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_auth_session *auth_sess) +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 *auth; + pjsip_authorization_hdr *hauth; char tmp[PJSIP_MAX_URL_SIZE]; pj_str_t uri_str; pj_pool_t *pool; + pj_status_t status; - pj_assert(hdr != NULL); - pj_assert(uri != NULL); - pj_assert(cred_info != NULL); - pj_assert(method != NULL); + /* 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)); + 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!"); - PJ_LOG(4,(THIS_FILE, "Unable to authorize: URI is too long!")); - return NULL; + return PJSIP_EURITOOLONG; } # if (PJSIP_AUTH_HEADER_CACHING) @@ -370,52 +412,51 @@ pjsip_auth_respond( pj_pool_t *req_pool, # endif if (hdr->type == PJSIP_H_WWW_AUTHENTICATE) - auth = pjsip_authorization_hdr_create(pool); + hauth = pjsip_authorization_hdr_create(pool); else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE) - auth = pjsip_proxy_authorization_hdr_create(pool); + hauth = pjsip_proxy_authorization_hdr_create(pool); else { - pj_assert(0); - return NULL; + pj_assert(!"Invalid response header!"); + return PJSIP_EINVALIDHDR; } /* Only support digest scheme at the moment. */ if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) { - pj_status_t rc; pj_str_t *cnonce = NULL; pj_uint32_t nc = 1; /* Update the session (nonce-count etc) if required. */ # if PJSIP_AUTH_QOP_SUPPORT { - if (auth_sess) { - update_digest_session( sess_pool, auth_sess, hdr ); + if (cached_auth) { + update_digest_session( sess_pool, cached_auth, hdr ); - cnonce = &auth_sess->cnonce; - nc = auth_sess->nc; + cnonce = &cached_auth->cnonce; + nc = cached_auth->nc; } } # endif /* PJSIP_AUTH_QOP_SUPPORT */ - auth->scheme = pjsip_DIGEST_STR; - rc = respond_digest( pool, &auth->credential.digest, - &hdr->challenge.digest, &uri_str, cred_info, - cnonce, nc, &method->name); - if (rc != 0) - return NULL; + 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 && auth_sess) { - if (auth_sess->qop_value == PJSIP_AUTH_QOP_NONE) { - pj_str_t *qop_val = &auth->credential.digest.qop; + 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)) { - auth_sess->qop_value = PJSIP_AUTH_QOP_AUTH; + cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH; } else { - auth_sess->qop_value = PJSIP_AUTH_QOP_UNKNOWN; + cached_auth->qop_value = PJSIP_AUTH_QOP_UNKNOWN; } } } } else { - auth = NULL; + return PJSIP_EINVALIDAUTHSCHEME; } /* Keep the new authorization header in the cache, only @@ -423,236 +464,166 @@ pjsip_auth_respond( pj_pool_t *req_pool, */ # if PJSIP_AUTH_HEADER_CACHING { - if (auth && auth_sess && auth_sess->qop_value == PJSIP_AUTH_QOP_NONE) { + 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 = auth_sess->cached_hdr.next; - while (cached_hdr != &auth_sess->cached_hdr) { + 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 != &auth_sess->cached_hdr) { - cached_hdr->hdr = auth; + 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 = auth; - pj_list_insert_before( &auth_sess->cached_hdr, cached_hdr ); + cached_hdr->hdr = hauth; + pj_list_insert_before( &cached_auth->cached_hdr, cached_hdr ); } } } # endif - return auth; + *p_h_auth = hauth; + return PJ_SUCCESS; } -/* Verify incoming Authorization/Proxy-Authorization header against existing - * credentials. Will return TRUE if the authorization request matches any of - * the credential. - */ -PJ_DEF(pj_bool_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[MD5STRLEN]; - pj_str_t digest; - const pjsip_digest_credential *dig = &hdr->credential.digest; - - /* Check that username match. */ - if (pj_strcmp(&dig->username, &cred_info->username) != 0) - return PJ_FALSE; - - /* Check that realm match. */ - if (pj_strcmp(&dig->realm, &cred_info->realm) != 0) - return PJ_FALSE; - - /* Prepare for our digest calculation. */ - digest.ptr = digest_buf; - digest.slen = MD5STRLEN; - - /* Create digest for comparison. */ - 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, - method ); - - return pj_stricmp(&digest, &hdr->credential.digest.response) == 0; - } else { - pj_assert(0); - return PJ_FALSE; - } -} - -/* Find credential to use for the specified realm and scheme. */ -PJ_DEF(const pjsip_cred_info*) pjsip_auth_find_cred( unsigned count, - const pjsip_cred_info cred[], - const pj_str_t *realm, - const pj_str_t *scheme) -{ - unsigned i; - PJ_UNUSED_ARG(scheme); - for (i=0; i<count; ++i) { - if (pj_stricmp(&cred[i].realm, realm) == 0) - return &cred[i]; - } - return NULL; -} - -#if PJSIP_AUTH_AUTO_SEND_NEXT -static void new_auth_for_req( pjsip_tx_data *tdata, - pj_pool_t *sess_pool, - pjsip_auth_session *sess, - int cred_count, - const pjsip_cred_info cred_info[]) +#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(sess->last_chal != NULL); + PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL); + PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL); - cred = pjsip_auth_find_cred( cred_count, cred_info, &sess->realm, - &sess->last_chal->scheme ); + cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme ); if (!cred) - return; - + 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; - hauth = pjsip_auth_respond( tdata->pool, sess->last_chal, - tdata->msg->line.req.uri, - cred, &tdata->msg->line.req.method, - sess_pool, sess); - if (hauth) { - pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth); - } + pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth); + + if (p_h_auth) + *p_h_auth = hauth; + + return PJ_SUCCESS; } #endif -/* - * Initialize new request message with authorization headers. - * This function will put Authorization/Proxy-Authorization headers to the - * outgoing request message. If caching is enabled (PJSIP_AUTH_HEADER_CACHING) - * and the session has previously sent Authorization/Proxy-Authorization header - * with the same method, then the same Authorization/Proxy-Authorization header - * will be resent from the cache only if qop is not present. If the stack is - * configured to automatically generate next Authorization/Proxy-Authorization - * headers (PJSIP_AUTH_AUTO_SEND_NEXT flag), then new Authorization/Proxy- - * Authorization headers are calculated and generated when they are not present - * in the case or if authorization session has qop. - * - * If both PJSIP_AUTH_HEADER_CACHING flag and PJSIP_AUTH_AUTO_SEND_NEXT flag - * are not set, this function will do nothing. The stack then will only send - * Authorization/Proxy-Authorization to respond 401/407 response. - */ -PJ_DEF(pj_status_t) pjsip_auth_init_req( pj_pool_t *sess_pool, - pjsip_tx_data *tdata, - pjsip_auth_session *sess_list, - int cred_count, - const pjsip_cred_info cred_info[]) -{ - pjsip_auth_session *sess; - pjsip_method *method = &tdata->msg->line.req.method; - - pj_assert(tdata->msg->type == PJSIP_REQUEST_MSG); - if (!sess_list) - return 0; - sess = sess_list->next; - while (sess != sess_list) { - if (sess->qop_value == PJSIP_AUTH_QOP_NONE) { -# if (PJSIP_AUTH_HEADER_CACHING) +/* 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; + pj_status_t status; + + 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); + + /* Get the method. */ + method = &tdata->msg->line.req.method; + + auth = sess->cached_auth.next; + while (auth != &sess->cached_auth) { + if (auth->qop_value == PJSIP_AUTH_QOP_NONE) { +# if defined(PJSIP_AUTH_HEADER_CACHING) && \ + PJSIP_AUTH_HEADER_CACHING!=0 { - pjsip_cached_auth_hdr *entry = sess->cached_hdr.next; - while (entry != &sess->cached_hdr) { + 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); } else { -# if (PJSIP_AUTH_AUTO_SEND_NEXT) - { - new_auth_for_req( tdata, sess_pool, sess, - cred_count, cred_info); - } -# else +# if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \ + PJSIP_AUTH_AUTO_SEND_NEXT!=0 { - PJ_UNUSED_ARG(sess_pool); - PJ_UNUSED_ARG(cred_count); - PJ_UNUSED_ARG(cred_info); + new_auth_for_req( tdata, sess, auth, NULL); } -# endif /* PJSIP_AUTH_AUTO_SEND_NEXT */ +# endif } entry = entry->next; } } -# elif (PJSIP_AUTH_AUTO_SEND_NEXT) +# elif defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \ + PJSIP_AUTH_AUTO_SEND_NEXT!=0 { - new_auth_for_req( tdata, sess_pool, sess, - cred_count, cred_info); + new_auth_for_req( tdata, sess, auth, NULL); } -# else - { - PJ_UNUSED_ARG(sess_pool); - PJ_UNUSED_ARG(cred_count); - PJ_UNUSED_ARG(cred_info); - } -# endif /* PJSIP_AUTH_HEADER_CACHING */ +# endif } -# if (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT) - else if (sess->qop_value == PJSIP_AUTH_QOP_AUTH) { +# 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; - cred = pjsip_auth_find_cred( cred_count, cred_info, - &sess->realm, - &sess->last_chal->scheme); + cred = auth_find_cred(sess, &auth->realm, + &auth->last_chal->scheme); if (!cred) { - sess = sess->next; + auth = auth->next; continue; } - hauth = pjsip_auth_respond( tdata->pool, sess->last_chal, - tdata->msg->line.req.uri, - cred, - &tdata->msg->line.req.method, - sess_pool, sess ); - if (hauth) { - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth); - } + 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); } # endif /* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */ - sess = sess->next; + auth = auth->next; } - return 0; + + return PJ_SUCCESS; } + /* Process authorization challenge */ -static pjsip_authorization_hdr *process_auth( pj_pool_t *req_pool, - const pjsip_www_authenticate_hdr *hchal, - const pjsip_uri *uri, - pjsip_tx_data *tdata, - int cred_count, - const pjsip_cred_info cred_info[], - pj_pool_t *ses_pool, - pjsip_auth_session *auth_sess) +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, *hauth; + 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; @@ -685,7 +656,7 @@ static pjsip_authorization_hdr *process_auth( pj_pool_t *req_pool, sent_auth->credential.digest.username.ptr, sent_auth->credential.digest.realm.slen, sent_auth->credential.digest.realm.ptr)); - return NULL; + return PJSIP_EFAILEDCREDENTIAL; } /* Otherwise remove old, stale authorization header from the mesasge. @@ -695,8 +666,8 @@ static pjsip_authorization_hdr *process_auth( pj_pool_t *req_pool, } /* Find credential to be used for the challenge. */ - cred = pjsip_auth_find_cred( cred_count, cred_info, - &hchal->challenge.common.realm, &hchal->scheme); + 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, @@ -704,14 +675,14 @@ static pjsip_authorization_hdr *process_auth( pj_pool_t *req_pool, tdata->obj_name, realm->slen, realm->ptr, hchal->scheme.slen, hchal->scheme.ptr)); - return NULL; + return PJSIP_ENOCREDENTIAL; } /* Respond to authorization challenge. */ - hauth = pjsip_auth_respond( req_pool, hchal, uri, cred, - &tdata->msg->line.req.method, - ses_pool, auth_sess); - return hauth; + status = auth_respond( req_pool, hchal, uri, cred, + &tdata->msg->line.req.method, + sess->pool, cached_auth, h_auth); + return status; } @@ -721,29 +692,35 @@ static pjsip_authorization_hdr *process_auth( pj_pool_t *req_pool, * - to put the newly created Authorization/Proxy-Authorization header * in cached_list. */ -PJ_DEF(pjsip_tx_data*) pjsip_auth_reinit_req( pjsip_endpoint *endpt, - pj_pool_t *ses_pool, - pjsip_auth_session *sess_list, - int cred_count, - const pjsip_cred_info cred_info[], - pjsip_tx_data *tdata, - const pjsip_rx_data *rdata) +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; pjsip_via_hdr *via; - - PJ_UNUSED_ARG(endpt); - - pj_assert(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG); - pj_assert(rdata->msg_info.msg->line.status.code == 401 || - rdata->msg_info.msg->line.status.code == 407 ); - + 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; + /* * Respond to each authentication challenge. */ hdr = rdata->msg_info.msg->hdr.next; while (hdr != &rdata->msg_info.msg->hdr) { - pjsip_auth_session *sess; + pjsip_cached_auth *cached_auth; const pjsip_www_authenticate_hdr *hchal; pjsip_authorization_hdr *hauth; @@ -762,26 +739,26 @@ PJ_DEF(pjsip_tx_data*) pjsip_auth_reinit_req( pjsip_endpoint *endpt, /* Find authentication session for this realm, create a new one * if not present. */ - sess = find_session(sess_list, &hchal->challenge.common.realm ); - if (!sess) { - sess = pj_pool_calloc( ses_pool, 1, sizeof(*sess)); - pj_strdup( ses_pool, &sess->realm, &hchal->challenge.common.realm); - sess->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE); + cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm ); + if (!cached_auth) { + cached_auth = pj_pool_zalloc( sess->pool, sizeof(*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(&sess->cached_hdr); + pj_list_init(&cached_auth->cached_hdr); } # endif - pj_list_insert_before( sess_list, sess ); + pj_list_insert_before( &sess->cached_auth, cached_auth ); } /* Create authorization header for this challenge, and update * authorization session. */ - hauth = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri, - tdata, cred_count, cred_info, ses_pool, sess ); - if (!hauth) - return NULL; + 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); @@ -799,6 +776,8 @@ PJ_DEF(pjsip_tx_data*) pjsip_auth_reinit_req( pjsip_endpoint *endpt, pjsip_tx_data_add_ref(tdata); /* Done. */ - return tdata; + *new_request = tdata; + return PJ_SUCCESS; + } diff --git a/pjsip/src/pjsip/sip_auth_server.c b/pjsip/src/pjsip/sip_auth_server.c new file mode 100644 index 00000000..7bd347b2 --- /dev/null +++ b/pjsip/src/pjsip/sip_auth_server.c @@ -0,0 +1,234 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 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> + + +/* Defined in sip_auth_client.c */ +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 pjsip_cred_info *cred_info, + const pj_str_t *method); + + +/* + * 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, + 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; + int 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_errno.c b/pjsip/src/pjsip/sip_errno.c index 62de365a..a2faf1a2 100644 --- a/pjsip/src/pjsip/sip_errno.c +++ b/pjsip/src/pjsip/sip_errno.c @@ -34,21 +34,28 @@ static const struct { PJSIP_EBUSY, "Object is busy" }, { PJSIP_ETYPEEXISTS , "Object with the same type exists" }, { PJSIP_ESHUTDOWN, "SIP stack shutting down" }, + { PJSIP_ENOTINITIALIZED, "SIP object is not initialized." }, /* Messaging errors */ { PJSIP_EINVALIDMSG, "Invalid message/syntax error" }, - { PJSIP_EINVALIDSCHEME, "Invalid URI scheme" }, + { PJSIP_ENOTREQUESTMSG, "Expecting request message"}, + { PJSIP_ENOTRESPONSEMSG, "Expecting response message"}, { PJSIP_EMSGTOOLONG, "Message too long" }, { PJSIP_EPARTIALMSG, "Partial message" }, + + { PJSIP_EINVALIDSTATUS, "Invalid status code"}, + + { PJSIP_EINVALIDSCHEME, "Invalid URI scheme" }, { PJSIP_EMISSINGREQURI, "Missing Request-URI" }, + { PJSIP_EINVALIDREQURI, "Invalid Request URI" }, + { PJSIP_EURITOOLONG, "URI is too long" }, + { PJSIP_EMISSINGHDR, "Missing required header(s)" }, - { PJSIP_EMISSINGBODY, "Missing message body" }, + { PJSIP_EINVALIDHDR, "Invalid header field"}, { PJSIP_EINVALIDVIA, "Invalid Via header" }, { PJSIP_EMULTIPLEVIA, "Multiple Via headers in response" }, - { PJSIP_EINVALIDREQURI, "Invalid Request URI" }, - { PJSIP_ENOTREQUESTMSG, "Expecting request message"}, - { PJSIP_ENOTRESPONSEMSG, "Expecting response message"}, - { PJSIP_EINVALIDHDR, "Invalid header field"}, + + { PJSIP_EMISSINGBODY, "Missing message body" }, /* Transport errors */ { PJSIP_EUNSUPTRANSPORT, "Unsupported transport"}, @@ -58,6 +65,19 @@ static const struct /* Transaction errors */ { PJSIP_ETSXDESTROYED, "Transaction has been destroyed"}, + + /* Authentication. */ + { PJSIP_EFAILEDCREDENTIAL, "Credential failed to authenticate"}, + { PJSIP_ENOCREDENTIAL, "No suitable credential"}, + { PJSIP_EINVALIDALGORITHM, "Invalid/unsupported digest algorithm" }, + { PJSIP_EINVALIDQOP, "Invalid/unsupported digest qop" }, + { PJSIP_EINVALIDAUTHSCHEME, "Unsupported authentication scheme" }, + { PJSIP_EAUTHNOPREVCHAL, "No previous challenge" }, + { PJSIP_EAUTHNOAUTH, "No suitable authorization header" }, + { PJSIP_EAUTHACCNOTFOUND, "Account or credential not found" }, + { PJSIP_EAUTHACCDISABLED, "Account or credential is disabled" }, + { PJSIP_EAUTHINVALIDREALM, "Invalid authorization realm"}, + { PJSIP_EAUTHINVALIDDIGEST, "Invalid authorization digest" } }; diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c index 5ce5433e..f281824b 100644 --- a/pjsip/src/pjsip/sip_parser.c +++ b/pjsip/src/pjsip/sip_parser.c @@ -1399,7 +1399,7 @@ static pjsip_hdr* parse_hdr_call_id(pjsip_parse_ctx *ctx) parse_hdr_end(ctx->scanner); if (ctx->rdata) - ctx->rdata->msg_info.call_id = hdr->id; + ctx->rdata->msg_info.cid = hdr; return (pjsip_hdr*)hdr; } diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c index ef59652b..61632ed7 100644 --- a/pjsip/src/pjsip/sip_transaction.c +++ b/pjsip/src/pjsip/sip_transaction.c @@ -269,7 +269,7 @@ static pj_status_t create_tsx_key_2543( pj_pool_t *pool, /* Calculate length required. */ len_required = 9 + /* CSeq number */ rdata->msg_info.from->tag.slen + /* From tag. */ - rdata->msg_info.call_id.slen + /* Call-ID */ + rdata->msg_info.cid->id.slen + /* Call-ID */ host->slen + /* Via host. */ 9 + /* Via port. */ 16; /* Separator+Allowance. */ @@ -299,8 +299,8 @@ static pj_status_t create_tsx_key_2543( pj_pool_t *pool, *p++ = SEPARATOR; /* Add Call-ID. */ - len = rdata->msg_info.call_id.slen; - pj_memcpy( p, rdata->msg_info.call_id.ptr, len ); + len = rdata->msg_info.cid->id.slen; + pj_memcpy( p, rdata->msg_info.cid->id.ptr, len ); p += len; *p++ = SEPARATOR; @@ -1589,12 +1589,11 @@ static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched) ++tsx->retransmit_count; - status = tsx_send_msg( tsx, tsx->last_tx); - if (status != PJ_SUCCESS) - return status; - - /* Restart timer T1. */ + /* 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 { @@ -1602,6 +1601,11 @@ static pj_status_t tsx_retransmit( pjsip_transaction *tsx, int resched) } } + status = tsx_send_msg( tsx, tsx->last_tx); + if (status != PJ_SUCCESS) { + return status; + } + return PJ_SUCCESS; } diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c index 6c8da28d..46d98d99 100644 --- a/pjsip/src/pjsip/sip_transport.c +++ b/pjsip/src/pjsip/sip_transport.c @@ -878,7 +878,7 @@ PJ_DEF(pj_ssize_t) pjsip_tpmgr_receive_packet( pjsip_tpmgr *mgr, } /* Perform basic header checking. */ - if (rdata->msg_info.call_id.ptr == NULL || + if (rdata->msg_info.cid->id.ptr == NULL || rdata->msg_info.from == NULL || rdata->msg_info.to == NULL || rdata->msg_info.via == NULL || diff --git a/pjsip/src/pjsip/sip_transport_loop.c b/pjsip/src/pjsip/sip_transport_loop.c index 33b9f229..98011b9f 100644 --- a/pjsip/src/pjsip/sip_transport_loop.c +++ b/pjsip/src/pjsip/sip_transport_loop.c @@ -250,7 +250,7 @@ static pj_status_t loop_destroy(pjsip_transport *tp) } /* Worker thread for loop transport. */ -static int loop_thread(void *arg) +static int loop_transport_worker_thread(void *arg) { struct loop_transport *loop = arg; struct recv_list r; @@ -384,7 +384,8 @@ PJ_DEF(pj_status_t) pjsip_loop_start( pjsip_endpoint *endpt, pj_list_init(&loop->send_list); /* Create worker thread. */ - status = pj_thread_create(pool, "loop", &loop_thread, loop, 0, + status = pj_thread_create(pool, "loop", + &loop_transport_worker_thread, loop, 0, PJ_THREAD_SUSPENDED, &loop->thread); if (status != PJ_SUCCESS) goto on_error; diff --git a/pjsip/src/pjsip/sip_util.c b/pjsip/src/pjsip/sip_util.c index 607b3989..f69ad9b3 100644 --- a/pjsip/src/pjsip/sip_util.c +++ b/pjsip/src/pjsip/sip_util.c @@ -351,6 +351,12 @@ PJ_DEF(pj_status_t) pjsip_endpt_create_response( pjsip_endpoint *endpt, 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); diff --git a/pjsip/src/test-pjsip/transport_test.c b/pjsip/src/test-pjsip/transport_test.c index b57fedbf..74f6d796 100644 --- a/pjsip/src/test-pjsip/transport_test.c +++ b/pjsip/src/test-pjsip/transport_test.c @@ -116,7 +116,7 @@ static pjsip_module my_module = static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata) { /* Check that this is our request. */ - if (pj_strcmp2(&rdata->msg_info.call_id, CALL_ID_HDR) == 0) { + if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) { /* It is! */ /* Send response. */ pjsip_tx_data *tdata; @@ -149,7 +149,7 @@ static pj_bool_t my_on_rx_request(pjsip_rx_data *rdata) static pj_bool_t my_on_rx_response(pjsip_rx_data *rdata) { - if (pj_strcmp2(&rdata->msg_info.call_id, CALL_ID_HDR) == 0) { + if (pj_strcmp2(&rdata->msg_info.cid->id, CALL_ID_HDR) == 0) { pj_get_timestamp(&my_recv_time); recv_status = PJ_SUCCESS; return PJ_TRUE; @@ -334,8 +334,8 @@ static pj_str_t rt_call_id; static pj_bool_t rt_on_rx_request(pjsip_rx_data *rdata) { - if (!pj_strncmp(&rdata->msg_info.call_id, &rt_call_id, rt_call_id.slen)) { - char *pos = pj_strchr(&rdata->msg_info.call_id, '/'); + if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) { + char *pos = pj_strchr(&rdata->msg_info.cid->id, '/'); int thread_id = (*pos - '0'); pjsip_tx_data *tdata; @@ -422,8 +422,8 @@ static pj_status_t rt_send_request(int thread_id) static pj_bool_t rt_on_rx_response(pjsip_rx_data *rdata) { - if (!pj_strncmp(&rdata->msg_info.call_id, &rt_call_id, rt_call_id.slen)) { - char *pos = pj_strchr(&rdata->msg_info.call_id, '/')+1; + if (!pj_strncmp(&rdata->msg_info.cid->id, &rt_call_id, rt_call_id.slen)) { + char *pos = pj_strchr(&rdata->msg_info.cid->id, '/')+1; int thread_id = (*pos - '0'); pj_timestamp recv_time; diff --git a/pjsip/src/test-pjsip/tsx_uac_test.c b/pjsip/src/test-pjsip/tsx_uac_test.c index 182269cd..2ec94e5a 100644 --- a/pjsip/src/test-pjsip/tsx_uac_test.c +++ b/pjsip/src/test-pjsip/tsx_uac_test.c @@ -398,12 +398,14 @@ static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) test_complete = -747; } - /* last_tx is ACK in this case. */ + /* last_tx MUST be the INVITE request + * (authorization depends on this behavior) + */ if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id != - PJSIP_ACK_METHOD) + PJSIP_INVITE_METHOD) { PJ_LOG(3,(THIS_FILE, - " error: last_tx is not ACK")); + " error: last_tx is not INVITE")); test_complete = -748; } } @@ -461,12 +463,14 @@ static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) test_complete = -763; } - /* last_tx is ACK in this case. */ + /* last_tx MUST be INVITE. + * (authorization depends on this behavior) + */ if (tsx->last_tx && tsx->last_tx->msg->line.req.method.id != - PJSIP_ACK_METHOD) + PJSIP_INVITE_METHOD) { PJ_LOG(3,(THIS_FILE, - " error: last_tx is not ACK")); + " error: last_tx is not INVITE")); test_complete = -764; } diff --git a/pjsip/src/test-pjsip/tsx_uas_test.c b/pjsip/src/test-pjsip/tsx_uas_test.c index bed6fd08..ecc174c4 100644 --- a/pjsip/src/test-pjsip/tsx_uas_test.c +++ b/pjsip/src/test-pjsip/tsx_uas_test.c @@ -66,10 +66,19 @@ ** until it's terminated by user). ** Transaction also MUST terminate in T4 seconds. ** - ** TEST10_BRANCH_ID - ** Test where INVITE UAS transaction never receives ACK - ** ** TEST11_BRANCH_ID + ** Test scenario where transport fails before response is sent (i.e. + ** in TRYING state). + ** + ** TEST12_BRANCH_ID + ** As above, after provisional response is sent but before final + ** response is sent (i.e. in PROCEEDING state). + ** + ** TEST13_BRANCH_ID + ** As above, for INVITE, after final response has been sent but before + ** ACK is received (i.e. in CONNECTED state). + ** + ** TEST14_BRANCH_ID ** When UAS failed to deliver the response with the selected transport, ** it should try contacting the client with other transport or begin ** RFC 3263 server resolution procedure. @@ -79,11 +88,6 @@ ** upon receiving request retransmission). ** c. COMPLETED state. ** - ** TEST12_BRANCH_ID - ** Variant of previous test, where transaction fails to deliver the - ** response using any kind of transports. Transaction should report - ** transport error to its transaction user. - ** **/ static char *TEST1_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test1"; @@ -98,6 +102,7 @@ static char *TEST9_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test9"; static char *TEST10_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test10"; static char *TEST11_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test11"; static char *TEST12_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test12"; +static char *TEST13_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test13"; #define TEST1_STATUS_CODE 200 #define TEST2_STATUS_CODE 301 @@ -115,6 +120,7 @@ static char *TEST12_BRANCH_ID = PJSIP_RFC3261_BRANCH_ID "-UAS-Test12"; #define TEST6_RESPONSE_COUNT 3 #define TEST7_STATUS_CODE 301 #define TEST8_STATUS_CODE 302 +#define TEST9_STATUS_CODE 301 #define TEST4_TITLE "test4: absorbing request retransmission" @@ -210,10 +216,14 @@ static void send_response_timer( pj_timer_heap_t *timer_heap, status = pjsip_tsx_send_msg(tsx, r->tdata); if (status != PJ_SUCCESS) { - PJ_LOG(3,(THIS_FILE," error: timer unable to send response")); + // Some tests do expect failure! + //PJ_LOG(3,(THIS_FILE," error: timer unable to send response")); + pj_mutex_unlock(tsx->mutex); pjsip_tx_data_dec_ref(r->tdata); return; } + + pj_mutex_unlock(tsx->mutex); } /* Utility to send response. */ @@ -234,9 +244,10 @@ static void send_response( pjsip_rx_data *rdata, status = pjsip_tsx_send_msg(tsx, tdata); if (status != PJ_SUCCESS) { - app_perror(" error: unable to send response", status); pjsip_tx_data_dec_ref(tdata); - test_complete = -197; + // Some tests do expect failure! + //app_perror(" error: unable to send response", status); + //test_complete = -197; return; } } @@ -249,6 +260,7 @@ static void schedule_send_response( pjsip_rx_data *rdata, { pj_status_t status; pjsip_tx_data *tdata; + pj_timer_entry *t; struct response *r; pj_time_val delay; @@ -268,14 +280,15 @@ static void schedule_send_response( pjsip_rx_data *rdata, delay.msec = msec_delay; pj_time_val_normalize(&delay); - timer.user_data = r; - timer.cb = &send_response_timer; + t = pj_pool_zalloc(tdata->pool, sizeof(*t)); + t->user_data = r; + t->cb = &send_response_timer; - status = pjsip_endpt_schedule_timer(endpt, &timer, &delay); + status = pjsip_endpt_schedule_timer(endpt, t, &delay); if (status != PJ_SUCCESS) { + pjsip_tx_data_dec_ref(tdata); app_perror(" error: unable to schedule timer", status); test_complete = -199; - pjsip_tx_data_dec_ref(tdata); return; } } @@ -574,6 +587,83 @@ static void tsx_user_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) } + + } else + if (pj_strcmp2(&tsx->branch, TEST9_BRANCH_ID)==0) { + /* + * TEST9_BRANCH_ID tests that retransmission of INVITE final response + * must cease when ACK is received. + */ + + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (test_complete == 0) + test_complete = 1; + + /* Check status code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -160; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -161; + } + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -162; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_TRYING) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -163; + } + + + } else if (tsx->state == PJSIP_TSX_STATE_CONFIRMED) { + + /* Check that status code is status_code. */ + if (tsx->status_code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE, " error: incorrect status code")); + test_complete = -164; + } + + /* Previous state. */ + if (e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED) { + PJ_LOG(3,(THIS_FILE, " error: incorrect prev_state")); + test_complete = -165; + } + + } else if (tsx->state != PJSIP_TSX_STATE_DESTROYED) { + + PJ_LOG(3,(THIS_FILE, " error: unexpected state")); + test_complete = -166; + + } + + + } else + if (pj_strcmp2(&tsx->branch, TEST10_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST11_BRANCH_ID)==0 || + pj_strcmp2(&tsx->branch, TEST12_BRANCH_ID)==0) + { + if (tsx->state == PJSIP_TSX_STATE_TERMINATED) { + + if (!test_complete) + test_complete = 1; + + if (tsx->status_code != PJSIP_SC_TSX_TRANSPORT_ERROR) { + PJ_LOG(3,(THIS_FILE," error: incorrect status code")); + test_complete = -170; + } + } } } @@ -612,7 +702,7 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) TEST1_STATUS_CODE : TEST2_STATUS_CODE; if (msg->type == PJSIP_REQUEST_MSG) { - /* On received response, create UAS and respond with final + /* On received request, create UAS and respond with final * response. */ pjsip_transaction *tsx; @@ -652,7 +742,7 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) /* TEST3_BRANCH_ID tests provisional response. */ if (msg->type == PJSIP_REQUEST_MSG) { - /* On received response, create UAS and respond with provisional + /* On received request, create UAS and respond with provisional * response, then schedule timer to send final response. */ pjsip_transaction *tsx; @@ -705,7 +795,7 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) /* TEST6_BRANCH_ID: retransmit last response in COMPLETED state. */ if (msg->type == PJSIP_REQUEST_MSG) { - /* On received response, create UAS. */ + /* On received request, create UAS. */ pjsip_transaction *tsx; status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); @@ -786,7 +876,7 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) */ if (msg->type == PJSIP_REQUEST_MSG) { - /* On received response, create UAS. */ + /* On received request, create UAS. */ pjsip_transaction *tsx; status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); @@ -861,7 +951,7 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) } return PJ_TRUE; - } else if (pj_strcmp2(&branch_param, TEST9_BRANCH_ID)) { + } else if (pj_strcmp2(&branch_param, TEST9_BRANCH_ID) == 0) { /* * TEST9_BRANCH_ID tests that the retransmission of INVITE final @@ -870,49 +960,36 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) */ if (msg->type == PJSIP_REQUEST_MSG) { - /* On received response, create UAS. */ + /* On received request, create UAS. */ pjsip_transaction *tsx; status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); if (status != PJ_SUCCESS) { app_perror(" error: unable to create transaction", status); - test_complete = -140; + test_complete = -150; return PJ_TRUE; } save_key(tsx); + send_response(rdata, tsx, TEST9_STATUS_CODE); - if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0) { - - send_response(rdata, tsx, TEST7_STATUS_CODE); - - } else { - - send_response(rdata, tsx, TEST8_STATUS_CODE); - - } } else { - int code; ++recv_count; - if (pj_strcmp2(&branch_param, TEST7_BRANCH_ID) == 0) - code = TEST7_STATUS_CODE; - else - code = TEST8_STATUS_CODE; + if (rdata->msg_info.msg->line.status.code != TEST9_STATUS_CODE) { + PJ_LOG(3,(THIS_FILE," error: invalid status code")); + test_complete = -151; + } if (recv_count==1) { - - if (rdata->msg_info.msg->line.status.code != code) { - PJ_LOG(3,(THIS_FILE," error: invalid status code")); - test_complete = -141; - } recv_last = rdata->pkt_info.timestamp; - } else { + } else if (recv_count < 5) { + /* Let UAS retransmit some messages before we send ACK. */ pj_time_val now; unsigned msec, msec_expected; @@ -930,19 +1007,90 @@ static pj_bool_t on_rx_message(pjsip_rx_data *rdata) " error: incorrect retransmission " "time (%d ms expected, %d ms received", msec_expected, msec)); - test_complete = -142; + test_complete = -152; } - if (recv_count > 11) { - PJ_LOG(3,(THIS_FILE," error: too many responses (%d)", - recv_count)); - test_complete = -143; + recv_last = rdata->pkt_info.timestamp; + + } else if (recv_count == 5) { + pjsip_tx_data *tdata; + pjsip_sip_uri *uri; + pjsip_via_hdr *via; + + status = pjsip_endpt_create_request_from_hdr( + endpt, &pjsip_ack_method, + rdata->msg_info.to->uri, + rdata->msg_info.from, + rdata->msg_info.to, + NULL, + rdata->msg_info.cid, + rdata->msg_info.cseq->cseq, + NULL, + &tdata); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create ACK", status); + test_complete = -153; + return PJ_TRUE; } - recv_last = rdata->pkt_info.timestamp; + uri=(pjsip_sip_uri*)pjsip_uri_get_uri(tdata->msg->line.req.uri); + uri->transport_param = pj_str("loop-dgram"); + + via = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL); + via->branch_param = pj_str(TEST9_BRANCH_ID); + + status = pjsip_endpt_send_request_stateless(endpt, tdata, + NULL, NULL); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to send ACK", status); + test_complete = -154; + } + + } else { + PJ_LOG(3,(THIS_FILE," error: too many responses (%d)", + recv_count)); + test_complete = -155; + } + + } + return PJ_TRUE; + + } else if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0 || + pj_strcmp2(&branch_param, TEST12_BRANCH_ID) == 0) + { + int test_num, code1, code2; + + if (pj_strcmp2(&branch_param, TEST10_BRANCH_ID) == 0) + test_num=10, code1 = 100, code2 = 0; + else if (pj_strcmp2(&branch_param, TEST11_BRANCH_ID) == 0) + test_num=11, code1 = 100, code2 = 200; + else + test_num=12, code1 = 200, code2 = 0; + + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG) { + + /* On received response, create UAS. */ + pjsip_transaction *tsx; + + status = pjsip_tsx_create_uas(&tsx_user, rdata, &tsx); + if (status != PJ_SUCCESS) { + app_perror(" error: unable to create transaction", status); + test_complete = -150; + return PJ_TRUE; } + save_key(tsx); + + schedule_send_response(rdata, &tsx_key, code1, 1000); + + if (code2) + schedule_send_response(rdata, &tsx_key, code2, 2000); + + } else { + } + return PJ_TRUE; } @@ -1212,6 +1360,113 @@ static int tsx_final_response_retransmission_test(void) } +/***************************************************************************** + ** + ** TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must + ** cease when ACK is received + ** + ***************************************************************************** + */ +static int tsx_ack_test(void) +{ + int status; + + PJ_LOG(3,(THIS_FILE, + " test9: receiving ACK for non-2xx final response")); + + status = perform_test("sip:129.0.0.1;transport=loop-dgram", + "sip:129.0.0.1;transport=loop-dgram", + TEST9_BRANCH_ID, + 20, /* allow 5 retransmissions */ + &pjsip_invite_method, 1, 0, 0); + if (status != 0) + return status; + + + return 0; +} + + + +/***************************************************************************** + ** + ** TEST10_BRANCH_ID: test transport failure in TRYING state. + ** TEST11_BRANCH_ID: test transport failure in PROCEEDING state. + ** TEST12_BRANCH_ID: test transport failure in CONNECTED state. + ** TEST13_BRANCH_ID: test transport failure in CONFIRMED state. + ** + ***************************************************************************** + */ +static int tsx_transport_failure_test(void) +{ + struct test_desc + { + int transport_delay; + int fail_delay; + char *branch_id; + char *title; + } tests[] = + { + { 0, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (no delay)" }, + { 50, 10, TEST10_BRANCH_ID, "test10: failed transport in TRYING state (50 ms delay)" }, + { 0, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (no delay)" }, + { 50, 1500, TEST11_BRANCH_ID, "test11: failed transport in PROCEEDING state (50 ms delay)" }, + { 0, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (no delay)" }, + { 50, 2500, TEST12_BRANCH_ID, "test12: failed transport in COMPLETED state (50 ms delay)" }, + }; + int i, status; + + for (i=0; i<PJ_ARRAY_SIZE(tests); ++i) { + pj_time_val fail_time, end_test, now; + + PJ_LOG(3,(THIS_FILE, " %s", tests[i].title)); + pjsip_loop_set_failure(loop, 0, NULL); + pjsip_loop_set_delay(loop, tests[i].transport_delay); + + status = perform_test("sip:129.0.0.1;transport=loop-dgram", + "sip:129.0.0.1;transport=loop-dgram", + tests[i].branch_id, + 0, + &pjsip_invite_method, 1, 0, 1); + if (status && status != TEST_TIMEOUT_ERROR) + return status; + if (!status) { + PJ_LOG(3,(THIS_FILE, " error: expecting timeout")); + return -40; + } + + pj_gettimeofday(&fail_time); + fail_time.msec += tests[i].fail_delay; + pj_time_val_normalize(&fail_time); + + do { + pj_time_val interval = { 0, 1 }; + pj_gettimeofday(&now); + pjsip_endpt_handle_events(endpt, &interval); + } while (PJ_TIME_VAL_LT(now, fail_time)); + + pjsip_loop_set_failure(loop, 1, NULL); + + end_test = now; + end_test.sec += 5; + + do { + pj_time_val interval = { 0, 1 }; + pj_gettimeofday(&now); + pjsip_endpt_handle_events(endpt, &interval); + } while (!test_complete && PJ_TIME_VAL_LT(now, end_test)); + + if (test_complete == 0) { + PJ_LOG(3,(THIS_FILE, " error: test has timed out")); + return -41; + } + + if (test_complete != 1) + return test_complete; + } + + return 0; +} /***************************************************************************** ** @@ -1244,7 +1499,6 @@ int tsx_uas_test(void) return -4; } -#if 0 /* TEST1_BRANCH_ID: Basic 2xx final response. * TEST2_BRANCH_ID: Basic non-2xx final response. */ @@ -1293,7 +1547,22 @@ int tsx_uas_test(void) if (status != 0) return status; -#endif + /* TEST9_BRANCH_ID: retransmission of non-2xx INVITE final response must + * cease when ACK is received + */ + status = tsx_ack_test(); + if (status != 0) + return status; + + /* TEST10_BRANCH_ID: test transport failure in TRYING state. + * TEST11_BRANCH_ID: test transport failure in PROCEEDING state. + * TEST12_BRANCH_ID: test transport failure in CONNECTED state. + * TEST13_BRANCH_ID: test transport failure in CONFIRMED state. + */ + status = tsx_transport_failure_test(); + if (status != 0) + return status; + pjsip_transport_dec_ref(loop); return 0; diff --git a/pjsip/src/test-pjsip/txdata_test.c b/pjsip/src/test-pjsip/txdata_test.c index def46b5a..327ea084 100644 --- a/pjsip/src/test-pjsip/txdata_test.c +++ b/pjsip/src/test-pjsip/txdata_test.c @@ -154,7 +154,7 @@ static int core_txdata_test(void) * We should never do this in real application, as there are many * many more fields need to be initialized!! */ - dummy_rdata.msg_info.call_id = (HFIND(invite->msg, cid, CALL_ID))->id; + dummy_rdata.msg_info.cid = HFIND(invite->msg, cid, CALL_ID); dummy_rdata.msg_info.clen = NULL; dummy_rdata.msg_info.cseq = HFIND(invite->msg, cseq, CSEQ); dummy_rdata.msg_info.ctype = NULL; |