diff options
author | Benny Prijono <bennylp@teluu.com> | 2007-10-03 18:28:49 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2007-10-03 18:28:49 +0000 |
commit | 3112361512e913a6ce28a9253a6d45434b975505 (patch) | |
tree | 914e356e5b5e2cf05c17c1177589064773638d20 /pjsip | |
parent | e2f5c2d529ae091a65c73f86ab60f4fc251645dc (diff) |
Ticket 5: Support for SIP UPDATE (RFC 3311) and fix the offer/answer negotiation
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1469 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip')
-rw-r--r-- | pjsip/build/Makefile | 3 | ||||
-rw-r--r-- | pjsip/build/test_pjsip.dsp | 4 | ||||
-rw-r--r-- | pjsip/build/test_pjsip.vcproj | 8 | ||||
-rw-r--r-- | pjsip/include/pjsip-ua/sip_100rel.h | 86 | ||||
-rw-r--r-- | pjsip/include/pjsip-ua/sip_inv.h | 15 | ||||
-rw-r--r-- | pjsip/include/pjsip/sip_config.h | 11 | ||||
-rw-r--r-- | pjsip/src/pjsip-ua/sip_100rel.c | 1244 | ||||
-rw-r--r-- | pjsip/src/pjsip-ua/sip_inv.c | 565 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_dialog.c | 4 | ||||
-rw-r--r-- | pjsip/src/pjsip/sip_transaction.c | 1 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 6 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/inv_offer_answer_test.c | 676 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/test.c | 4 | ||||
-rw-r--r-- | pjsip/src/test-pjsip/test.h | 6 |
14 files changed, 1933 insertions, 700 deletions
diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile index a9c363b8..6ddb0146 100644 --- a/pjsip/build/Makefile +++ b/pjsip/build/Makefile @@ -88,7 +88,8 @@ export TEST_OBJS += dlg_core_test.o dns_test.o msg_err_test.o \ test.o transport_loop_test.o transport_tcp_test.o \ transport_test.o transport_udp_test.o \ tsx_basic_test.o tsx_bench.o tsx_uac_test.o \ - tsx_uas_test.o txdata_test.o uri_test.o + tsx_uas_test.o txdata_test.o uri_test.o \ + inv_offer_answer_test.o export TEST_CFLAGS += $(_CFLAGS) export TEST_LDFLAGS += $(PJ_LDFLAGS) $(PJ_LDLIBS) $(LDFLAGS) export TEST_EXE := ../bin/pjsip-test-$(TARGET_NAME)$(HOST_EXE) diff --git a/pjsip/build/test_pjsip.dsp b/pjsip/build/test_pjsip.dsp index aa7de06a..1429063e 100644 --- a/pjsip/build/test_pjsip.dsp +++ b/pjsip/build/test_pjsip.dsp @@ -97,6 +97,10 @@ SOURCE="..\src\test-pjsip\dns_test.c" # End Source File
# Begin Source File
+SOURCE="..\src\test-pjsip\inv_offer_answer_test.c"
+# End Source File
+# Begin Source File
+
SOURCE="..\src\test-pjsip\main.c"
# End Source File
# Begin Source File
diff --git a/pjsip/build/test_pjsip.vcproj b/pjsip/build/test_pjsip.vcproj index 91208c48..dd6c8d37 100644 --- a/pjsip/build/test_pjsip.vcproj +++ b/pjsip/build/test_pjsip.vcproj @@ -44,7 +44,7 @@ Name="VCCLCompilerTool"
Optimization="2"
InlineFunctionExpansion="2"
- AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include"
+ AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjmedia/include"
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;PJ_WIN32;PJ_M_I386"
StringPooling="true"
RuntimeLibrary="2"
@@ -139,7 +139,7 @@ <Tool
Name="VCCLCompilerTool"
Optimization="0"
- AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include"
+ AdditionalIncludeDirectories="../include,../../pjlib/include,../../pjlib-util/include,../../pjmedia/include"
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PJ_WIN32;PJ_M_I386"
MinimalRebuild="true"
BasicRuntimeChecks="3"
@@ -255,6 +255,10 @@ </FileConfiguration>
</File>
<File
+ RelativePath="..\src\test-pjsip\inv_offer_answer_test.c"
+ >
+ </File>
+ <File
RelativePath="..\src\test-pjsip\main.c"
>
<FileConfiguration
diff --git a/pjsip/include/pjsip-ua/sip_100rel.h b/pjsip/include/pjsip-ua/sip_100rel.h index fc9e27bc..6ba8485c 100644 --- a/pjsip/include/pjsip-ua/sip_100rel.h +++ b/pjsip/include/pjsip-ua/sip_100rel.h @@ -45,12 +45,7 @@ * * \subsection pjsip_100rel_init Initializing 100rel Module * - * \a PRACK and \a 100rel extension support is built into the library when - * #PJSIP_HAS_100REL macro is enabled. The default is yes. Application can - * set this macro to zero if it does not wish to support reliable provisional - * response extension. - * - * Application must also explicitly initialize 100rel module by calling + * Application must explicitly initialize 100rel module by calling * #pjsip_100rel_init_module() in application initialization function. * * Once the 100rel module is initialized, it will register \a PRACK method @@ -59,7 +54,7 @@ * \subsection pjsip_100rel_sess Using 100rel in a Session * * For UAC, \a 100rel support will be enabled in the session if \a 100rel - * support is enabled in the library (with #PJSIP_HAS_100REL macro). + * support is enabled in the library (default is yes). * Outgoing INVITE request will include \a 100rel tag in \a Supported * header and \a PRACK method in \a Allow header. When callee endpoint * sends reliable provisional responses, the UAC will automatically send @@ -86,9 +81,7 @@ * \verbatim unsigned options = 0; -#if PJSIP_HAS_100REL options |= PJSIP_INV_SUPPORT_100REL; -#endif status = pjsip_inv_verify_request(rdata, &options, answer, NULL, endpt, &resp); @@ -129,6 +122,20 @@ PJ_BEGIN_DECL + +/** + * PRACK method constant. + * @see pjsip_get_prack_method() + */ +PJ_DECL_DATA(const pjsip_method) pjsip_prack_method; + + +/** + * Get #pjsip_invite_method constant. + */ +PJ_DECL(const pjsip_method*) pjsip_get_prack_method(void); + + /** * Initialize 100rel module. This function must be called once during * application initialization, to register 100rel module to SIP endpoint. @@ -139,6 +146,7 @@ PJ_BEGIN_DECL */ PJ_DECL(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt); + /** * Add 100rel support to the specified invite session. This function will * be called internally by the invite session if it detects that the @@ -150,6 +158,56 @@ PJ_DECL(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt); */ PJ_DECL(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv); + +/** + * Check if incoming response has reliable provisional response feature. + * + * @param rdata Receive data buffer containing the response. + * + * @return PJ_TRUE if the provisional response is reliable. + */ +PJ_DECL(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata); + + +/** + * Create PRACK request for the incoming reliable provisional response. + * Note that PRACK request MUST be sent using #pjsip_100rel_send_prack(). + * + * @param inv The invite session. + * @param rdata The incoming reliable provisional response. + * @param p_tdata Upon return, it will be initialized with the + * PRACK request. + * + * @return PJ_SUCCESS on successful. + */ +PJ_DECL(pj_status_t) pjsip_100rel_create_prack(pjsip_inv_session *inv, + pjsip_rx_data *rdata, + pjsip_tx_data **p_tdata); + +/** + * Send PRACK request. + * + * @param inv The invite session. + * @param tdata The PRACK request. + * + * @return PJ_SUCCESS on successful. + */ +PJ_DECL(pj_status_t) pjsip_100rel_send_prack(pjsip_inv_session *inv, + pjsip_tx_data *tdata); + + +/** + * Handle incoming PRACK request. + * + * @param inv The invite session. + * @param rdata Incoming PRACK request. + * + * @return PJ_SUCCESS on successful. + */ +PJ_DECL(pj_status_t) pjsip_100rel_on_rx_prack(pjsip_inv_session *inv, + pjsip_rx_data *rdata); + + /** * Transmit INVITE response (provisional or final) reliably according to * 100rel specification. The 100rel module will take care of retransmitting @@ -166,6 +224,16 @@ PJ_DECL(pj_status_t) pjsip_100rel_tx_response(pjsip_inv_session *inv, pjsip_tx_data *tdata); +/** + * Notify 100rel module that the invite session has been disconnected. + * + * @param inv The invite session. + * + * @return PJ_SUCCESS on successful. + */ +PJ_DECL(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv); + + PJ_END_DECL /** diff --git a/pjsip/include/pjsip-ua/sip_inv.h b/pjsip/include/pjsip-ua/sip_inv.h index f04f4aed..a367fe1e 100644 --- a/pjsip/include/pjsip-ua/sip_inv.h +++ b/pjsip/include/pjsip-ua/sip_inv.h @@ -253,6 +253,8 @@ struct pjsip_inv_session pjmedia_sdp_neg *neg; /**< Negotiator. */ pjsip_transaction *invite_tsx; /**< 1st invite tsx. */ pjsip_tx_data *last_answer; /**< Last INVITE resp. */ + pjsip_tx_data *last_ack; /**< Last ACK request */ + pj_int32_t last_ack_cseq; /**< CSeq of last ACK */ void *mod_data[PJSIP_MAX_MODULE];/**< Modules data. */ }; @@ -561,20 +563,15 @@ PJ_DECL(pj_status_t) pjsip_inv_reinvite(pjsip_inv_session *inv, /** - * Create an UPDATE request. + * Create an UPDATE request to initiate new SDP offer. * * @param inv The invite session. * @param new_contact If application wants to update its local contact * and inform peer to perform target refresh with a new * contact, it can specify the new contact in this * argument; otherwise this argument must be NULL. - * @param new_offer Application MAY initiate a new SDP offer/answer - * session in the request when there is no pending answer - * to be sent or received. It can detect this condition - * by observing the state of the SDP negotiator of the - * invite session. If new offer should be sent to remote, - * the offer must be specified in this argument; otherwise - * this argument must be NULL. + * @param offer Offer to be sent to remote. This argument is + * mandatory. * @param p_tdata Pointer to receive the UPDATE request message to * be created. * @@ -584,7 +581,7 @@ PJ_DECL(pj_status_t) pjsip_inv_reinvite(pjsip_inv_session *inv, */ PJ_DECL(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv, const pj_str_t *new_contact, - const pjmedia_sdp_session *new_offer, + const pjmedia_sdp_session *offer, pjsip_tx_data **p_tdata ); diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index 3ad94c29..846c2a57 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -65,17 +65,6 @@ /** - * Specify whether support for reliable provisional response (100rel, PRACK) - * should be built in the library. - * - * Default: 1 - */ -#ifndef PJSIP_HAS_100REL -# define PJSIP_HAS_100REL 1 -#endif - - -/** * Specify maximum transaction count in transaction hash table. * Default value is 16*1024 */ diff --git a/pjsip/src/pjsip-ua/sip_100rel.c b/pjsip/src/pjsip-ua/sip_100rel.c index 48798259..cb8852d2 100644 --- a/pjsip/src/pjsip-ua/sip_100rel.c +++ b/pjsip/src/pjsip-ua/sip_100rel.c @@ -28,17 +28,21 @@ #include <pj/pool.h> #include <pj/rand.h> -#if defined(PJSIP_HAS_100REL) && PJSIP_HAS_100REL!=0 - #define THIS_FILE "sip_100rel.c" +/* PRACK method */ +PJ_DEF_DATA(const pjsip_method) pjsip_prack_method = +{ + PJSIP_OTHER_METHOD, + { "PRACK", 5 } +}; + typedef struct dlg_data dlg_data; /* * Static prototypes. */ static pj_status_t mod_100rel_load(pjsip_endpoint *endpt); -static void mod_100rel_on_tsx_state(pjsip_transaction*, pjsip_event*); static void handle_incoming_prack(dlg_data *dd, pjsip_transaction *tsx, pjsip_event *e); @@ -48,13 +52,6 @@ static void on_retransmit(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry); -/* PRACK method */ -const pjsip_method pjsip_prack_method = -{ - PJSIP_OTHER_METHOD, - { "PRACK", 5 } -}; - const pj_str_t tag_100rel = { "100rel", 6 }; const pj_str_t RSEQ = { "RSeq", 4 }; const pj_str_t RACK = { "RAck", 4 }; @@ -80,7 +77,7 @@ static struct mod_100rel NULL, /* on_rx_response() */ NULL, /* on_tx_request. */ NULL, /* on_tx_response() */ - &mod_100rel_on_tsx_state, /* on_tsx_state() */ + NULL, /* on_tsx_state() */ } }; @@ -132,361 +129,369 @@ struct dlg_data */ static pj_status_t mod_100rel_load(pjsip_endpoint *endpt) { - mod_100rel.endpt = endpt; - pjsip_endpt_add_capability(endpt, &mod_100rel.mod, - PJSIP_H_ALLOW, NULL, - 1, &pjsip_prack_method.name); - pjsip_endpt_add_capability(endpt, &mod_100rel.mod, - PJSIP_H_SUPPORTED, NULL, - 1, &tag_100rel); - - return PJ_SUCCESS; + mod_100rel.endpt = endpt; + pjsip_endpt_add_capability(endpt, &mod_100rel.mod, + PJSIP_H_ALLOW, NULL, + 1, &pjsip_prack_method.name); + pjsip_endpt_add_capability(endpt, &mod_100rel.mod, + PJSIP_H_SUPPORTED, NULL, + 1, &tag_100rel); + + return PJ_SUCCESS; } static pjsip_require_hdr *find_req_hdr(pjsip_msg *msg) { - pjsip_require_hdr *hreq; + pjsip_require_hdr *hreq; - hreq = (pjsip_require_hdr*) - pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL); + hreq = (pjsip_require_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, NULL); - while (hreq) { - unsigned i; - for (i=0; i<hreq->count; ++i) { - if (!pj_stricmp(&hreq->values[i], &tag_100rel)) { - return hreq; - } - } + while (hreq) { + unsigned i; + for (i=0; i<hreq->count; ++i) { + if (!pj_stricmp(&hreq->values[i], &tag_100rel)) { + return hreq; + } + } - if ((void*)hreq->next == (void*)&msg->hdr) - return NULL; + if ((void*)hreq->next == (void*)&msg->hdr) + return NULL; - hreq = (pjsip_require_hdr*) - pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next); + hreq = (pjsip_require_hdr*) + pjsip_msg_find_hdr(msg, PJSIP_H_REQUIRE, hreq->next); - } + } - return NULL; + return NULL; } -static void mod_100rel_on_tsx_state(pjsip_transaction *tsx, pjsip_event *e) + +/* + * Get PRACK method constant. + */ +PJ_DEF(const pjsip_method*) pjsip_get_prack_method(void) { - pjsip_dialog *dlg; - dlg_data *dd; - - dlg = pjsip_tsx_get_dlg(tsx); - if (!dlg) - return; - - dd = (dlg_data*) dlg->mod_data[mod_100rel.mod.id]; - if (!dd) - return; - - if (tsx->role == PJSIP_ROLE_UAS && - tsx->state == PJSIP_TSX_STATE_TRYING && - pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0) - { - /* - * Handle incoming PRACK request. - */ - handle_incoming_prack(dd, tsx, e); - - } else if (tsx->role == PJSIP_ROLE_UAC && - tsx->method.id == PJSIP_INVITE_METHOD && - e->type == PJSIP_EVENT_TSX_STATE && - e->body.tsx_state.type == PJSIP_EVENT_RX_MSG && - e->body.tsx_state.src.rdata->msg_info.msg->line.status.code > 100 && - e->body.tsx_state.src.rdata->msg_info.msg->line.status.code < 200 && - e->body.tsx_state.src.rdata->msg_info.require != NULL) - { - /* - * Handle incoming provisional response which wants to - * be PRACK-ed - */ - - if (find_req_hdr(e->body.tsx_state.src.rdata->msg_info.msg)) { - /* Received provisional response which needs to be - * PRACK-ed. - */ - handle_incoming_response(dd, tsx, e); - } - - } else if (tsx->role == PJSIP_ROLE_UAC && - tsx->state == PJSIP_TSX_STATE_COMPLETED && - pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0) - { - /* - * Handle the status of outgoing PRACK request. - */ - if (tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST || - tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || - tsx->status_code == PJSIP_SC_TSX_TIMEOUT || - tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR) - { - /* These are fatal errors which should terminate - * the session AND dialog! - */ - PJ_TODO(TERMINATE_SESSION_ON_481); - } - - } else if (tsx == dd->inv->invite_tsx && - tsx->role == PJSIP_ROLE_UAS && - tsx->state == PJSIP_TSX_STATE_TERMINATED) - { - /* Make sure we don't have pending transmission */ - if (dd->uas_state) { - pj_assert(!dd->uas_state->retransmit_timer.id); - pj_assert(pj_list_empty(&dd->uas_state->tx_data_list)); - } - } + return &pjsip_prack_method; } -static void parse_rack(const pj_str_t *rack, - pj_uint32_t *p_rseq, pj_int32_t *p_seq, - pj_str_t *p_method) + +/* + * init module + */ +PJ_DEF(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt) { - const char *p = rack->ptr, *end = p + rack->slen; - pj_str_t token; + if (mod_100rel.mod.id != -1) + return PJ_SUCCESS; - token.ptr = (char*)p; - while (p < end && pj_isdigit(*p)) - ++p; - token.slen = p - token.ptr; - *p_rseq = pj_strtoul(&token); + return pjsip_endpt_register_module(endpt, &mod_100rel.mod); +} - ++p; - token.ptr = (char*)p; - while (p < end && pj_isdigit(*p)) - ++p; - token.slen = p - token.ptr; - *p_seq = pj_strtoul(&token); - ++p; - if (p < end) { - p_method->ptr = (char*)p; - p_method->slen = end - p; - } else { - p_method->ptr = NULL; - p_method->slen = 0; - } +/* + * API: attach 100rel support in invite session. Called by + * sip_inv.c + */ +PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv) +{ + dlg_data *dd; + + /* Check that 100rel module has been initialized */ + PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP); + + /* Create and attach as dialog usage */ + dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data); + dd->inv = inv; + pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd); + + PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached")); + + return PJ_SUCCESS; } -/* Clear all responses in the transmission list */ -static void clear_all_responses(dlg_data *dd) + +/* + * Check if incoming response has reliable provisional response feature. + */ +PJ_DEF(pj_bool_t) pjsip_100rel_is_reliable(pjsip_rx_data *rdata) { - tx_data_list_t *tl; + pjsip_msg *msg = rdata->msg_info.msg; - tl = dd->uas_state->tx_data_list.next; - while (tl != &dd->uas_state->tx_data_list) { - pjsip_tx_data_dec_ref(tl->tdata); - tl = tl->next; - } - pj_list_init(&dd->uas_state->tx_data_list); + PJ_ASSERT_RETURN(msg->type == PJSIP_RESPONSE_MSG, PJ_FALSE); + + return msg->line.status.code > 100 && msg->line.status.code < 200 && + rdata->msg_info.require != NULL && + find_req_hdr(msg) != NULL; } -static void handle_incoming_prack(dlg_data *dd, pjsip_transaction *tsx, - pjsip_event *e) +/* + * Create PRACK request for the incoming reliable provisional response. + */ +PJ_DEF(pj_status_t) pjsip_100rel_create_prack( pjsip_inv_session *inv, + pjsip_rx_data *rdata, + pjsip_tx_data **p_tdata) { - pjsip_rx_data *rdata; - pjsip_msg *msg; - pjsip_generic_string_hdr *rack_hdr; - pjsip_tx_data *tdata; - pj_uint32_t rseq; - pj_int32_t cseq; - pj_str_t method; - pj_status_t status; + dlg_data *dd; + pjsip_transaction *tsx; + pjsip_msg *msg; + pjsip_generic_string_hdr *rseq_hdr; + pjsip_generic_string_hdr *rack_hdr; + unsigned rseq; + pj_str_t rack; + char rack_buf[80]; + pjsip_tx_data *tdata; + pj_status_t status; + + *p_tdata = NULL; + + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED); + + tsx = pjsip_rdata_get_tsx(rdata); + msg = rdata->msg_info.msg; + + /* Check our assumptions */ + pj_assert( tsx->role == PJSIP_ROLE_UAC && + tsx->method.id == PJSIP_INVITE_METHOD && + msg->line.status.code > 100 && + msg->line.status.code < 200); + + + /* Get the RSeq header */ + rseq_hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL); + if (rseq_hdr == NULL) { + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Ignoring provisional response with no RSeq header")); + return PJSIP_EMISSINGHDR; + } + rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue); + + /* Create new UAC state if we don't have one */ + if (dd->uac_state == NULL) { + dd->uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool, + uac_state_t); + dd->uac_state->cseq = rdata->msg_info.cseq->cseq; + dd->uac_state->rseq = rseq - 1; + } + /* If this is from new INVITE transaction, reset UAC state */ + if (rdata->msg_info.cseq->cseq != dd->uac_state->cseq) { + dd->uac_state->cseq = rdata->msg_info.cseq->cseq; + dd->uac_state->rseq = rseq - 1; + } - rdata = e->body.tsx_state.src.rdata; - msg = rdata->msg_info.msg; + /* Ignore provisional response retransmission */ + if (rseq <= dd->uac_state->rseq) { + /* This should have been handled before */ + return PJ_EIGNORED; + + /* Ignore provisional response with out-of-order RSeq */ + } else if (rseq != dd->uac_state->rseq + 1) { + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Ignoring provisional response because RSeq jump " + "(expecting %u, got %u)", + dd->uac_state->rseq+1, rseq)); + return PJ_EIGNORED; + } - /* Always reply with 200/OK for PRACK */ - status = pjsip_endpt_create_response(tsx->endpt, rdata, - 200, NULL, &tdata); - if (status == PJ_SUCCESS) - pjsip_tsx_send_msg(tsx, tdata); + /* Update our RSeq */ + dd->uac_state->rseq = rseq; - /* Ignore if we don't have pending transmission */ - if (dd->uas_state == NULL || - pj_list_empty(&dd->uas_state->tx_data_list)) - { - PJ_LOG(4,(dd->inv->dlg->obj_name, - "PRACK ignored - no pending response")); - return; - } + /* Create PRACK */ + status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method, + -1, &tdata); + if (status != PJ_SUCCESS) + return status; - /* Find RAck header */ - rack_hdr = (pjsip_generic_string_hdr*) - pjsip_msg_find_hdr_by_name(msg, &RACK, NULL); - if (!rack_hdr) { - /* RAck header not found */ - PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header")); - return; - } - parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method); - - /* Match RAck against outgoing transmission */ - if (rseq == dd->uas_state->tx_data_list.next->rseq && - cseq == dd->uas_state->cseq) - { - tx_data_list_t *tl = dd->uas_state->tx_data_list.next; - - /* Yes it match! */ - if (dd->uas_state->retransmit_timer.id) { - pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, - &dd->uas_state->retransmit_timer); - dd->uas_state->retransmit_timer.id = PJ_FALSE; - } - - /* Remove from the list */ - if (tl != &dd->uas_state->tx_data_list) { - pj_list_erase(tl); - - /* Destroy the response */ - pjsip_tx_data_dec_ref(tl->tdata); - } - - /* Schedule next packet */ - dd->uas_state->retransmit_count = 0; - if (!pj_list_empty(&dd->uas_state->tx_data_list)) { - on_retransmit(NULL, &dd->uas_state->retransmit_timer); - } + /* Create RAck header */ + rack.ptr = rack_buf; + rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf), + "%u %u %.*s", + rseq, rdata->msg_info.cseq->cseq, + (int)tsx->method.name.slen, + tsx->method.name.ptr); + rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr); - } else { - /* No it doesn't match */ - PJ_LOG(4,(dd->inv->dlg->obj_name, - "Rx PRACK with no matching reliable response")); - } + /* Done */ + *p_tdata = tdata; + + return PJ_SUCCESS; } /* - * Handle incoming provisional response with 100rel requirement. - * In this case we shall transmit PRACK request. + * Send PRACK request. */ -static void handle_incoming_response(dlg_data *dd, pjsip_transaction *tsx, - pjsip_event *e) +PJ_DEF(pj_status_t) pjsip_100rel_send_prack( pjsip_inv_session *inv, + pjsip_tx_data *tdata) { - pjsip_rx_data *rdata; - pjsip_msg *msg; - pjsip_generic_string_hdr *rseq_hdr; - pjsip_generic_string_hdr *rack_hdr; - unsigned rseq; - pj_str_t rack; - char rack_buf[80]; - pjsip_tx_data *tdata; - pj_status_t status; + dlg_data *dd; - rdata = e->body.tsx_state.src.rdata; - msg = rdata->msg_info.msg; - - /* Check our assumptions */ - pj_assert( tsx->role == PJSIP_ROLE_UAC && - tsx->method.id == PJSIP_INVITE_METHOD && - e->type == PJSIP_EVENT_TSX_STATE && - e->body.tsx_state.type == PJSIP_EVENT_RX_MSG && - msg->line.status.code > 100 && - msg->line.status.code < 200); - - - /* Get the RSeq header */ - rseq_hdr = (pjsip_generic_string_hdr*) - pjsip_msg_find_hdr_by_name(msg, &RSEQ, NULL); - if (rseq_hdr == NULL) { - PJ_LOG(4,(dd->inv->dlg->obj_name, - "Ignoring provisional response with no RSeq header")); - return; - } - rseq = (pj_uint32_t) pj_strtoul(&rseq_hdr->hvalue); - - /* Create new UAC state if we don't have one */ - if (dd->uac_state == NULL) { - dd->uac_state = PJ_POOL_ZALLOC_T(dd->inv->dlg->pool, - uac_state_t); - dd->uac_state->cseq = rdata->msg_info.cseq->cseq; - dd->uac_state->rseq = rseq - 1; - } + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + PJ_ASSERT_ON_FAIL(dd != NULL, + {pjsip_tx_data_dec_ref(tdata); return PJSIP_ENOTINITIALIZED; }); - /* If this is from new INVITE transaction, reset UAC state */ - if (rdata->msg_info.cseq->cseq != dd->uac_state->cseq) { - dd->uac_state->cseq = rdata->msg_info.cseq->cseq; - dd->uac_state->rseq = rseq - 1; - } + return pjsip_dlg_send_request(inv->dlg, tdata, + mod_100rel.mod.id, (void*) dd); - /* Ignore provisional response retransmission */ - if (rseq <= dd->uac_state->rseq) { - /* This should have been handled before */ - return; - - /* Ignore provisional response with out-of-order RSeq */ - } else if (rseq != dd->uac_state->rseq + 1) { - PJ_LOG(4,(dd->inv->dlg->obj_name, - "Ignoring provisional response because RSeq jump " - "(expecting %u, got %u)", - dd->uac_state->rseq+1, rseq)); - return; - } +} - /* Update our RSeq */ - dd->uac_state->rseq = rseq; - /* Create PRACK */ - status = pjsip_dlg_create_request(dd->inv->dlg, &pjsip_prack_method, - -1, &tdata); - if (status != PJ_SUCCESS) { - PJ_LOG(4,(dd->inv->dlg->obj_name, - "Error creating PRACK request (status=%d)", status)); - return; - } +/* + * Notify 100rel module that the invite session has been disconnected. + */ +PJ_DEF(pj_status_t) pjsip_100rel_end_session(pjsip_inv_session *inv) +{ + dlg_data *dd; - /* Create RAck header */ - rack.ptr = rack_buf; - rack.slen = pj_ansi_snprintf(rack.ptr, sizeof(rack_buf), - "%u %u %.*s", - rseq, rdata->msg_info.cseq->cseq, - (int)tsx->method.name.slen, - tsx->method.name.ptr); - PJ_ASSERT_ON_FAIL(rack.slen > 0 && rack.slen < (int)sizeof(rack_buf), - { pjsip_tx_data_dec_ref(tdata); return; }); - rack_hdr = pjsip_generic_string_hdr_create(tdata->pool, &RACK, &rack); - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) rack_hdr); - - /* Send PRACK */ - pjsip_dlg_send_request(dd->inv->dlg, tdata, - mod_100rel.mod.id, (void*) dd); + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + if (!dd) + return PJ_SUCCESS; + /* Make sure we don't have pending transmission */ + if (dd->uas_state) { + pj_assert(!dd->uas_state->retransmit_timer.id); + pj_assert(pj_list_empty(&dd->uas_state->tx_data_list)); + } + + return PJ_SUCCESS; } -/* - * API: init module - */ -PJ_DEF(pj_status_t) pjsip_100rel_init_module(pjsip_endpoint *endpt) +static void parse_rack(const pj_str_t *rack, + pj_uint32_t *p_rseq, pj_int32_t *p_seq, + pj_str_t *p_method) { - return pjsip_endpt_register_module(endpt, &mod_100rel.mod); + const char *p = rack->ptr, *end = p + rack->slen; + pj_str_t token; + + token.ptr = (char*)p; + while (p < end && pj_isdigit(*p)) + ++p; + token.slen = p - token.ptr; + *p_rseq = pj_strtoul(&token); + + ++p; + token.ptr = (char*)p; + while (p < end && pj_isdigit(*p)) + ++p; + token.slen = p - token.ptr; + *p_seq = pj_strtoul(&token); + + ++p; + if (p < end) { + p_method->ptr = (char*)p; + p_method->slen = end - p; + } else { + p_method->ptr = NULL; + p_method->slen = 0; + } +} + +/* Clear all responses in the transmission list */ +static void clear_all_responses(dlg_data *dd) +{ + tx_data_list_t *tl; + + tl = dd->uas_state->tx_data_list.next; + while (tl != &dd->uas_state->tx_data_list) { + pjsip_tx_data_dec_ref(tl->tdata); + tl = tl->next; + } + pj_list_init(&dd->uas_state->tx_data_list); } /* - * API: attach 100rel support in invite session. Called by - * sip_inv.c + * Handle incoming PRACK request. */ -PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv) +PJ_DEF(pj_status_t) pjsip_100rel_on_rx_prack( pjsip_inv_session *inv, + pjsip_rx_data *rdata) { - dlg_data *dd; + dlg_data *dd; + pjsip_transaction *tsx; + pjsip_msg *msg; + pjsip_generic_string_hdr *rack_hdr; + pjsip_tx_data *tdata; + pj_uint32_t rseq; + pj_int32_t cseq; + pj_str_t method; + pj_status_t status; + + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + PJ_ASSERT_RETURN(dd != NULL, PJSIP_ENOTINITIALIZED); + + tsx = pjsip_rdata_get_tsx(rdata); + pj_assert(tsx != NULL); + + msg = rdata->msg_info.msg; + + /* Always reply with 200/OK for PRACK */ + status = pjsip_dlg_create_response(inv->dlg, rdata, 200, NULL, &tdata); + if (status == PJ_SUCCESS) { + status = pjsip_dlg_send_response(inv->dlg, tsx, tdata); + } - /* Check that 100rel module has been initialized */ - PJ_ASSERT_RETURN(mod_100rel.mod.id >= 0, PJ_EINVALIDOP); + /* Ignore if we don't have pending transmission */ + if (dd->uas_state == NULL || pj_list_empty(&dd->uas_state->tx_data_list)) { + PJ_LOG(4,(dd->inv->dlg->obj_name, + "PRACK ignored - no pending response")); + return PJ_EIGNORED; + } - /* Create and attach as dialog usage */ - dd = PJ_POOL_ZALLOC_T(inv->dlg->pool, dlg_data); - dd->inv = inv; - pjsip_dlg_add_usage(inv->dlg, &mod_100rel.mod, (void*)dd); + /* Find RAck header */ + rack_hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(msg, &RACK, NULL); + if (!rack_hdr) { + /* RAck header not found */ + PJ_LOG(4,(dd->inv->dlg->obj_name, "No RAck header")); + return PJSIP_EMISSINGHDR; + } - PJ_LOG(5,(dd->inv->dlg->obj_name, "100rel module attached")); + /* Parse RAck header */ + parse_rack(&rack_hdr->hvalue, &rseq, &cseq, &method); - return PJ_SUCCESS; + + /* Match RAck against outgoing transmission */ + if (rseq == dd->uas_state->tx_data_list.next->rseq && + cseq == dd->uas_state->cseq) + { + /* + * Yes this PRACK matches outgoing transmission. + */ + tx_data_list_t *tl = dd->uas_state->tx_data_list.next; + + if (dd->uas_state->retransmit_timer.id) { + pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, + &dd->uas_state->retransmit_timer); + dd->uas_state->retransmit_timer.id = PJ_FALSE; + } + + /* Remove from the list */ + if (tl != &dd->uas_state->tx_data_list) { + pj_list_erase(tl); + + /* Destroy the response */ + pjsip_tx_data_dec_ref(tl->tdata); + } + + /* Schedule next packet */ + dd->uas_state->retransmit_count = 0; + if (!pj_list_empty(&dd->uas_state->tx_data_list)) { + on_retransmit(NULL, &dd->uas_state->retransmit_timer); + } + + } else { + /* No it doesn't match */ + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Rx PRACK with no matching reliable response")); + return PJ_EIGNORED; + } + + return PJ_SUCCESS; } @@ -497,136 +502,138 @@ PJ_DEF(pj_status_t) pjsip_100rel_attach(pjsip_inv_session *inv) static void on_retransmit(pj_timer_heap_t *timer_heap, struct pj_timer_entry *entry) { - dlg_data *dd; - tx_data_list_t *tl; - pjsip_tx_data *tdata; - pj_bool_t final; - pj_time_val delay; - - PJ_UNUSED_ARG(timer_heap); - - dd = (dlg_data*) entry->user_data; - - entry->id = PJ_FALSE; - - ++dd->uas_state->retransmit_count; - if (dd->uas_state->retransmit_count >= 7) { - /* If a reliable provisional response is retransmitted for - 64*T1 seconds without reception of a corresponding PRACK, - the UAS SHOULD reject the original request with a 5xx - response. - */ - pj_str_t reason = pj_str("Reliable response timed out"); - pj_status_t status; - - /* Clear all pending responses */ - clear_all_responses(dd); - - /* Send 500 response */ - status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata); - if (status == PJ_SUCCESS) { - pjsip_dlg_send_response(dd->inv->dlg, - dd->inv->invite_tsx, - tdata); - } - return; + dlg_data *dd; + tx_data_list_t *tl; + pjsip_tx_data *tdata; + pj_bool_t final; + pj_time_val delay; + + PJ_UNUSED_ARG(timer_heap); + + dd = (dlg_data*) entry->user_data; + + entry->id = PJ_FALSE; + + ++dd->uas_state->retransmit_count; + if (dd->uas_state->retransmit_count >= 7) { + /* If a reliable provisional response is retransmitted for + 64*T1 seconds without reception of a corresponding PRACK, + the UAS SHOULD reject the original request with a 5xx + response. + */ + pj_str_t reason = pj_str("Reliable response timed out"); + pj_status_t status; + + /* Clear all pending responses */ + clear_all_responses(dd); + + /* Send 500 response */ + status = pjsip_inv_end_session(dd->inv, 500, &reason, &tdata); + if (status == PJ_SUCCESS) { + pjsip_dlg_send_response(dd->inv->dlg, + dd->inv->invite_tsx, + tdata); } + return; + } - pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list)); - tl = dd->uas_state->tx_data_list.next; - tdata = tl->tdata; + pj_assert(!pj_list_empty(&dd->uas_state->tx_data_list)); + tl = dd->uas_state->tx_data_list.next; + tdata = tl->tdata; - pjsip_tx_data_add_ref(tdata); - final = tdata->msg->line.status.code >= 200; + pjsip_tx_data_add_ref(tdata); + final = tdata->msg->line.status.code >= 200; - if (dd->uas_state->retransmit_count == 1) { - pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata); - } else { - pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata); - } + if (dd->uas_state->retransmit_count == 1) { + pjsip_tsx_send_msg(dd->inv->invite_tsx, tdata); + } else { + pjsip_tsx_retransmit_no_state(dd->inv->invite_tsx, tdata); + } - if (final) { - /* This is final response, which will be retransmitted by - * UA layer. There's no more task to do, so clear the - * transmission list and bail out. - */ - clear_all_responses(dd); - return; - } + if (final) { + /* This is final response, which will be retransmitted by + * UA layer. There's no more task to do, so clear the + * transmission list and bail out. + */ + clear_all_responses(dd); + return; + } - /* Schedule next retransmission */ - if (dd->uas_state->retransmit_count < 6) { - delay.sec = 0; - delay.msec = (1 << dd->uas_state->retransmit_count) * - PJSIP_T1_TIMEOUT; - pj_time_val_normalize(&delay); - } else { - delay.sec = 1; - delay.msec = 500; - } + /* Schedule next retransmission */ + if (dd->uas_state->retransmit_count < 6) { + delay.sec = 0; + delay.msec = (1 << dd->uas_state->retransmit_count) * + PJSIP_T1_TIMEOUT; + pj_time_val_normalize(&delay); + } else { + delay.sec = 1; + delay.msec = 500; + } - pjsip_endpt_schedule_timer(dd->inv->dlg->endpt, - &dd->uas_state->retransmit_timer, - &delay); + pjsip_endpt_schedule_timer(dd->inv->dlg->endpt, + &dd->uas_state->retransmit_timer, + &delay); - entry->id = PJ_TRUE; + entry->id = PJ_TRUE; } + /* Clone response. */ static pjsip_tx_data *clone_tdata(dlg_data *dd, const pjsip_tx_data *src) { - pjsip_tx_data *dst; - const pjsip_hdr *hsrc; - pjsip_msg *msg; - pj_status_t status; + pjsip_tx_data *dst; + const pjsip_hdr *hsrc; + pjsip_msg *msg; + pj_status_t status; - status = pjsip_endpt_create_tdata(dd->inv->dlg->endpt, &dst); - if (status != PJ_SUCCESS) - return NULL; - - msg = pjsip_msg_create(dst->pool, PJSIP_RESPONSE_MSG); - dst->msg = msg; - pjsip_tx_data_add_ref(dst); - - /* Duplicate status line */ - msg->line.status.code = src->msg->line.status.code; - pj_strdup(dst->pool, &msg->line.status.reason, - &src->msg->line.status.reason); - - /* Duplicate all headers */ - hsrc = src->msg->hdr.next; - while (hsrc != &src->msg->hdr) { - pjsip_hdr *h = (pjsip_hdr*) pjsip_hdr_clone(dst->pool, hsrc); - pjsip_msg_add_hdr(msg, h); - hsrc = hsrc->next; - } + status = pjsip_endpt_create_tdata(dd->inv->dlg->endpt, &dst); + if (status != PJ_SUCCESS) + return NULL; + + msg = pjsip_msg_create(dst->pool, PJSIP_RESPONSE_MSG); + dst->msg = msg; + pjsip_tx_data_add_ref(dst); + + /* Duplicate status line */ + msg->line.status.code = src->msg->line.status.code; + pj_strdup(dst->pool, &msg->line.status.reason, + &src->msg->line.status.reason); + + /* Duplicate all headers */ + hsrc = src->msg->hdr.next; + while (hsrc != &src->msg->hdr) { + pjsip_hdr *h = (pjsip_hdr*) pjsip_hdr_clone(dst->pool, hsrc); + pjsip_msg_add_hdr(msg, h); + hsrc = hsrc->next; + } - /* Duplicate message body */ - if (src->msg->body) - msg->body = pjsip_msg_body_clone(dst->pool, src->msg->body); + /* Duplicate message body */ + if (src->msg->body) + msg->body = pjsip_msg_body_clone(dst->pool, src->msg->body); - PJ_LOG(5,(dd->inv->dlg->obj_name, - "Reliable response %s created", - pjsip_tx_data_get_info(dst))); + PJ_LOG(5,(dd->inv->dlg->obj_name, + "Reliable response %s created", + pjsip_tx_data_get_info(dst))); - return dst; + return dst; } -/* Check if pending response has SDP */ + +/* Check if any pending response in transmission list has SDP */ static pj_bool_t has_sdp(dlg_data *dd) { - tx_data_list_t *tl; + tx_data_list_t *tl; - tl = dd->uas_state->tx_data_list.next; - while (tl != &dd->uas_state->tx_data_list) { - if (tl->tdata->msg->body) - return PJ_TRUE; - tl = tl->next; - } + tl = dd->uas_state->tx_data_list.next; + while (tl != &dd->uas_state->tx_data_list) { + if (tl->tdata->msg->body) + return PJ_TRUE; + tl = tl->next; + } - return PJ_FALSE; + return PJ_FALSE; } @@ -634,212 +641,221 @@ static pj_bool_t has_sdp(dlg_data *dd) PJ_DEF(pj_status_t) pjsip_100rel_tx_response(pjsip_inv_session *inv, pjsip_tx_data *tdata) { - pjsip_cseq_hdr *cseq_hdr; - pjsip_generic_string_hdr *rseq_hdr; - pjsip_require_hdr *req_hdr; - int status_code; - dlg_data *dd; - pjsip_tx_data *old_tdata; - pj_status_t status; - - PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG, - PJ_EINVALIDOP); - - status_code = tdata->msg->line.status.code; - - /* 100 response doesn't need PRACK */ - if (status_code == 100) - return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); - - /* Get the dialog data */ - dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; - PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP); - - - /* Clone tdata */ - old_tdata = tdata; - tdata = clone_tdata(dd, old_tdata); - pjsip_tx_data_dec_ref(old_tdata); - - /* Get CSeq header */ - cseq_hdr = (pjsip_cseq_hdr*) - pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); - PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG); - PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD, - PJ_EINVALIDOP); - - /* Remove existing Require header */ - req_hdr = find_req_hdr(tdata->msg); - if (req_hdr) { - pj_list_erase(req_hdr); + pjsip_cseq_hdr *cseq_hdr; + pjsip_generic_string_hdr *rseq_hdr; + pjsip_require_hdr *req_hdr; + int status_code; + dlg_data *dd; + pjsip_tx_data *old_tdata; + pj_status_t status; + + PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG, + PJSIP_ENOTRESPONSEMSG); + + status_code = tdata->msg->line.status.code; + + /* 100 response doesn't need PRACK */ + if (status_code == 100) + return pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); + + + /* Get the 100rel data attached to this dialog */ + dd = (dlg_data*) inv->dlg->mod_data[mod_100rel.mod.id]; + PJ_ASSERT_RETURN(dd != NULL, PJ_EINVALIDOP); + + + /* Clone tdata. + * We need to clone tdata because we may need to keep it in our + * retransmission list, while the original dialog may modify it + * if it wants to send another response. + */ + old_tdata = tdata; + tdata = clone_tdata(dd, old_tdata); + pjsip_tx_data_dec_ref(old_tdata); + + + /* Get CSeq header, and make sure this is INVITE response */ + cseq_hdr = (pjsip_cseq_hdr*) + pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CSEQ, NULL); + PJ_ASSERT_RETURN(cseq_hdr != NULL, PJ_EBUG); + PJ_ASSERT_RETURN(cseq_hdr->method.id == PJSIP_INVITE_METHOD, + PJ_EINVALIDOP); + + /* Remove existing Require header */ + req_hdr = find_req_hdr(tdata->msg); + if (req_hdr) { + pj_list_erase(req_hdr); + } + + /* Remove existing RSeq header */ + rseq_hdr = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL); + if (rseq_hdr) + pj_list_erase(rseq_hdr); + + /* Different treatment for provisional and final response */ + if (status_code/100 == 2) { + + /* RFC 3262 Section 3: UAS Behavior: + + The UAS MAY send a final response to the initial request + before having received PRACKs for all unacknowledged + reliable provisional responses, unless the final response + is 2xx and any of the unacknowledged reliable provisional + responses contained a session description. In that case, + it MUST NOT send a final response until those provisional + responses are acknowledged. + */ + + if (dd->uas_state && has_sdp(dd)) { + /* Yes we have transmitted 1xx with SDP reliably. + * In this case, must queue the 2xx response. + */ + tx_data_list_t *tl; + + tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t); + tl->tdata = tdata; + tl->rseq = (pj_uint32_t)-1; + pj_list_push_back(&dd->uas_state->tx_data_list, tl); + + /* Will send later */ + status = PJ_SUCCESS; + + PJ_LOG(4,(dd->inv->dlg->obj_name, + "2xx response will be sent after PRACK")); + + } else if (dd->uas_state) { + /* + RFC 3262 Section 3: UAS Behavior: + + If the UAS does send a final response when reliable + responses are still unacknowledged, it SHOULD NOT + continue to retransmit the unacknowledged reliable + provisional responses, but it MUST be prepared to + process PRACK requests for those outstanding + responses. + */ + + PJ_LOG(4,(dd->inv->dlg->obj_name, + "No SDP sent so far, sending 2xx now")); + + /* Cancel the retransmit timer */ + if (dd->uas_state->retransmit_timer.id) { + pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, + &dd->uas_state->retransmit_timer); + dd->uas_state->retransmit_timer.id = PJ_FALSE; + } + + /* Clear all pending responses (drop 'em) */ + clear_all_responses(dd); + + /* And transmit the 2xx response */ + status=pjsip_dlg_send_response(inv->dlg, + inv->invite_tsx, tdata); + + } else { + /* We didn't send any reliable provisional response */ + + /* Transmit the 2xx response */ + status=pjsip_dlg_send_response(inv->dlg, + inv->invite_tsx, tdata); } - - /* Remove existing RSeq header */ - rseq_hdr = (pjsip_generic_string_hdr*) - pjsip_msg_find_hdr_by_name(tdata->msg, &RSEQ, NULL); - if (rseq_hdr) - pj_list_erase(rseq_hdr); - - /* Different treatment for provisional and final response */ - if (status_code/100 == 2) { - - /* RFC 3262 Section 3: UAS Behavior: - - The UAS MAY send a final response to the initial request - before having received PRACKs for all unacknowledged - reliable provisional responses, unless the final response - is 2xx and any of the unacknowledged reliable provisional - responses contained a session description. In that case, - it MUST NOT send a final response until those provisional - responses are acknowledged. - */ - - if (dd->uas_state && has_sdp(dd)) { - /* Yes we have transmitted 1xx with SDP reliably. - * In this case, must queue the 2xx response. - */ - tx_data_list_t *tl; - - tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t); - tl->tdata = tdata; - tl->rseq = (pj_uint32_t)-1; - pj_list_push_back(&dd->uas_state->tx_data_list, tl); - - /* Will send later */ - status = PJ_SUCCESS; - - PJ_LOG(4,(dd->inv->dlg->obj_name, - "2xx response will be sent after PRACK")); - - } else if (dd->uas_state) { - /* - If the UAS does send a final response when reliable - responses are still unacknowledged, it SHOULD NOT - continue to retransmit the unacknowledged reliable - provisional responses, but it MUST be prepared to - process PRACK requests for those outstanding - responses. - */ - - PJ_LOG(4,(dd->inv->dlg->obj_name, - "No SDP sent so far, sending 2xx now")); - - /* Cancel the retransmit timer */ - if (dd->uas_state->retransmit_timer.id) { - pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, - &dd->uas_state->retransmit_timer); - dd->uas_state->retransmit_timer.id = PJ_FALSE; - } - - /* Clear all pending responses (drop 'em) */ - clear_all_responses(dd); - - /* And transmit the 2xx response */ - status=pjsip_dlg_send_response(inv->dlg, - inv->invite_tsx, tdata); - - } else { - /* We didn't send any reliable provisional response */ - - /* Transmit the 2xx response */ - status=pjsip_dlg_send_response(inv->dlg, - inv->invite_tsx, tdata); - } - - } else if (status_code >= 300) { - - /* - If the UAS does send a final response when reliable - responses are still unacknowledged, it SHOULD NOT - continue to retransmit the unacknowledged reliable - provisional responses, but it MUST be prepared to - process PRACK requests for those outstanding - responses. - */ - - /* Cancel the retransmit timer */ - if (dd->uas_state && dd->uas_state->retransmit_timer.id) { - pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, - &dd->uas_state->retransmit_timer); - dd->uas_state->retransmit_timer.id = PJ_FALSE; - - /* Clear all pending responses (drop 'em) */ - clear_all_responses(dd); - } - - /* And transmit the 2xx response */ - status=pjsip_dlg_send_response(inv->dlg, - inv->invite_tsx, tdata); - + + } else if (status_code >= 300) { + + /* + RFC 3262 Section 3: UAS Behavior: + + If the UAS does send a final response when reliable + responses are still unacknowledged, it SHOULD NOT + continue to retransmit the unacknowledged reliable + provisional responses, but it MUST be prepared to + process PRACK requests for those outstanding + responses. + */ + + /* Cancel the retransmit timer */ + if (dd->uas_state && dd->uas_state->retransmit_timer.id) { + pjsip_endpt_cancel_timer(dd->inv->dlg->endpt, + &dd->uas_state->retransmit_timer); + dd->uas_state->retransmit_timer.id = PJ_FALSE; + + /* Clear all pending responses (drop 'em) */ + clear_all_responses(dd); + } + + /* And transmit the 2xx response */ + status=pjsip_dlg_send_response(inv->dlg, + inv->invite_tsx, tdata); + + } else { + /* + * This is provisional response. + */ + char rseq_str[32]; + pj_str_t rseq; + tx_data_list_t *tl; + + /* Create UAS state if we don't have one */ + if (dd->uas_state == NULL) { + dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool, + uas_state_t); + dd->uas_state->cseq = cseq_hdr->cseq; + dd->uas_state->rseq = pj_rand() % 0x7FFF; + pj_list_init(&dd->uas_state->tx_data_list); + dd->uas_state->retransmit_timer.user_data = dd; + dd->uas_state->retransmit_timer.cb = &on_retransmit; + } + + /* Check that CSeq match */ + PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq, + PJ_EINVALIDOP); + + /* Add Require header */ + req_hdr = pjsip_require_hdr_create(tdata->pool); + req_hdr->count = 1; + req_hdr->values[0] = tag_100rel; + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr); + + /* Add RSeq header */ + pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u", + dd->uas_state->rseq); + rseq = pj_str(rseq_str); + rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool, + &RSEQ, &rseq); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr); + + /* Create list entry for this response */ + tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t); + tl->tdata = tdata; + tl->rseq = dd->uas_state->rseq++; + + /* Add to queue if there's pending response, otherwise + * transmit immediately. + */ + if (!pj_list_empty(&dd->uas_state->tx_data_list)) { + + int code = tdata->msg->line.status.code; + + /* Will send later */ + pj_list_push_back(&dd->uas_state->tx_data_list, tl); + status = PJ_SUCCESS; + + PJ_LOG(4,(dd->inv->dlg->obj_name, + "Reliable %d response enqueued (%d pending)", + code, pj_list_size(&dd->uas_state->tx_data_list))); + } else { - /* - * This is provisional response. - */ - char rseq_str[32]; - pj_str_t rseq; - tx_data_list_t *tl; - - /* Create UAS state if we don't have one */ - if (dd->uas_state == NULL) { - dd->uas_state = PJ_POOL_ZALLOC_T(inv->dlg->pool, - uas_state_t); - dd->uas_state->cseq = cseq_hdr->cseq; - dd->uas_state->rseq = pj_rand() % 0x7FFF; - pj_list_init(&dd->uas_state->tx_data_list); - dd->uas_state->retransmit_timer.user_data = dd; - dd->uas_state->retransmit_timer.cb = &on_retransmit; - } - - /* Check that CSeq match */ - PJ_ASSERT_RETURN(cseq_hdr->cseq == dd->uas_state->cseq, - PJ_EINVALIDOP); - - /* Add Require header */ - req_hdr = pjsip_require_hdr_create(tdata->pool); - req_hdr->count = 1; - req_hdr->values[0] = tag_100rel; - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)req_hdr); - - /* Add RSeq header */ - pj_ansi_snprintf(rseq_str, sizeof(rseq_str), "%u", - dd->uas_state->rseq); - rseq = pj_str(rseq_str); - rseq_hdr = pjsip_generic_string_hdr_create(tdata->pool, - &RSEQ, &rseq); - pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)rseq_hdr); - - /* Create list entry for this response */ - tl = PJ_POOL_ZALLOC_T(tdata->pool, tx_data_list_t); - tl->tdata = tdata; - tl->rseq = dd->uas_state->rseq++; - - /* Add to queue if there's pending response, otherwise - * transmit immediately. - */ - if (!pj_list_empty(&dd->uas_state->tx_data_list)) { - - int code = tdata->msg->line.status.code; - - /* Will send later */ - pj_list_push_back(&dd->uas_state->tx_data_list, tl); - status = PJ_SUCCESS; - - PJ_LOG(4,(dd->inv->dlg->obj_name, - "Reliable %d response enqueued (%d pending)", - code, pj_list_size(&dd->uas_state->tx_data_list))); - - } else { - pj_list_push_back(&dd->uas_state->tx_data_list, tl); - - dd->uas_state->retransmit_count = 0; - on_retransmit(NULL, &dd->uas_state->retransmit_timer); - status = PJ_SUCCESS; - } - + pj_list_push_back(&dd->uas_state->tx_data_list, tl); + + dd->uas_state->retransmit_count = 0; + on_retransmit(NULL, &dd->uas_state->retransmit_timer); + status = PJ_SUCCESS; } - - return status; + + } + + return status; } -#endif /* PJSIP_HAS_100REL */ diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c index 0243452a..6c7fe222 100644 --- a/pjsip/src/pjsip-ua/sip_inv.c +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -31,6 +31,28 @@ #include <pj/os.h> #include <pj/log.h> +/* + * Note on offer/answer: + * + * The offer/answer framework in this implementation assumes the occurence + * of SDP in a particular request/response according to this table: + + offer answer Note: + ======================================================================== + INVITE X INVITE may contain offer + 18x/INVITE X X Response may contain offer or answer + 2xx/INVITE X X Response may contain offer or answer + ACK X ACK may contain answer + + PRACK X PRACK can only contain answer + 2xx/PRACK Response may not have offer nor answer + + UPDATE X UPDATE may only contain offer + 2xx/UPDATE X Response may only contain answer + ======================================================================== + + * + */ #define THIS_FILE "sip_inv.c" @@ -46,6 +68,13 @@ static const char *inv_state_names[] = "TERMINATED", }; +/* UPDATE method */ +const pjsip_method pjsip_update_method = +{ + PJSIP_OTHER_METHOD, + { "UPDATE", 6 } +}; + /* * Static prototypes. */ @@ -66,6 +95,9 @@ static void inv_on_state_disconnected( pjsip_inv_session *inv, pjsip_event *e); static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_rx_data *rdata); +static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv ); +static pjsip_msg_body *create_sdp_body(pj_pool_t *pool, + const pjmedia_sdp_session *c_sdp); static pj_status_t process_answer( pjsip_inv_session *inv, int st_code, pjsip_tx_data *tdata, @@ -119,10 +151,11 @@ struct tsx_inv_data */ static pj_status_t mod_inv_load(pjsip_endpoint *endpt) { - pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6}}; + pj_str_t allowed[] = {{"INVITE", 6}, {"ACK",3}, {"BYE",3}, {"CANCEL",6}, + { "UPDATE", 6}}; pj_str_t accepted = { "application/sdp", 15 }; - /* Register supported methods: INVITE, ACK, BYE, CANCEL */ + /* Register supported methods: INVITE, ACK, BYE, CANCEL, UPDATE */ pjsip_endpt_add_capability(endpt, &mod_inv.mod, PJSIP_H_ALLOW, NULL, PJ_ARRAY_SIZE(allowed), allowed); @@ -186,6 +219,11 @@ void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state, if (inv->state == PJSIP_INV_STATE_DISCONNECTED && prev_state != PJSIP_INV_STATE_DISCONNECTED) { + if (inv->last_ack) { + pjsip_tx_data_dec_ref(inv->last_ack); + inv->last_ack = NULL; + } + pjsip_100rel_end_session(inv); pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod); } } @@ -209,27 +247,103 @@ void inv_set_cause(pjsip_inv_session *inv, int cause_code, } +/* + * Check if outgoing request needs to have SDP answer. + * This applies for both ACK and PRACK requests. + */ +static pjmedia_sdp_session *inv_has_pending_answer(pjsip_inv_session *inv, + pjsip_transaction *tsx) +{ + pjmedia_sdp_neg_state neg_state; + pjmedia_sdp_session *sdp = NULL; + pj_status_t status; + + /* If SDP negotiator is ready, start negotiation. */ + + /* Start nego when appropriate. */ + neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) : + PJMEDIA_SDP_NEG_STATE_NULL; + + if (neg_state == PJMEDIA_SDP_NEG_STATE_DONE) { + + /* Nothing to do */ + + } else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO && + pjmedia_sdp_neg_has_local_answer(inv->neg) ) + { + struct tsx_inv_data *tsx_inv_data; + + /* Get invite session's transaction data */ + tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id]; + + status = inv_negotiate_sdp(inv); + if (status != PJ_SUCCESS) + return NULL; + + /* Mark this transaction has having SDP offer/answer done. */ + tsx_inv_data->sdp_done = 1; + + status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp); + + } else { + /* This remark is only valid for ACK. + PJ_LOG(4,(inv->dlg->obj_name, + "FYI, the SDP negotiator state (%s) is in a mess " + "when sending this ACK/PRACK request", + pjmedia_sdp_neg_state_str(neg_state))); + */ + } + + return sdp; +} + /* * Send ACK for 2xx response. */ static pj_status_t inv_send_ack(pjsip_inv_session *inv, pjsip_rx_data *rdata) { - pjsip_tx_data *tdata; pj_status_t status; PJ_LOG(5,(inv->obj_name, "Received %s, sending ACK", pjsip_rx_data_get_info(rdata))); - status = pjsip_dlg_create_request(inv->dlg, pjsip_get_ack_method(), - rdata->msg_info.cseq->cseq, &tdata); - if (status != PJ_SUCCESS) { - /* Better luck next time */ - pj_assert(!"Unable to create ACK!"); - return status; + /* Check if we have cached ACK request */ + if (inv->last_ack && rdata->msg_info.cseq->cseq == inv->last_ack_cseq) { + pjsip_tx_data_add_ref(inv->last_ack); + } else { + pjmedia_sdp_session *sdp = NULL; + + /* Destroy last_ack */ + if (inv->last_ack) { + pjsip_tx_data_dec_ref(inv->last_ack); + inv->last_ack = NULL; + } + + /* Create new ACK request */ + status = pjsip_dlg_create_request(inv->dlg, pjsip_get_ack_method(), + rdata->msg_info.cseq->cseq, + &inv->last_ack); + if (status != PJ_SUCCESS) { + /* Better luck next time */ + pj_assert(!"Unable to create ACK!"); + return status; + } + + /* See if we have pending SDP answer to send */ + sdp = inv_has_pending_answer(inv, inv->invite_tsx); + if (sdp) { + inv->last_ack->msg->body=create_sdp_body(inv->last_ack->pool, sdp); + } + + + /* Keep this for subsequent response retransmission */ + inv->last_ack_cseq = rdata->msg_info.cseq->cseq; + pjsip_tx_data_add_ref(inv->last_ack); } - status = pjsip_dlg_send_request(inv->dlg, tdata, -1, NULL); + /* Send ACK */ + status = pjsip_dlg_send_request(inv->dlg, inv->last_ack, -1, NULL); if (status != PJ_SUCCESS) { /* Better luck next time */ pj_assert(!"Unable to send ACK!"); @@ -492,14 +606,6 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg, if (options & PJSIP_INV_REQUIRE_100REL) options |= PJSIP_INV_SUPPORT_100REL; -#if !PJSIP_HAS_100REL - /* options cannot specify 100rel if 100rel is disabled */ - PJ_ASSERT_RETURN( - (options & (PJSIP_INV_REQUIRE_100REL | PJSIP_INV_SUPPORT_100REL))==0, - PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EXTENSION)); - -#endif - if (options & PJSIP_INV_REQUIRE_TIMER) options |= PJSIP_INV_SUPPORT_TIMER; @@ -538,10 +644,8 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uac( pjsip_dialog *dlg, /* Increment dialog session */ pjsip_dlg_inc_session(dlg, &mod_inv.mod); -#if PJSIP_HAS_100REL /* Create 100rel handler */ pjsip_100rel_attach(inv); -#endif /* Done */ *p_inv = inv; @@ -943,14 +1047,6 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg, if (options & PJSIP_INV_REQUIRE_100REL) options |= PJSIP_INV_SUPPORT_100REL; -#if !PJSIP_HAS_100REL - /* options cannot specify 100rel if 100rel is disabled */ - PJ_ASSERT_RETURN( - (options & (PJSIP_INV_REQUIRE_100REL | PJSIP_INV_SUPPORT_100REL))==0, - PJSIP_ERRNO_FROM_SIP_STATUS(PJSIP_SC_BAD_EXTENSION)); - -#endif - if (options & PJSIP_INV_REQUIRE_TIMER) options |= PJSIP_INV_SUPPORT_TIMER; @@ -1020,12 +1116,10 @@ PJ_DEF(pj_status_t) pjsip_inv_create_uas( pjsip_dialog *dlg, tsx_inv_data->inv = inv; inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data; -#if PJSIP_HAS_100REL /* Create 100rel handler */ if (inv->options & PJSIP_INV_REQUIRE_100REL) { pjsip_100rel_attach(inv); } -#endif /* Done */ pjsip_dlg_dec_lock(dlg); @@ -1239,7 +1333,7 @@ on_return: /* - * Negotiate SDP. + * Initiate SDP negotiation in the SDP negotiator. */ static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv ) { @@ -1781,16 +1875,84 @@ on_return: */ PJ_DEF(pj_status_t) pjsip_inv_update ( pjsip_inv_session *inv, const pj_str_t *new_contact, - const pjmedia_sdp_session *new_offer, + const pjmedia_sdp_session *offer, pjsip_tx_data **p_tdata ) { - PJ_UNUSED_ARG(inv); - PJ_UNUSED_ARG(new_contact); - PJ_UNUSED_ARG(new_offer); - PJ_UNUSED_ARG(p_tdata); + pjsip_contact_hdr *contact_hdr = NULL; + pjsip_tx_data *tdata = NULL; + pjmedia_sdp_session *sdp_copy; + pj_status_t status = PJ_SUCCESS; + + /* Verify arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata && offer, PJ_EINVAL); + + /* Dialog must have been established */ + PJ_ASSERT_RETURN(inv->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED, + PJ_EINVALIDOP); + + /* Invite session must not have been disconnected */ + PJ_ASSERT_RETURN(inv->state < PJSIP_INV_STATE_DISCONNECTED, + PJ_EINVALIDOP); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(inv->dlg); - PJ_TODO(CREATE_UPDATE_REQUEST); - return PJ_ENOTSUP; + /* Process offer */ + if (pjmedia_sdp_neg_get_state(inv->neg)!=PJMEDIA_SDP_NEG_STATE_DONE) { + PJ_LOG(4,(inv->dlg->obj_name, + "Invalid SDP offer/answer state for UPDATE")); + status = PJ_EINVALIDOP; + goto on_error; + } + + status = pjmedia_sdp_neg_modify_local_offer(inv->pool,inv->neg, + offer); + if (status != PJ_SUCCESS) + goto on_error; + + + /* Update Contact if required */ + if (new_contact) { + pj_str_t tmp; + const pj_str_t STR_CONTACT = { "Contact", 7 }; + + pj_strdup_with_null(inv->dlg->pool, &tmp, new_contact); + contact_hdr = (pjsip_contact_hdr*) + pjsip_parse_hdr(inv->dlg->pool, &STR_CONTACT, + tmp.ptr, tmp.slen, NULL); + if (!contact_hdr) { + status = PJSIP_EINVALIDURI; + goto on_error; + } + + inv->dlg->local.contact = contact_hdr; + } + + /* Create request */ + status = pjsip_dlg_create_request(inv->dlg, &pjsip_update_method, + -1, &tdata); + if (status != PJ_SUCCESS) + goto on_error; + + /* Attach SDP body */ + sdp_copy = pjmedia_sdp_session_clone(tdata->pool, offer); + pjsip_create_sdp_body(tdata->pool, sdp_copy, &tdata->msg->body); + + /* Unlock dialog. */ + pjsip_dlg_dec_lock(inv->dlg); + + *p_tdata = tdata; + + return PJ_SUCCESS; + +on_error: + if (tdata) + pjsip_tx_data_dec_ref(tdata); + + /* Unlock dialog. */ + pjsip_dlg_dec_lock(inv->dlg); + + return status; } /* @@ -1832,13 +1994,11 @@ PJ_DEF(pj_status_t) pjsip_inv_send_msg( pjsip_inv_session *inv, && (cseq->cseq == inv->invite_tsx->cseq), PJ_EINVALIDOP); -#if PJSIP_HAS_100REL if (inv->options & PJSIP_INV_REQUIRE_100REL) { - status = pjsip_100rel_tx_response(inv, tdata); + status = pjsip_100rel_tx_response(inv, tdata); } else -#endif { - status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); + status = pjsip_dlg_send_response(inv->dlg, inv->invite_tsx, tdata); } if (status != PJ_SUCCESS) @@ -1987,6 +2147,188 @@ static void inv_handle_bye_response( pjsip_inv_session *inv, } /* + * Respond to incoming UPDATE request. + */ +static void inv_respond_incoming_update(pjsip_inv_session *inv, + pjsip_rx_data *rdata) +{ + pjmedia_sdp_neg_state neg_state; + pj_status_t status; + pjsip_tx_data *tdata = NULL; + + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + + /* Send 491 if we receive UPDATE while we're waiting for an answer */ + if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) { + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_REQUEST_PENDING, NULL, + &tdata); + } + /* Send 500 with Retry-After header set randomly between 0 and 10 if we + * receive UPDATE while we haven't sent answer. + */ + else if (neg_state == PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER || + neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) { + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_INTERNAL_SERVER_ERROR, + NULL, &tdata); + + } else { + /* We receive new offer from remote */ + inv_check_sdp_in_incoming_msg(inv, pjsip_rdata_get_tsx(rdata), rdata); + + /* Application MUST have supplied the answer by now. + * If so, negotiate the SDP. + */ + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + if (neg_state != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO || + (status=inv_negotiate_sdp(inv)) != PJ_SUCCESS) + { + /* Negotiation has failed */ + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_NOT_ACCEPTABLE_HERE, + NULL, &tdata); + } else { + /* New media has been negotiated successfully, send 200/OK */ + status = pjsip_dlg_create_response(inv->dlg, rdata, + PJSIP_SC_OK, NULL, &tdata); + if (status == PJ_SUCCESS) { + pjmedia_sdp_session *sdp; + status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp); + if (status == PJ_SUCCESS) + tdata->msg->body = create_sdp_body(tdata->pool, sdp); + } + } + } + + if (status != PJ_SUCCESS) { + if (tdata != NULL) { + pjsip_tx_data_dec_ref(tdata); + tdata = NULL; + } + return; + } + + pjsip_dlg_send_response(inv->dlg, pjsip_rdata_get_tsx(rdata), tdata); +} + + +/* + * Handle incoming response to UAC UPDATE request. + */ +static void inv_handle_update_response( pjsip_inv_session *inv, + pjsip_event *e) +{ + pjsip_transaction *tsx = e->body.tsx_state.tsx; + struct tsx_inv_data *tsx_inv_data = NULL; + pj_status_t status = -1; + + /* Process 2xx response */ + if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + tsx->status_code/100 == 2 && + e->body.tsx_state.src.rdata->msg_info.msg->body) + { + status = inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + + } else { + /* Get/attach invite session's transaction data */ + tsx_inv_data = (struct tsx_inv_data*)tsx->mod_data[mod_inv.mod.id]; + if (tsx_inv_data == NULL) { + tsx_inv_data=PJ_POOL_ZALLOC_T(tsx->pool, struct tsx_inv_data); + tsx_inv_data->inv = inv; + tsx->mod_data[mod_inv.mod.id] = tsx_inv_data; + } + } + + /* Otherwise if we don't get successful response, cancel + * our negotiator. + */ + if (status != PJ_SUCCESS && + pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER && + tsx_inv_data && tsx_inv_data->sdp_done == PJ_FALSE) + { + pjmedia_sdp_neg_cancel_offer(inv->neg); + + /* Prevent from us cancelling different offer! */ + tsx_inv_data->sdp_done = PJ_TRUE; + } +} + + +/* + * Handle incoming reliable response. + */ +static void inv_handle_incoming_reliable_response(pjsip_inv_session *inv, + pjsip_rx_data *rdata) +{ + pjsip_tx_data *tdata; + pjmedia_sdp_session *sdp; + pj_status_t status; + + /* Create PRACK */ + status = pjsip_100rel_create_prack(inv, rdata, &tdata); + if (status != PJ_SUCCESS) + return; + + /* See if we need to attach SDP answer on the PRACK request */ + sdp = inv_has_pending_answer(inv, pjsip_rdata_get_tsx(rdata)); + if (sdp) { + tdata->msg->body = create_sdp_body(tdata->pool, sdp); + } + + /* Send PRACK (must be using 100rel module!) */ + pjsip_100rel_send_prack(inv, tdata); +} + + +/* + * Handle incoming PRACK. + */ +static void inv_respond_incoming_prack(pjsip_inv_session *inv, + pjsip_rx_data *rdata) +{ + pj_status_t status; + + /* Run through 100rel module to see if we can accept this + * PRACK request. The 100rel will send 200/OK to PRACK request. + */ + status = pjsip_100rel_on_rx_prack(inv, rdata); + if (status != PJ_SUCCESS) + return; + + /* Now check for SDP answer in the PRACK request */ + if (rdata->msg_info.msg->body) { + status = inv_check_sdp_in_incoming_msg(inv, + pjsip_rdata_get_tsx(rdata), rdata); + } else { + /* No SDP body */ + status = -1; + } + + /* If SDP negotiation has been successful, also mark the + * SDP negotiation flag in the invite transaction to be + * done too. + */ + if (status == PJ_SUCCESS && inv->invite_tsx) { + struct tsx_inv_data *tsx_inv_data; + + /* Get/attach invite session's transaction data */ + tsx_inv_data = (struct tsx_inv_data*) + inv->invite_tsx->mod_data[mod_inv.mod.id]; + if (tsx_inv_data == NULL) { + tsx_inv_data = PJ_POOL_ZALLOC_T(inv->invite_tsx->pool, + struct tsx_inv_data); + tsx_inv_data->inv = inv; + inv->invite_tsx->mod_data[mod_inv.mod.id] = tsx_inv_data; + } + + tsx_inv_data->sdp_done = PJ_TRUE; + } +} + + +/* * State NULL is before anything is sent/received. */ static void inv_on_state_null( pjsip_inv_session *inv, pjsip_event *e) @@ -2072,6 +2414,11 @@ static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e) inv_check_sdp_in_incoming_msg(inv, tsx, e->body.tsx_state.src.rdata); + if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) { + inv_handle_incoming_reliable_response( + inv, e->body.tsx_state.src.rdata); + } + } else { /* Ignore 100 (Trying) response, as it doesn't change * session state. It only ceases retransmissions. @@ -2166,12 +2513,9 @@ static void inv_on_state_calling( pjsip_inv_session *inv, pjsip_event *e) break; } - } else if (inv->role == PJSIP_ROLE_UAC && - tsx->role == PJSIP_ROLE_UAC && - tsx->method.id == PJSIP_CANCEL_METHOD) - { + } else if (tsx->role == PJSIP_ROLE_UAC) { /* - * Handle case when outgoing CANCEL is answered with 481 (Call/ + * Handle case when outgoing request is answered with 481 (Call/ * Transaction Does Not Exist), 408, or when it's timed out. In these * cases, disconnect session (i.e. dialog usage only). */ @@ -2284,6 +2628,11 @@ static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e) if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) { inv_check_sdp_in_incoming_msg(inv, tsx, e->body.tsx_state.src.rdata); + + if (pjsip_100rel_is_reliable(e->body.tsx_state.src.rdata)) { + inv_handle_incoming_reliable_response( + inv, e->body.tsx_state.src.rdata); + } } break; @@ -2353,12 +2702,38 @@ static void inv_on_state_early( pjsip_inv_session *inv, pjsip_event *e) inv_respond_incoming_cancel(inv, tsx, e->body.tsx_state.src.rdata); - } else if (inv->role == PJSIP_ROLE_UAC && - tsx->role == PJSIP_ROLE_UAC && - tsx->method.id == PJSIP_CANCEL_METHOD) + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle incoming UPDATE + */ + inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata); + + + } else if (tsx->role == PJSIP_ROLE_UAC && + (tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_TERMINATED) && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) { /* - * Handle case when outgoing CANCEL is answered with 481 (Call/ + * Handle response to outgoing UPDATE request. + */ + inv_handle_update_response(inv, e); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0) + { + /* + * Handle incoming PRACK + */ + inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAC) { + /* + * Handle case when outgoing request is answered with 481 (Call/ * Transaction Does Not Exist), 408, or when it's timed out. In these * cases, disconnect session (i.e. dialog usage only). */ @@ -2466,7 +2841,51 @@ static void inv_on_state_connecting( pjsip_inv_session *inv, pjsip_event *e) status = pjsip_dlg_send_response(dlg, tsx, tdata); if (status != PJ_SUCCESS) return; + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle incoming UPDATE + */ + inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata); + + + } else if (tsx->role == PJSIP_ROLE_UAC && + (tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_TERMINATED) && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle response to outgoing UPDATE request. + */ + inv_handle_update_response(inv, e); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0) + { + /* + * Handle incoming PRACK + */ + inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAC) { + /* + * Handle case when outgoing request is answered with 481 (Call/ + * Transaction Does Not Exist), 408, or when it's timed out. In these + * cases, disconnect session (i.e. dialog usage only). + */ + if (tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST || + tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR) + { + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } } + } /* @@ -2730,7 +3149,51 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) pjmedia_sdp_neg_cancel_offer(inv->neg); } } + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle incoming UPDATE + */ + inv_respond_incoming_update(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAC && + (tsx->state == PJSIP_TSX_STATE_COMPLETED || + tsx->state == PJSIP_TSX_STATE_TERMINATED) && + pjsip_method_cmp(&tsx->method, &pjsip_update_method)==0) + { + /* + * Handle response to outgoing UPDATE request. + */ + inv_handle_update_response(inv, e); + + } else if (tsx->role == PJSIP_ROLE_UAS && + tsx->state == PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_prack_method)==0) + { + /* + * Handle strandled incoming PRACK + */ + inv_respond_incoming_prack(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->role == PJSIP_ROLE_UAC) { + /* + * Handle case when outgoing request is answered with 481 (Call/ + * Transaction Does Not Exist), 408, or when it's timed out. In these + * cases, disconnect session (i.e. dialog usage only). + */ + if (tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST || + tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TIMEOUT || + tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR) + { + inv_set_cause(inv, tsx->status_code, &tsx->status_text); + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } } + } /* diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c index 99741a4e..85212bb9 100644 --- a/pjsip/src/pjsip/sip_dialog.c +++ b/pjsip/src/pjsip/sip_dialog.c @@ -1200,8 +1200,8 @@ static void dlg_beautify_response(pjsip_dialog *dlg, } } - /* Add Allow header in 2xx and 405 response. */ - if (((st_class==2 && dlg->add_allow) + /* 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) { diff --git a/pjsip/src/pjsip/sip_transaction.c b/pjsip/src/pjsip/sip_transaction.c index e721044c..dbbccf60 100644 --- a/pjsip/src/pjsip/sip_transaction.c +++ b/pjsip/src/pjsip/sip_transaction.c @@ -904,6 +904,7 @@ static pj_status_t tsx_create( pjsip_module *tsx_user, 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; diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 22a11053..ac5db1dd 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -195,6 +195,8 @@ PJ_DEF(pj_status_t) pjsua_enum_calls( pjsua_call_id ids[], } +#define LATE_SDP 0 + /* * Make outgoing call to the specified URI using the specified account. */ @@ -312,11 +314,15 @@ PJ_DEF(pj_status_t) pjsua_call_make_call( pjsua_acc_id acc_id, } /* Create SDP offer */ +#if LATE_SDP + offer = NULL; +#else status = pjsua_media_channel_create_sdp(call->index, dlg->pool, &offer); if (status != PJ_SUCCESS) { pjsua_perror(THIS_FILE, "pjmedia unable to create SDP", status); goto on_error; } +#endif /* Create the INVITE session: */ #if PJSIP_HAS_100REL diff --git a/pjsip/src/test-pjsip/inv_offer_answer_test.c b/pjsip/src/test-pjsip/inv_offer_answer_test.c new file mode 100644 index 00000000..9da8d250 --- /dev/null +++ b/pjsip/src/test-pjsip/inv_offer_answer_test.c @@ -0,0 +1,676 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "test.h" +#include <pjsip_ua.h> +#include <pjsip.h> +#include <pjlib.h> + +#define THIS_FILE "inv_offer_answer_test.c" +#define PORT 5068 +#define CONTACT "sip:127.0.0.1:5068" +#define TRACE_(x) PJ_LOG(3,x) + +static struct oa_sdp_t +{ + const char *offer; + const char *answer; + unsigned pt_result; +} oa_sdp[] = +{ + { + /* Offer: */ + "v=0\r\n" + "o=alice 1 1 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 1 1 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 0 RTP/AVP 31\r\n", + + 0 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 2 2 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 8\r\n" + "a=rtpmap:0 PCMA/8000\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 2 2 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 8\r\n" + "a=rtpmap:0 PCMA/8000\r\n", + + 8 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 3 3 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 3\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 3 3 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 3\r\n", + + 3 + }, + + { + /* Offer: */ + "v=0\r\n" + "o=alice 4 4 IN IP4 host.anywhere.com\r\n" + "s= \r\n" + "c=IN IP4 host.anywhere.com\r\n" + "t=0 0\r\n" + "m=audio 49170 RTP/AVP 4\r\n", + + /* Answer: */ + "v=0\r\n" + "o=bob 4 4 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 4\r\n", + + 4 + } +}; + + + +typedef enum oa_t +{ + OFFERER_NONE, + OFFERER_UAC, + OFFERER_UAS +} oa_t; + +typedef struct inv_test_param_t +{ + char *title; + unsigned inv_option; + pj_bool_t need_established; + unsigned count; + oa_t oa[4]; +} inv_test_param_t; + +typedef struct inv_test_t +{ + inv_test_param_t param; + pjsip_inv_session *uac; + pjsip_inv_session *uas; + + pj_bool_t complete; + pj_bool_t uas_complete, + uac_complete; + + unsigned oa_index; + unsigned uac_update_cnt, + uas_update_cnt; +} inv_test_t; + + +/**************** GLOBALS ******************/ +static inv_test_t inv_test; +static unsigned job_cnt; + +typedef enum job_type +{ + SEND_OFFER, + ESTABLISH_CALL +} job_type; + +typedef struct job_t +{ + job_type type; + pjsip_role_e who; +} job_t; + +static job_t jobs[128]; + + +/**************** UTILS ******************/ +static pjmedia_sdp_session *create_sdp(pj_pool_t *pool, const char *body) +{ + pjmedia_sdp_session *sdp; + pj_str_t dup; + pj_status_t status; + + pj_strdup2_with_null(pool, &dup, body); + status = pjmedia_sdp_parse(pool, dup.ptr, dup.slen, &sdp); + pj_assert(status == PJ_SUCCESS); + + return sdp; +} + +/**************** INVITE SESSION CALLBACKS ******************/ +static void on_rx_offer(pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) +{ + pjmedia_sdp_session *sdp; + + sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].answer); + pjsip_inv_set_sdp_answer(inv, sdp); + + if (inv_test.oa_index == inv_test.param.count-1 && + inv_test.param.need_established) + { + jobs[job_cnt].type = ESTABLISH_CALL; + jobs[job_cnt].who = PJSIP_ROLE_UAS; + job_cnt++; + } +} + + +static void on_create_offer(pjsip_inv_session *inv, + pjmedia_sdp_session **p_offer) +{ + pj_assert(!"Should not happen"); +} + +static void on_media_update(pjsip_inv_session *inv_ses, + pj_status_t status) +{ + if (inv_ses == inv_test.uas) { + inv_test.uas_update_cnt++; + pj_assert(inv_test.uas_update_cnt - inv_test.uac_update_cnt <= 1); + TRACE_((THIS_FILE, " Callee media is established")); + } else if (inv_ses == inv_test.uac) { + inv_test.uac_update_cnt++; + pj_assert(inv_test.uac_update_cnt - inv_test.uas_update_cnt <= 1); + TRACE_((THIS_FILE, " Caller media is established")); + + } else { + pj_assert(!"Unknown session!"); + } + + if (inv_test.uac_update_cnt == inv_test.uas_update_cnt) { + inv_test.oa_index++; + + if (inv_test.oa_index < inv_test.param.count) { + switch (inv_test.param.oa[inv_test.oa_index]) { + case OFFERER_UAC: + jobs[job_cnt].type = SEND_OFFER; + jobs[job_cnt].who = PJSIP_ROLE_UAC; + job_cnt++; + break; + case OFFERER_UAS: + jobs[job_cnt].type = SEND_OFFER; + jobs[job_cnt].who = PJSIP_ROLE_UAS; + job_cnt++; + break; + default: + pj_assert(!"Invalid oa"); + } + } + + pj_assert(job_cnt <= PJ_ARRAY_SIZE(jobs)); + } +} + +static void on_state_changed(pjsip_inv_session *inv, pjsip_event *e) +{ + const char *who = NULL; + + if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + TRACE_((THIS_FILE, " %s call disconnected", + (inv==inv_test.uas ? "Callee" : "Caller"))); + return; + } + + if (inv->state != PJSIP_INV_STATE_CONFIRMED) + return; + + if (inv == inv_test.uas) { + inv_test.uas_complete = PJ_TRUE; + who = "Callee"; + } else if (inv == inv_test.uac) { + inv_test.uac_complete = PJ_TRUE; + who = "Caller"; + } else + pj_assert(!"No session"); + + TRACE_((THIS_FILE, " %s call is confirmed", who)); + + if (inv_test.uac_complete && inv_test.uas_complete) + inv_test.complete = PJ_TRUE; +} + + +/**************** MODULE TO RECEIVE INITIAL INVITE ******************/ + +static pj_bool_t on_rx_request(pjsip_rx_data *rdata) +{ + if (rdata->msg_info.msg->type == PJSIP_REQUEST_MSG && + rdata->msg_info.msg->line.req.method.id == PJSIP_INVITE_METHOD) + { + pjsip_dialog *dlg; + pjmedia_sdp_session *sdp; + pj_str_t uri; + pjsip_tx_data *tdata; + pj_status_t status; + + /* + * Create UAS + */ + uri = pj_str(CONTACT); + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, + &uri, &dlg); + pj_assert(status == PJ_SUCCESS); + + if (inv_test.param.oa[0] == OFFERER_UAC) + sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].answer); + else if (inv_test.param.oa[0] == OFFERER_UAS) + sdp = create_sdp(rdata->tp_info.pool, oa_sdp[0].offer); + else + pj_assert(!"Invalid offerer type"); + + status = pjsip_inv_create_uas(dlg, rdata, sdp, inv_test.param.inv_option, &inv_test.uas); + pj_assert(status == PJ_SUCCESS); + + TRACE_((THIS_FILE, " Sending 183 with SDP")); + + /* + * Answer with 183 + */ + status = pjsip_inv_initial_answer(inv_test.uas, rdata, 183, NULL, + NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv_test.uas, tdata); + pj_assert(status == PJ_SUCCESS); + + return PJ_TRUE; + } + + return PJ_FALSE; +} + +static pjsip_module mod_inv_oa_test = +{ + NULL, NULL, /* prev, next. */ + { "mod-inv-oa-test", 15 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION, /* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &on_rx_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + +/**************** THE TEST ******************/ +static void run_job(job_t *j) +{ + pjsip_inv_session *inv; + pjsip_tx_data *tdata; + pjmedia_sdp_session *sdp; + pj_status_t status; + + if (j->who == PJSIP_ROLE_UAC) + inv = inv_test.uac; + else + inv = inv_test.uas; + + switch (j->type) { + case SEND_OFFER: + sdp = create_sdp(inv->dlg->pool, oa_sdp[inv_test.oa_index].offer); + + TRACE_((THIS_FILE, " Sending UPDATE with offer")); + status = pjsip_inv_update(inv, NULL, sdp, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv, tdata); + pj_assert(status == PJ_SUCCESS); + break; + case ESTABLISH_CALL: + TRACE_((THIS_FILE, " Sending 200/OK")); + status = pjsip_inv_answer(inv, 200, NULL, NULL, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv, tdata); + pj_assert(status == PJ_SUCCESS); + break; + } +} + + +static int perform_test(inv_test_param_t *param) +{ + pj_str_t uri; + pjsip_dialog *dlg; + pjmedia_sdp_session *sdp; + pjsip_tx_data *tdata; + pj_status_t status; + + PJ_LOG(3,(THIS_FILE, " %s", param->title)); + + pj_bzero(&inv_test, sizeof(inv_test)); + pj_memcpy(&inv_test.param, param, sizeof(*param)); + job_cnt = 0; + + uri = pj_str(CONTACT); + + /* + * Create UAC + */ + status = pjsip_dlg_create_uac(pjsip_ua_instance(), + &uri, &uri, &uri, &uri, &dlg); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -10); + + if (inv_test.param.oa[0] == OFFERER_UAC) + sdp = create_sdp(dlg->pool, oa_sdp[0].offer); + else + sdp = NULL; + + status = pjsip_inv_create_uac(dlg, sdp, inv_test.param.inv_option, &inv_test.uac); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -20); + + TRACE_((THIS_FILE, " Sending INVITE %s offer", (sdp ? "with" : "without"))); + + /* + * Make call! + */ + status = pjsip_inv_invite(inv_test.uac, &tdata); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30); + + status = pjsip_inv_send_msg(inv_test.uac, tdata); + PJ_ASSERT_RETURN(status==PJ_SUCCESS, -30); + + /* + * Wait until test completes + */ + while (!inv_test.complete) { + pj_time_val delay = {0, 20}; + + pjsip_endpt_handle_events(endpt, &delay); + + while (job_cnt) { + job_t j; + + j = jobs[0]; + pj_array_erase(jobs, sizeof(jobs[0]), job_cnt, 0); + --job_cnt; + + run_job(&j); + } + } + + flush_events(100); + + /* + * Hangup + */ + TRACE_((THIS_FILE, " Disconnecting call")); + status = pjsip_inv_end_session(inv_test.uas, PJSIP_SC_DECLINE, 0, &tdata); + pj_assert(status == PJ_SUCCESS); + + status = pjsip_inv_send_msg(inv_test.uas, tdata); + pj_assert(status == PJ_SUCCESS); + + flush_events(500); + + return 0; +} + + +static pj_bool_t log_on_rx_msg(pjsip_rx_data *rdata) +{ + pjsip_msg *msg = rdata->msg_info.msg; + char info[80]; + + if (msg->type == PJSIP_REQUEST_MSG) + pj_ansi_snprintf(info, sizeof(info), "%.*s", + (int)msg->line.req.method.name.slen, + msg->line.req.method.name.ptr); + else + pj_ansi_snprintf(info, sizeof(info), "%d/%.*s", + msg->line.status.code, + (int)rdata->msg_info.cseq->method.name.slen, + rdata->msg_info.cseq->method.name.ptr); + + TRACE_((THIS_FILE, " Received %s %s sdp", info, + (msg->body ? "with" : "without"))); + + return PJ_FALSE; +} + + +/* Message logger module. */ +static pjsip_module mod_msg_logger = +{ + NULL, NULL, /* prev and next */ + { "mod-msg-loggee", 14}, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_TRANSPORT_LAYER-1,/* Priority */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &log_on_rx_msg, /* on_rx_request() */ + &log_on_rx_msg, /* on_rx_response() */ + NULL, /* on_tx_request() */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + +static inv_test_param_t test_params[] = +{ +/* Normal scenario: + + UAC UAS + INVITE (offer) --> + 200/INVITE (answer) <-- + ACK --> + */ +#if 0 + { + "Standard INVITE with offer", + 0, + PJ_TRUE, + 1, + { OFFERER_UAC } + }, + + { + "Standard INVITE with offer, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 1, + { OFFERER_UAC } + }, +#endif + +/* Delayed offer: + UAC UAS + INVITE (no SDP) --> + 200/INVITE (offer) <-- + ACK (answer) --> + */ +#if 1 + { + "INVITE with no offer", + 0, + PJ_TRUE, + 1, + { OFFERER_UAS } + }, + + { + "INVITE with no offer, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 1, + { OFFERER_UAS } + }, +#endif + +/* Subsequent UAC offer with UPDATE: + + UAC UAS + INVITE (offer) --> + 180/rel (answer) <-- + UPDATE (offer) --> inv_update() on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) <-- + 200/INVITE <-- + ACK --> +*/ +#if 1 + { + "INVITE and UPDATE by UAC", + 0, + PJ_TRUE, + 2, + { OFFERER_UAC, OFFERER_UAC } + }, + { + "INVITE and UPDATE by UAC, with 100rel", + PJSIP_INV_REQUIRE_100REL, + PJ_TRUE, + 2, + { OFFERER_UAC, OFFERER_UAC } + }, +#endif + +/* Subsequent UAS offer with UPDATE: + + INVITE (offer --> + 180/rel (answer) <-- + UPDATE (offer) <-- inv_update() + on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) --> + UPDATE (offer) --> on_rx_offer() + set_sdp_answer() + 200/UPDATE (answer) <-- + 200/INVITE <-- + ACK --> + + */ + { + "INVITE and many UPDATE by UAC and UAS", + 0, + PJ_TRUE, + 4, + { OFFERER_UAC, OFFERER_UAS, OFFERER_UAC, OFFERER_UAS } + }, + +}; + + +static pjsip_dialog* on_dlg_forked(pjsip_dialog *first_set, pjsip_rx_data *res) +{ + return NULL; +} + + +static void on_new_session(pjsip_inv_session *inv, pjsip_event *e) +{ +} + + +int inv_offer_answer_test(void) +{ + unsigned i; + int rc = 0; + + /* Init UA layer */ + if (pjsip_ua_instance()->id == -1) { + pjsip_ua_init_param ua_param; + pj_bzero(&ua_param, sizeof(ua_param)); + ua_param.on_dlg_forked = &on_dlg_forked; + pjsip_ua_init_module(endpt, &ua_param); + } + + /* Init inv-usage */ + if (pjsip_inv_usage_instance()->id == -1) { + pjsip_inv_callback inv_cb; + pj_bzero(&inv_cb, sizeof(inv_cb)); + inv_cb.on_media_update = &on_media_update; + inv_cb.on_rx_offer = &on_rx_offer; + inv_cb.on_create_offer = &on_create_offer; + inv_cb.on_state_changed = &on_state_changed; + inv_cb.on_new_session = &on_new_session; + pjsip_inv_usage_init(endpt, &inv_cb); + } + + /* 100rel module */ + pjsip_100rel_init_module(endpt); + + /* Our module */ + pjsip_endpt_register_module(endpt, &mod_inv_oa_test); + pjsip_endpt_register_module(endpt, &mod_msg_logger); + + /* Create SIP UDP transport */ + { + pj_sockaddr_in addr; + pjsip_transport *tp; + pj_status_t status; + + pj_sockaddr_in_init(&addr, NULL, PORT); + status = pjsip_udp_transport_start(endpt, &addr, NULL, 1, &tp); + pj_assert(status == PJ_SUCCESS); + } + + /* Do tests */ + for (i=0; i<PJ_ARRAY_SIZE(test_params); ++i) { + rc = perform_test(&test_params[i]); + if (rc != 0) + goto on_return; + } + + +on_return: + return rc; +} + diff --git a/pjsip/src/test-pjsip/test.c b/pjsip/src/test-pjsip/test.c index 62e3aed3..b02a8cf6 100644 --- a/pjsip/src/test-pjsip/test.c +++ b/pjsip/src/test-pjsip/test.c @@ -355,6 +355,10 @@ int test_main(void) } #endif +#if INCLUDE_INV_OA_TEST + DO_TEST(inv_offer_answer_test()); +#endif + on_return: flush_events(500); diff --git a/pjsip/src/test-pjsip/test.h b/pjsip/src/test-pjsip/test.h index 956e3614..b2566510 100644 --- a/pjsip/src/test-pjsip/test.h +++ b/pjsip/src/test-pjsip/test.h @@ -39,6 +39,7 @@ extern pjsip_endpoint *endpt; #define INCLUDE_MESSAGING_GROUP 1 #define INCLUDE_TRANSPORT_GROUP 1 #define INCLUDE_TSX_GROUP 1 +#define INCLUDE_INV_GROUP 1 /* * Include tests that normally would fail under certain gcc @@ -58,7 +59,7 @@ extern pjsip_endpoint *endpt; #define INCLUDE_TCP_TEST INCLUDE_TRANSPORT_GROUP #define INCLUDE_RESOLVE_TEST INCLUDE_TRANSPORT_GROUP #define INCLUDE_TSX_TEST INCLUDE_TSX_GROUP - +#define INCLUDE_INV_OA_TEST INCLUDE_INV_GROUP /* The tests */ @@ -94,6 +95,9 @@ int transport_rt_test( pjsip_transport_type_e tp_type, char *target_url, int *pkt_lost); +/* Invite session */ +int inv_offer_answer_test(void); + /* Test main entry */ int test_main(void); |