summaryrefslogtreecommitdiff
path: root/pjsip
diff options
context:
space:
mode:
authorBenny Prijono <bennylp@teluu.com>2007-10-03 18:28:49 +0000
committerBenny Prijono <bennylp@teluu.com>2007-10-03 18:28:49 +0000
commit3112361512e913a6ce28a9253a6d45434b975505 (patch)
tree914e356e5b5e2cf05c17c1177589064773638d20 /pjsip
parente2f5c2d529ae091a65c73f86ab60f4fc251645dc (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/Makefile3
-rw-r--r--pjsip/build/test_pjsip.dsp4
-rw-r--r--pjsip/build/test_pjsip.vcproj8
-rw-r--r--pjsip/include/pjsip-ua/sip_100rel.h86
-rw-r--r--pjsip/include/pjsip-ua/sip_inv.h15
-rw-r--r--pjsip/include/pjsip/sip_config.h11
-rw-r--r--pjsip/src/pjsip-ua/sip_100rel.c1244
-rw-r--r--pjsip/src/pjsip-ua/sip_inv.c565
-rw-r--r--pjsip/src/pjsip/sip_dialog.c4
-rw-r--r--pjsip/src/pjsip/sip_transaction.c1
-rw-r--r--pjsip/src/pjsua-lib/pjsua_call.c6
-rw-r--r--pjsip/src/test-pjsip/inv_offer_answer_test.c676
-rw-r--r--pjsip/src/test-pjsip/test.c4
-rw-r--r--pjsip/src/test-pjsip/test.h6
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);