From fcba4d392ea03e8ac4cfde87d8efd7999ff4a38c Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Tue, 21 Feb 2006 23:47:00 +0000 Subject: Implemented major feature: call hold and transfer git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@212 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/src/pjsip-simple/errno.c | 79 ++++ pjsip/src/pjsip-simple/evsub.c | 93 ++++- pjsip/src/pjsip-simple/presence.c | 5 +- pjsip/src/pjsip-ua/sip_inv.c | 396 ++++++++++++++++--- pjsip/src/pjsip-ua/sip_xfer.c | 614 ++++++++++++++++++++++++++++++ pjsip/src/pjsip/sip_dialog.c | 78 +++- pjsip/src/pjsip/sip_endpoint.c | 2 + pjsip/src/pjsip/sip_msg.c | 12 + pjsip/src/pjsua/main.c | 181 ++++++--- pjsip/src/pjsua/pjsua.h | 42 ++- pjsip/src/pjsua/pjsua_core.c | 12 +- pjsip/src/pjsua/pjsua_inv.c | 775 +++++++++++++++++++++++++++++++------- 12 files changed, 2032 insertions(+), 257 deletions(-) create mode 100644 pjsip/src/pjsip-ua/sip_xfer.c (limited to 'pjsip/src') diff --git a/pjsip/src/pjsip-simple/errno.c b/pjsip/src/pjsip-simple/errno.c index 374178e2..e26b5a7b 100644 --- a/pjsip/src/pjsip-simple/errno.c +++ b/pjsip/src/pjsip-simple/errno.c @@ -17,4 +17,83 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include +#include + +/* PJSIP-SIMPLE's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ +static const struct +{ + int code; + const char *msg; +} err_str[] = +{ + { PJSIP_SIMPLE_ENOPKG, "No SIP event package with the specified name" }, + { PJSIP_SIMPLE_EPKGEXISTS, "SIP event package already exist" }, + + { PJSIP_SIMPLE_ENOTSUBSCRIBE, "Expecting SUBSCRIBE request" }, + { PJSIP_SIMPLE_ENOPRESENCE, "No presence associated with the subscription" }, + { PJSIP_SIMPLE_ENOPRESENCEINFO, "No presence info in the server subscription" }, + { PJSIP_SIMPLE_EBADCONTENT, "Bad Content-Type for presence" }, + { PJSIP_SIMPLE_EBADPIDF, "Bad PIDF content for presence" }, + { PJSIP_SIMPLE_EBADXPIDF, "Bad XPIDF content for presence" }, +}; + + + +/* + * pjsipsimple_strerror() + */ +PJ_DEF(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, + char *buf, pj_size_t bufsize ) +{ + pj_str_t errstr; + + if (statcode >= PJSIP_SIMPLE_ERRNO_START && + statcode < PJSIP_SIMPLE_ERRNO_START + PJ_ERRNO_SPACE_SIZE) + { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n/2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid+1; + n -= (half+1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char*)err_str[first].msg; + msg.slen = pj_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + + } + } + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_snprintf(buf, bufsize, + "Unknown error %d", + statcode); + + return errstr; +} diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c index be27db39..8f102af5 100644 --- a/pjsip/src/pjsip-simple/evsub.c +++ b/pjsip/src/pjsip-simple/evsub.c @@ -197,6 +197,7 @@ struct pjsip_evsub pjsip_endpoint *endpt; /**< Endpoint instance. */ pjsip_dialog *dlg; /**< Underlying dialog. */ struct evpkg *pkg; /**< The event package. */ + unsigned option; /**< Options. */ pjsip_evsub_user user; /**< Callback. */ pjsip_role_e role; /**< UAC=subscriber, UAS=notifier */ pjsip_evsub_state state; /**< Subscription state. */ @@ -235,6 +236,7 @@ static const pj_str_t STR_ACTIVE = { "active", 6 }; static const pj_str_t STR_PENDING = { "pending", 7 }; static const pj_str_t STR_TIMEOUT = { "timeout", 7}; + /* * On unload module. */ @@ -246,6 +248,12 @@ static pj_status_t mod_evsub_unload(void) return PJ_SUCCESS; } +/* Proto for pjsipsimple_strerror(). + * Defined in errno.c + */ +PJ_DECL(pj_str_t) pjsipsimple_strerror( pj_status_t statcode, + char *buf, pj_size_t bufsize ); + /* * Init and register module. */ @@ -257,6 +265,9 @@ PJ_DEF(pj_status_t) pjsip_evsub_init_module(pjsip_endpoint *endpt) { "NOTIFY", 6} }; + pj_register_strerror(PJSIP_SIMPLE_ERRNO_START, PJ_ERRNO_SPACE_SIZE, + &pjsipsimple_strerror); + PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL); PJ_ASSERT_RETURN(mod_evsub.mod.id == -1, PJ_EINVALIDOP); @@ -618,6 +629,7 @@ static pj_status_t evsub_create( pjsip_dialog *dlg, pjsip_role_e role, const pjsip_evsub_user *user_cb, const pj_str_t *event, + unsigned option, pjsip_evsub **p_evsub ) { pjsip_evsub *sub; @@ -640,6 +652,7 @@ static pj_status_t evsub_create( pjsip_dialog *dlg, sub->dlg = dlg; sub->pkg = pkg; sub->role = role; + sub->option = option; sub->state = PJSIP_EVSUB_STATE_NULL; sub->state_str = evsub_state_names[sub->state]; sub->expires = pjsip_expires_hdr_create(sub->pool, pkg->pkg_expires); @@ -697,6 +710,7 @@ static pj_status_t evsub_create( pjsip_dialog *dlg, PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg, const pjsip_evsub_user *user_cb, const pj_str_t *event, + unsigned option, pjsip_evsub **p_evsub) { pjsip_evsub *sub; @@ -705,12 +719,16 @@ PJ_DEF(pj_status_t) pjsip_evsub_create_uac( pjsip_dialog *dlg, PJ_ASSERT_RETURN(dlg && event && p_evsub, PJ_EINVAL); pjsip_dlg_inc_lock(dlg); - status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, &sub); + status = evsub_create(dlg, PJSIP_UAC_ROLE, user_cb, event, option, &sub); if (status != PJ_SUCCESS) goto on_return; - /* Add unique Id to Event header */ - pj_create_unique_string(sub->pool, &sub->event->id_param); + /* Add unique Id to Event header, only when PJSIP_EVSUB_NO_EVENT_ID + * is not specified. + */ + if ((option & PJSIP_EVSUB_NO_EVENT_ID) == 0) { + pj_create_unique_string(sub->pool, &sub->event->id_param); + } /* Increment dlg session. */ pjsip_dlg_inc_session(sub->dlg, &mod_evsub.mod); @@ -730,6 +748,7 @@ on_return: PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg, const pjsip_evsub_user *user_cb, pjsip_rx_data *rdata, + unsigned option, pjsip_evsub **p_evsub) { pjsip_evsub *sub; @@ -757,8 +776,9 @@ PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg, /* Package MUST implement on_rx_refresh */ PJ_ASSERT_RETURN(user_cb->on_rx_refresh, PJ_EINVALIDOP); - /* Request MUST have "Event" header: */ - + /* Request MUST have "Event" header. We need the Event header to get + * the package name (don't want to add more arguments in the function). + */ event_hdr = (pjsip_event_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &STR_EVENT, NULL); if (event_hdr == NULL) { @@ -772,7 +792,7 @@ PJ_DEF(pj_status_t) pjsip_evsub_create_uas( pjsip_dialog *dlg, /* Create the session: */ status = evsub_create(dlg, PJSIP_UAS_ROLE, user_cb, - &event_hdr->event_type, &sub); + &event_hdr->event_type, option, &sub); if (status != PJ_SUCCESS) goto on_return; @@ -1147,6 +1167,7 @@ static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx, return NULL; } + switch (event->body.tsx_state.type) { case PJSIP_EVENT_RX_MSG: msg = event->body.tsx_state.src.rdata->msg_info.msg; @@ -1163,7 +1184,11 @@ static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx, } if (!msg) { - pj_assert(!"First transaction event is not TX or RX!"); + //Note: + // this transaction can be other transaction in the dialog. + // The assertion below probably only valid for dialog that + // only has one event subscription usage. + //pj_assert(!"First transaction event is not TX or RX!"); return NULL; } @@ -1187,12 +1212,43 @@ static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx, while (dlgsub != dlgsub_head) { - /* Match event type and Id */ - if (pj_strcmp(&dlgsub->sub->event->id_param, &event_hdr->id_param)==0 && - pj_stricmp(&dlgsub->sub->event->event_type, &event_hdr->event_type)==0) + if (pj_stricmp(&dlgsub->sub->event->event_type, + &event_hdr->event_type)==0) { - break; + /* Event type matched. + * Check if event ID matched too. + */ + if (pj_strcmp(&dlgsub->sub->event->id_param, + &event_hdr->id_param)==0) + { + + break; + + } + /* + * Otherwise if it is an UAC subscription, AND + * PJSIP_EVSUB_NO_EVENT_ID flag is set, AND + * the session's event id is NULL, AND + * the incoming request is NOTIFY with event ID, then + * we consider it as a match, and update the + * session's event id. + */ + else if (dlgsub->sub->role == PJSIP_ROLE_UAC && + (dlgsub->sub->option & PJSIP_EVSUB_NO_EVENT_ID)!=0 && + dlgsub->sub->event->id_param.slen==0 && + !pjsip_method_cmp(&tsx->method, &pjsip_notify_method)) + { + /* Update session's event id. */ + pj_strdup(dlgsub->sub->pool, + &dlgsub->sub->event->id_param, + &event_hdr->id_param); + + break; + } } + + + dlgsub = dlgsub->next; } @@ -1200,6 +1256,8 @@ static pjsip_evsub *on_new_transaction( pjsip_transaction *tsx, /* This could be incoming request to create new subscription */ PJ_LOG(4,(THIS_FILE, "Subscription not found for %.*s, event=%.*s;id=%.*s", + (int)tsx->method.name.slen, + tsx->method.name.ptr, (int)event_hdr->event_type.slen, event_hdr->event_type.ptr, (int)event_hdr->id_param.slen, @@ -1737,7 +1795,18 @@ static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx, /* Can't authenticate. Terminate session (?) */ set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, NULL); } - } + + } + /* + * Terminate event usage if we receive 481, 408, and 7 class + * responses. + */ + if (sub->state != PJSIP_EVSUB_STATE_TERMINATED && + (tsx->status_code==481 || tsx->status_code==408 || + tsx->status_code/100 == 7)) + { + set_state(sub, PJSIP_EVSUB_STATE_TERMINATED, NULL, event); + } } else { diff --git a/pjsip/src/pjsip-simple/presence.c b/pjsip/src/pjsip-simple/presence.c index e87cd451..c47ae1fa 100644 --- a/pjsip/src/pjsip-simple/presence.c +++ b/pjsip/src/pjsip-simple/presence.c @@ -191,13 +191,14 @@ PJ_DEF(pj_status_t) pjsip_pres_create_uac( pjsip_dialog *dlg, pjsip_dlg_inc_lock(dlg); /* Create event subscription */ - status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE, &sub); + status = pjsip_evsub_create_uac( dlg, &pres_user, &STR_PRESENCE, 0, &sub); if (status != PJ_SUCCESS) goto on_return; /* Create presence */ pres = pj_pool_zalloc(dlg->pool, sizeof(pjsip_pres)); pres->dlg = dlg; + pres->sub = sub; if (user_cb) pj_memcpy(&pres->user_cb, user_cb, sizeof(pjsip_evsub_user)); @@ -297,7 +298,7 @@ PJ_DEF(pj_status_t) pjsip_pres_create_uas( pjsip_dialog *dlg, /* Create server subscription */ - status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, &sub); + status = pjsip_evsub_create_uas( dlg, &pres_user, rdata, 0, &sub); if (status != PJ_SUCCESS) goto on_return; diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c index 63250e3f..a41f7f6e 100644 --- a/pjsip/src/pjsip-ua/sip_inv.c +++ b/pjsip/src/pjsip-ua/sip_inv.c @@ -200,10 +200,7 @@ static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata) /* On receipt ACK request, when state is CONNECTING, * move state to CONFIRMED. */ - if (method->id == PJSIP_ACK_METHOD && inv && - inv->state != PJSIP_INV_STATE_CONFIRMED) - { - pjsip_event event; + if (method->id == PJSIP_ACK_METHOD && inv) { /* Terminate INVITE transaction, if it's still present. */ if (inv->invite_tsx && @@ -214,8 +211,12 @@ static pj_bool_t mod_inv_on_rx_request(pjsip_rx_data *rdata) inv->invite_tsx = NULL; } - PJSIP_EVENT_INIT_RX_MSG(event, rdata); - inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event); + if (inv->state != PJSIP_INV_STATE_CONFIRMED) { + pjsip_event event; + + PJSIP_EVENT_INIT_RX_MSG(event, rdata); + inv_set_state(inv, PJSIP_INV_STATE_CONFIRMED, &event); + } } return PJ_FALSE; @@ -250,7 +251,9 @@ static pj_bool_t mod_inv_on_rx_response(pjsip_rx_data *rdata) * If it is, we need to send ACK. */ if (msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/100==2 && - rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD) { + rdata->msg_info.cseq->method.id == PJSIP_INVITE_METHOD && + inv->invite_tsx == NULL) + { inv_send_ack(inv, rdata); return PJ_TRUE; @@ -865,13 +868,16 @@ PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv, { pjsip_tx_data *tdata; const pjsip_hdr *hdr; + pj_bool_t has_sdp; pj_status_t status; /* Verify arguments. */ PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); - /* State MUST be NULL. */ - PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL, PJ_EINVAL); + /* State MUST be NULL or CONFIRMED. */ + PJ_ASSERT_RETURN(inv->state == PJSIP_INV_STATE_NULL || + inv->state == PJSIP_INV_STATE_CONFIRMED, + PJ_EINVALIDOP); /* Create the INVITE request. */ status = pjsip_dlg_create_request(inv->dlg, &pjsip_invite_method, -1, @@ -879,10 +885,37 @@ PJ_DEF(pj_status_t) pjsip_inv_invite( pjsip_inv_session *inv, if (status != PJ_SUCCESS) return status; + /* If this is the first INVITE, then copy the headers from inv_hdr. + * These are the headers parsed from the request URI when the + * dialog was created. + */ + if (inv->state == PJSIP_INV_STATE_NULL) { + hdr = inv->dlg->inv_hdr.next; + + while (hdr != &inv->dlg->inv_hdr) { + pjsip_msg_add_hdr(tdata->msg, + pjsip_hdr_shallow_clone(tdata->pool, hdr)); + hdr = hdr->next; + } + } + + /* See if we have SDP to send. */ + if (inv->neg) { + pjmedia_sdp_neg_state neg_state; + + neg_state = pjmedia_sdp_neg_get_state(inv->neg); + + has_sdp = (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER || + (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO && + pjmedia_sdp_neg_has_local_answer(inv->neg))); + + + } else { + has_sdp = PJ_FALSE; + } + /* Add SDP, if any. */ - if (inv->neg && - pjmedia_sdp_neg_get_state(inv->neg)==PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) - { + if (has_sdp) { const pjmedia_sdp_session *offer; status = pjmedia_sdp_neg_get_neg_local(inv->neg, &offer); @@ -940,9 +973,9 @@ static pj_status_t inv_negotiate_sdp( pjsip_inv_session *inv ) /* * Check in incoming message for SDP offer/answer. */ -static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, - pjsip_transaction *tsx, - pjsip_rx_data *rdata) +static pj_status_t inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_rx_data *rdata) { struct tsx_inv_data *tsx_inv_data; static const pj_str_t str_application = { "application", 11 }; @@ -963,21 +996,21 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, */ if (tsx_inv_data->sdp_done) - return; + return PJ_SUCCESS; /* Check if SDP is present in the message. */ msg = rdata->msg_info.msg; if (msg->body == NULL) { /* Message doesn't have body. */ - return; + return PJ_SUCCESS; } if (pj_stricmp(&msg->body->content_type.type, &str_application) || pj_stricmp(&msg->body->content_type.subtype, &str_sdp)) { /* Message body is not "application/sdp" */ - return; + return PJMEDIA_SDP_EINSDP; } /* Parse the SDP body. */ @@ -989,7 +1022,7 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, pj_strerror(status, errmsg, sizeof(errmsg)); PJ_LOG(4,(THIS_FILE, "Error parsing SDP in %s: %s", pjsip_rx_data_get_info(rdata), errmsg)); - return; + return PJMEDIA_SDP_EINSDP; } /* The SDP can be an offer or answer, depending on negotiator's state */ @@ -1015,13 +1048,16 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, pj_strerror(status, errmsg, sizeof(errmsg)); PJ_LOG(4,(THIS_FILE, "Error processing SDP offer in %s: %s", pjsip_rx_data_get_info(rdata), errmsg)); - return; + return PJMEDIA_SDP_EINSDP; } /* Inform application about remote offer. */ - if (mod_inv.cb.on_rx_offer) - (*mod_inv.cb.on_rx_offer)(inv); + if (mod_inv.cb.on_rx_offer) { + + (*mod_inv.cb.on_rx_offer)(inv, sdp); + + } } else if (pjmedia_sdp_neg_get_state(inv->neg) == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) @@ -1041,7 +1077,7 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, pj_strerror(status, errmsg, sizeof(errmsg)); PJ_LOG(4,(THIS_FILE, "Error processing SDP answer in %s: %s", pjsip_rx_data_get_info(rdata), errmsg)); - return; + return PJMEDIA_SDP_EINSDP; } /* Negotiate SDP */ @@ -1059,12 +1095,68 @@ static void inv_check_sdp_in_incoming_msg( pjsip_inv_session *inv, pjmedia_sdp_neg_state_str(pjmedia_sdp_neg_get_state(inv->neg)))); } + return PJ_SUCCESS; } +/* + * Process INVITE answer, for both initial and subsequent re-INVITE + */ +static pj_status_t process_answer( pjsip_inv_session *inv, + int st_code, + pjsip_tx_data *tdata ) +{ + pj_status_t status; + pjmedia_sdp_session *sdp = NULL; + + /* Include SDP for 18x and 2xx response. + * Also if SDP negotiator is ready, start negotiation. + */ + if (st_code/10 == 18 || st_code/10 == 20) { + + pjmedia_sdp_neg_state neg_state; + + neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) : + PJMEDIA_SDP_NEG_STATE_NULL; + + if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) { + + status = pjmedia_sdp_neg_get_neg_local(inv->neg, &sdp); + + } else if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO && + pjmedia_sdp_neg_has_local_answer(inv->neg) ) + { + + status = inv_negotiate_sdp(inv); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_sdp_neg_get_active_local(inv->neg, &sdp); + } + + } + + + + /* Include SDP when it's available. + * Subsequent response will include this SDP. + */ + if (sdp) { + tdata->msg->body = create_sdp_body(tdata->pool, sdp); + } + + /* Remove message body if this is a non-2xx final response */ + if (st_code >= 300) + tdata->msg->body = NULL; + + + return PJ_SUCCESS; +} + /* - * Answer initial INVITE. + * Answer initial INVITE + * Re-INVITE will be answered automatically, and will not use this function. */ PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv, int st_code, @@ -1087,7 +1179,7 @@ PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv, /* If local_sdp is specified, then we MUST NOT have answered the * offer before. */ - if (local_sdp) { + if (local_sdp && (st_code/100==1 || st_code/100==2)) { if (inv->neg == NULL) { status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, local_sdp, @@ -1108,47 +1200,43 @@ PJ_DEF(pj_status_t) pjsip_inv_answer( pjsip_inv_session *inv, return status; } - last_res = inv->invite_tsx->last_tx; + + /* Modify last response. */ + last_res = inv->invite_tsx->last_tx; status = pjsip_dlg_modify_response(inv->dlg, last_res, st_code, st_text); if (status != PJ_SUCCESS) return status; - /* Include SDP for 18x and 2xx response. - * Also if SDP negotiator is ready, start negotiation. - */ - if (st_code/10 == 18 || st_code/10 == 20) { - pjmedia_sdp_neg_state neg_state; + /* Process SDP in answer */ + status = process_answer(inv, st_code, last_res); + if (status != PJ_SUCCESS) + return status; - neg_state = inv->neg ? pjmedia_sdp_neg_get_state(inv->neg) : - PJMEDIA_SDP_NEG_STATE_NULL; - if (neg_state == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER || - neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) - { - const pjmedia_sdp_session *local; + *p_tdata = last_res; - status = pjmedia_sdp_neg_get_neg_local(inv->neg, &local); - if (status == PJ_SUCCESS) - last_res->msg->body = create_sdp_body(last_res->pool, local); - } + return PJ_SUCCESS; +} - /* Start negotiation, if ready. */ - if (neg_state == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) { - status = inv_negotiate_sdp(inv); - if (status != PJ_SUCCESS) { - pjsip_tx_data_dec_ref(last_res); - return status; - } - } - } +/* + * Set SDP answer. + */ +PJ_DEF(pj_status_t) pjsip_inv_set_sdp_answer( pjsip_inv_session *inv, + const pjmedia_sdp_session *sdp ) +{ + pj_status_t status; - *p_tdata = last_res; + PJ_ASSERT_RETURN(inv && sdp, PJ_EINVAL); - return PJ_SUCCESS; + pjsip_dlg_inc_lock(inv->dlg); + status = pjmedia_sdp_neg_set_local_answer( inv->pool, inv->neg, sdp); + pjsip_dlg_dec_lock(inv->dlg); + + return status; } @@ -1205,8 +1293,9 @@ PJ_DEF(pj_status_t) pjsip_inv_end_session( pjsip_inv_session *inv, tdata = inv->invite_tsx->last_tx; PJ_ASSERT_RETURN(tdata != NULL, PJ_EINVALIDOP); - status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code, - st_text); + //status = pjsip_dlg_modify_response(inv->dlg, tdata, st_code, + // st_text); + status = pjsip_inv_answer(inv, st_code, st_text, NULL, &tdata); } break; @@ -1247,13 +1336,82 @@ PJ_DEF(pj_status_t) pjsip_inv_reinvite( pjsip_inv_session *inv, const pjmedia_sdp_session *new_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); + pj_status_t status; + pjsip_contact_hdr *contact_hdr = NULL; - PJ_TODO(CREATE_REINVITE_REQUEST); - return PJ_ENOTSUP; + /* Check arguments. */ + PJ_ASSERT_RETURN(inv && p_tdata, PJ_EINVAL); + + /* Must NOT have a pending INVITE transaction */ + PJ_ASSERT_RETURN(inv->invite_tsx==NULL, PJ_EINVALIDOP); + + + pjsip_dlg_inc_lock(inv->dlg); + + 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_parse_hdr(inv->dlg->pool, &STR_CONTACT, + tmp.ptr, tmp.slen, NULL); + if (!contact_hdr) { + status = PJSIP_EINVALIDURI; + goto on_return; + } + } + + + if (new_offer) { + if (!inv->neg) { + status = pjmedia_sdp_neg_create_w_local_offer(inv->pool, new_offer, + &inv->neg); + if (status != PJ_SUCCESS) + goto on_return; + + } else switch (pjmedia_sdp_neg_get_state(inv->neg)) { + + case PJMEDIA_SDP_NEG_STATE_NULL: + pj_assert(!"Unexpected SDP neg state NULL"); + status = PJ_EBUG; + goto on_return; + + case PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER: + PJ_LOG(4,(inv->obj_name, + "pjsip_inv_reinvite: already have an offer, new " + "offer is ignored")); + break; + + case PJMEDIA_SDP_NEG_STATE_REMOTE_OFFER: + status = pjmedia_sdp_neg_set_local_answer(inv->pool, inv->neg, + new_offer); + if (status != PJ_SUCCESS) + goto on_return; + break; + + case PJMEDIA_SDP_NEG_STATE_WAIT_NEGO: + PJ_LOG(4,(inv->obj_name, + "pjsip_inv_reinvite: SDP in WAIT_NEGO state, new " + "offer is ignored")); + break; + + case PJMEDIA_SDP_NEG_STATE_DONE: + status = pjmedia_sdp_neg_modify_local_offer(inv->pool,inv->neg, + new_offer); + if (status != PJ_SUCCESS) + goto on_return; + break; + } + } + + if (contact_hdr) + inv->dlg->local.contact = contact_hdr; + + status = pjsip_inv_invite(inv, p_tdata); + +on_return: + pjsip_dlg_dec_lock(inv->dlg); + return status; } /* @@ -1908,6 +2066,124 @@ static void inv_on_state_confirmed( pjsip_inv_session *inv, pjsip_event *e) inv_respond_incoming_bye( inv, tsx, e->body.tsx_state.src.rdata, e ); } + else if (tsx->method.id == PJSIP_INVITE_METHOD && + tsx->role == PJSIP_ROLE_UAS) + { + + /* + * Handle incoming re-INVITE + */ + if (tsx->state == PJSIP_TSX_STATE_TRYING) { + + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Check if we have INVITE pending. */ + if (inv->invite_tsx && inv->invite_tsx!=tsx) { + + /* Can not receive re-INVITE while another one is pending. */ + status = pjsip_dlg_create_response( inv->dlg, rdata, 500, NULL, + &tdata); + if (status != PJ_SUCCESS) + return; + + status = pjsip_dlg_send_response( inv->dlg, tsx, tdata); + + + return; + } + + /* Save the invite transaction. */ + inv->invite_tsx = tsx; + + /* Process SDP in incoming message. */ + status = inv_check_sdp_in_incoming_msg(inv, tsx, rdata); + + if (status != PJ_SUCCESS) { + + /* Not Acceptable */ + const pjsip_hdr *accept; + + status = pjsip_dlg_create_response(inv->dlg, rdata, + 488, NULL, &tdata); + if (status != PJ_SUCCESS) + return; + + + accept = pjsip_endpt_get_capability(dlg->endpt, PJSIP_H_ACCEPT, + NULL); + if (accept) { + pjsip_msg_add_hdr(tdata->msg, + pjsip_hdr_clone(tdata->pool, accept)); + } + + status = pjsip_dlg_send_response(dlg, tsx, tdata); + + return; + } + + /* Create 2xx ANSWER */ + status = pjsip_dlg_create_response(dlg, rdata, 200, NULL, &tdata); + if (status != PJ_SUCCESS) + return; + + /* Process SDP in the answer */ + status = process_answer(inv, 200, tdata); + if (status != PJ_SUCCESS) + return; + + status = pjsip_inv_send_msg(inv, tdata, NULL); + + } + + } + else if (tsx->method.id == PJSIP_INVITE_METHOD && + tsx->role == PJSIP_ROLE_UAC) + { + /* + * Handle outgoing re-INVITE + */ + if (tsx->state == PJSIP_TSX_STATE_TERMINATED && + tsx->status_code/100 == 2) + { + + /* Re-INVITE was accepted. */ + + /* Process SDP */ + inv_check_sdp_in_incoming_msg(inv, tsx, + e->body.tsx_state.src.rdata); + + /* Send ACK */ + inv_send_ack(inv, e->body.tsx_state.src.rdata); + + } else if (tsx->state == PJSIP_TSX_STATE_COMPLETED && + (tsx->status_code==401 || tsx->status_code==407)) + { + pjsip_tx_data *tdata; + pj_status_t status; + + /* Handle authentication challenge. */ + status = pjsip_auth_clt_reinit_req( &dlg->auth_sess, + e->body.tsx_state.src.rdata, + tsx->last_tx, + &tdata); + if (status != PJ_SUCCESS) + return; + + /* Send re-INVITE */ + status = pjsip_inv_send_msg( inv, tdata, NULL); + + } else if (tsx->status_code==PJSIP_SC_CALL_TSX_DOES_NOT_EXIST || + tsx->status_code==PJSIP_SC_REQUEST_TIMEOUT || + tsx->status_code >= 700) + { + /* + * Handle responses that terminates dialog. + */ + inv_set_state(inv, PJSIP_INV_STATE_DISCONNECTED, e); + } + } } /* diff --git a/pjsip/src/pjsip-ua/sip_xfer.c b/pjsip/src/pjsip-ua/sip_xfer.c new file mode 100644 index 00000000..aadcd3f2 --- /dev/null +++ b/pjsip/src/pjsip-ua/sip_xfer.c @@ -0,0 +1,614 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2006 Benny Prijono + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * Refer module (mod-refer) + */ +static struct pjsip_module mod_xfer = +{ + NULL, NULL, /* prev, next. */ + { "mod-refer", 9 }, /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_APPLICATION-1, /* Priority */ + NULL, /* User data. */ + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + NULL, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ +}; + + +/* Declare PJSIP_REFER_METHOD, so that if somebody declares this in + * sip_msg.h we can catch the error here. + */ +enum +{ + PJSIP_REFER_METHOD = PJSIP_OTHER_METHOD +}; + +const pjsip_method pjsip_refer_method = { + PJSIP_REFER_METHOD, + { "REFER", 5} +}; + + +/* + * String constants + */ +const pj_str_t STR_REFER = { "refer", 5 }; +const pj_str_t STR_MESSAGE = { "message", 7 }; +const pj_str_t STR_SIPFRAG = { "sipfrag", 7 }; +const pj_str_t STR_SIPFRAG_VERSION = {";version=2.0", 12 }; + + +/* + * Transfer struct. + */ +struct pjsip_xfer +{ + pjsip_evsub *sub; /**< Event subscribtion record. */ + pjsip_dialog *dlg; /**< The dialog. */ + pjsip_evsub_user user_cb; /**< The user callback. */ + pj_str_t refer_to_uri; /**< The full Refer-To URI. */ + int last_st_code; /**< st_code sent in last NOTIFY */ + pj_str_t last_st_text; /**< st_text sent in last NOTIFY */ +}; + + +typedef struct pjsip_xfer pjsip_xfer; + + + +/* + * Forward decl for evsub callback. + */ +static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event); +static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event); +static void xfer_on_evsub_rx_refresh( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); +static void xfer_on_evsub_rx_notify( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); +static void xfer_on_evsub_client_refresh(pjsip_evsub *sub); +static void xfer_on_evsub_server_timeout(pjsip_evsub *sub); + + +/* + * Event subscription callback for xference. + */ +static pjsip_evsub_user xfer_user = +{ + &xfer_on_evsub_state, + &xfer_on_evsub_tsx_state, + &xfer_on_evsub_rx_refresh, + &xfer_on_evsub_rx_notify, + &xfer_on_evsub_client_refresh, + &xfer_on_evsub_server_timeout, +}; + + + + +/* + * Initialize the REFER subsystem. + */ +PJ_DEF(pj_status_t) pjsip_xfer_init_module(pjsip_endpoint *endpt) +{ + const pj_str_t accept = { "message/sipfrag;version=2.0", 27 }; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(mod_xfer.id == -1, PJ_EINVALIDOP); + + status = pjsip_endpt_register_module(endpt, &mod_xfer); + if (status != PJ_SUCCESS) + return status; + + status = pjsip_endpt_add_capability( endpt, &mod_xfer, PJSIP_H_ALLOW, + NULL, 1, &pjsip_refer_method.name); + if (status != PJ_SUCCESS) + return status; + + status = pjsip_evsub_register_pkg( &mod_xfer, &STR_REFER, 300, 1, &accept); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + + +/* + * Create transferer (sender of REFER request). + * + */ +PJ_DEF(pj_status_t) pjsip_xfer_create_uac( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_evsub **p_evsub ) +{ + pj_status_t status; + pjsip_xfer *xfer; + pjsip_evsub *sub; + + PJ_ASSERT_RETURN(dlg && p_evsub, PJ_EINVAL); + + pjsip_dlg_inc_lock(dlg); + + /* Create event subscription */ + status = pjsip_evsub_create_uac( dlg, &xfer_user, &STR_REFER, + PJSIP_EVSUB_NO_EVENT_ID, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create xfer session */ + xfer = pj_pool_zalloc(dlg->pool, sizeof(pjsip_xfer)); + xfer->dlg = dlg; + xfer->sub = sub; + if (user_cb) + pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer); + + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; + +} + + + + +/* + * Create transferee (receiver of REFER request). + * + */ +PJ_DEF(pj_status_t) pjsip_xfer_create_uas( pjsip_dialog *dlg, + const pjsip_evsub_user *user_cb, + pjsip_rx_data *rdata, + pjsip_evsub **p_evsub ) +{ + pjsip_evsub *sub; + pjsip_xfer *xfer; + const pj_str_t STR_EVENT = {"Event", 5 }; + pjsip_event_hdr *event_hdr; + pj_status_t status; + + /* Check arguments */ + PJ_ASSERT_RETURN(dlg && rdata && p_evsub, PJ_EINVAL); + + /* Must be request message */ + PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_REQUEST_MSG, + PJSIP_ENOTREQUESTMSG); + + /* Check that request is REFER */ + PJ_ASSERT_RETURN(pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, + &pjsip_refer_method)==0, + PJSIP_ENOTREFER); + + /* Lock dialog */ + pjsip_dlg_inc_lock(dlg); + + /* The evsub framework expects an Event header in the request, + * while a REFER request conveniently doesn't have one (pun intended!). + * So create a dummy Event header. + */ + if (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, + &STR_EVENT, NULL)==NULL) + { + event_hdr = pjsip_event_hdr_create(rdata->tp_info.pool); + event_hdr->event_type = STR_REFER; + pjsip_msg_add_hdr(rdata->msg_info.msg, (pjsip_hdr*)event_hdr); + } + + /* Create server subscription */ + status = pjsip_evsub_create_uas( dlg, &xfer_user, rdata, + PJSIP_EVSUB_NO_EVENT_ID, &sub); + if (status != PJ_SUCCESS) + goto on_return; + + /* Create server xfer subscription */ + xfer = pj_pool_zalloc(dlg->pool, sizeof(pjsip_xfer)); + xfer->dlg = dlg; + xfer->sub = sub; + if (user_cb) + pj_memcpy(&xfer->user_cb, user_cb, sizeof(pjsip_evsub_user)); + + /* Attach to evsub */ + pjsip_evsub_set_mod_data(sub, mod_xfer.id, xfer); + + /* Done: */ + *p_evsub = sub; + +on_return: + pjsip_dlg_dec_lock(dlg); + return status; +} + + + +/* + * Call this function to create request to initiate REFER subscription. + * + */ +PJ_DEF(pj_status_t) pjsip_xfer_initiate( pjsip_evsub *sub, + const pj_str_t *refer_to_uri, + pjsip_tx_data **p_tdata) +{ + pjsip_xfer *xfer; + const pj_str_t refer_to = { "Refer-To", 8}; + pjsip_tx_data *tdata; + pjsip_generic_string_hdr *hdr; + pj_status_t status; + + /* sub and p_tdata argument must be valid. */ + PJ_ASSERT_RETURN(sub && p_tdata, PJ_EINVAL); + + + /* Get the xfer object. */ + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION); + + /* refer_to_uri argument MAY be NULL for subsequent REFER requests, + * but it MUST be specified in the first REFER. + */ + PJ_ASSERT_RETURN((refer_to_uri || xfer->refer_to_uri.slen), PJ_EINVAL); + + /* Lock dialog. */ + pjsip_dlg_inc_lock(xfer->dlg); + + /* Create basic REFER request */ + status = pjsip_evsub_initiate(sub, &pjsip_refer_method, -1, + &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + /* Save Refer-To URI. */ + if (refer_to_uri == NULL) { + refer_to_uri = &xfer->refer_to_uri; + } else { + pj_strdup(xfer->dlg->pool, &xfer->refer_to_uri, refer_to_uri); + } + + /* Create and add Refer-To header. */ + hdr = pjsip_generic_string_hdr_create(tdata->pool, &refer_to, + refer_to_uri); + if (!hdr) { + pjsip_tx_data_dec_ref(tdata); + status = PJ_ENOMEM; + goto on_return; + } + + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hdr); + + + /* Done. */ + *p_tdata = tdata; + + status = PJ_SUCCESS; + +on_return: + pjsip_dlg_dec_lock(xfer->dlg); + return status; +} + + +/* + * Accept the incoming REFER request by sending 2xx response. + * + */ +PJ_DEF(pj_status_t) pjsip_xfer_accept( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int st_code, + const pjsip_hdr *hdr_list ) +{ + /* + * Don't need to add custom headers, so just call basic + * evsub response. + */ + return pjsip_evsub_accept( sub, rdata, st_code, hdr_list ); +} + + +/* + * For notifier, create NOTIFY request to subscriber, and set the state + * of the subscription. + */ +PJ_DEF(pj_status_t) pjsip_xfer_notify( pjsip_evsub *sub, + pjsip_evsub_state state, + int xfer_st_code, + const pj_str_t *xfer_st_text, + pjsip_tx_data **p_tdata) +{ + pjsip_tx_data *tdata; + pjsip_xfer *xfer; + const pj_str_t reason = { "noresource", 10 }; + char *body; + int bodylen; + pjsip_msg_body *msg_body; + pj_status_t status; + + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + /* Get the xfer object. */ + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION); + + + /* Lock object. */ + pjsip_dlg_inc_lock(xfer->dlg); + + /* Create the NOTIFY request. + * Note that reason is only used when state is TERMINATED, and + * the defined termination reason for REFER is "noresource". + */ + status = pjsip_evsub_notify( sub, state, NULL, &reason, &tdata); + if (status != PJ_SUCCESS) + goto on_return; + + + /* Check status text */ + if (xfer_st_text==NULL || xfer_st_text->slen==0) + xfer_st_text = pjsip_get_status_text(xfer_st_code); + + /* Save st_code and st_text, for current_notify() */ + xfer->last_st_code = xfer_st_code; + pj_strdup(xfer->dlg->pool, &xfer->last_st_text, xfer_st_text); + + /* Create sipfrag content. */ + body = pj_pool_alloc(tdata->pool, 128); + bodylen = pj_ansi_snprintf(body, 128, "SIP/2.0 %u %.*s", + xfer_st_code, + (int)xfer_st_text->slen, + xfer_st_text->ptr); + PJ_ASSERT_ON_FAIL(bodylen > 0 && bodylen < 128, + {status=PJ_EBUG; pjsip_tx_data_dec_ref(tdata); + goto on_return; }); + + + /* Create SIP message body. */ + msg_body = pj_pool_zalloc(tdata->pool, sizeof(pjsip_msg_body)); + msg_body->content_type.type = STR_MESSAGE; + msg_body->content_type.subtype = STR_SIPFRAG; + msg_body->content_type.param = STR_SIPFRAG_VERSION; + msg_body->data = body; + msg_body->len = bodylen; + msg_body->print_body = &pjsip_print_text_body; + msg_body->clone_data = &pjsip_clone_text_data; + + /* Attach sipfrag body. */ + tdata->msg->body = msg_body; + + + /* Done. */ + *p_tdata = tdata; + + +on_return: + pjsip_dlg_dec_lock(xfer->dlg); + return status; + +} + + +/* + * Send current state and the last sipfrag body. + */ +PJ_DEF(pj_status_t) pjsip_xfer_current_notify( pjsip_evsub *sub, + pjsip_tx_data **p_tdata ) +{ + pjsip_xfer *xfer; + pj_status_t status; + + + /* Check arguments. */ + PJ_ASSERT_RETURN(sub, PJ_EINVAL); + + /* Get the xfer object. */ + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_RETURN(xfer != NULL, PJSIP_ENOREFERSESSION); + + pjsip_dlg_inc_lock(xfer->dlg); + + status = pjsip_xfer_notify(sub, pjsip_evsub_get_state(sub), + xfer->last_st_code, &xfer->last_st_text, + p_tdata); + + pjsip_dlg_dec_lock(xfer->dlg); + + return status; +} + + +/* + * Send request message. + */ +PJ_DEF(pj_status_t) pjsip_xfer_send_request( pjsip_evsub *sub, + pjsip_tx_data *tdata) +{ + return pjsip_evsub_send_request(sub, tdata); +} + + +/* + * This callback is called by event subscription when subscription + * state has changed. + */ +static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + pjsip_xfer *xfer; + + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_evsub_state) + (*xfer->user_cb.on_evsub_state)(sub, event); + +} + +/* + * Called when transaction state has changed. + */ +static void xfer_on_evsub_tsx_state( pjsip_evsub *sub, pjsip_transaction *tsx, + pjsip_event *event) +{ + pjsip_xfer *xfer; + + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_tsx_state) + (*xfer->user_cb.on_tsx_state)(sub, tsx, event); +} + +/* + * Called when REFER is received to refresh subscription. + */ +static void xfer_on_evsub_rx_refresh( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) +{ + pjsip_xfer *xfer; + + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_rx_refresh) { + (*xfer->user_cb.on_rx_refresh)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); + + } else { + /* Implementors MUST send NOTIFY if it implements on_rx_refresh + * (implementor == "us" from evsub point of view. + */ + pjsip_tx_data *tdata; + pj_status_t status; + + if (pjsip_evsub_get_state(sub)==PJSIP_EVSUB_STATE_TERMINATED) { + status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_TERMINATED, + xfer->last_st_code, + &xfer->last_st_text, + &tdata); + } else { + status = pjsip_xfer_current_notify(sub, &tdata); + } + + if (status == PJ_SUCCESS) + pjsip_xfer_send_request(sub, tdata); + } +} + + +/* + * Called when NOTIFY is received. + */ +static void xfer_on_evsub_rx_notify( pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body) +{ + pjsip_xfer *xfer; + + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_rx_notify) + (*xfer->user_cb.on_rx_notify)(sub, rdata, p_st_code, p_st_text, + res_hdr, p_body); +} + +/* + * Called when it's time to send SUBSCRIBE. + */ +static void xfer_on_evsub_client_refresh(pjsip_evsub *sub) +{ + pjsip_xfer *xfer; + + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_client_refresh) { + (*xfer->user_cb.on_client_refresh)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_xfer_initiate(sub, NULL, &tdata); + if (status == PJ_SUCCESS) + pjsip_xfer_send_request(sub, tdata); + } +} + + +/* + * Called when no refresh is received after the interval. + */ +static void xfer_on_evsub_server_timeout(pjsip_evsub *sub) +{ + pjsip_xfer *xfer; + + xfer = pjsip_evsub_get_mod_data(sub, mod_xfer.id); + PJ_ASSERT_ON_FAIL(xfer!=NULL, {return;}); + + if (xfer->user_cb.on_server_timeout) { + (*xfer->user_cb.on_server_timeout)(sub); + } else { + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + xfer->last_st_code, + &xfer->last_st_text, &tdata); + if (status == PJ_SUCCESS) + pjsip_xfer_send_request(sub, tdata); + } +} + diff --git a/pjsip/src/pjsip/sip_dialog.c b/pjsip/src/pjsip/sip_dialog.c index 3056469c..3dfc8b94 100644 --- a/pjsip/src/pjsip/sip_dialog.c +++ b/pjsip/src/pjsip/sip_dialog.c @@ -78,6 +78,8 @@ static pj_status_t create_dialog( pjsip_user_agent *ua, dlg->endpt = endpt; dlg->state = PJSIP_DIALOG_STATE_NULL; + pj_list_init(&dlg->inv_hdr); + status = pj_mutex_create_recursive(pool, "dlg%p", &dlg->mutex); if (status != PJ_SUCCESS) goto on_error; @@ -131,10 +133,31 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua, goto on_error; } + /* Put any header param in the target URI into INVITE header list. */ + if (PJSIP_URI_SCHEME_IS_SIP(dlg->target) || + PJSIP_URI_SCHEME_IS_SIPS(dlg->target)) + { + pjsip_param *param; + pjsip_sip_uri *uri = (pjsip_sip_uri*)pjsip_uri_get_uri(dlg->target); + + param = uri->header_param.next; + while (param != &uri->header_param) { + pjsip_generic_string_hdr *req_hdr; + + req_hdr = pjsip_generic_string_hdr_create(dlg->pool, ¶m->name, + ¶m->value); + pj_list_push_back(&dlg->inv_hdr, req_hdr); + + param = param->next; + } + } + /* Init local info. */ dlg->local.info = pjsip_from_hdr_create(dlg->pool); - pj_strdup_with_null(dlg->pool, &tmp, local_uri); - dlg->local.info->uri = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0); + pj_strdup_with_null(dlg->pool, &dlg->local.info_str, local_uri); + dlg->local.info->uri = pjsip_parse_uri(dlg->pool, + dlg->local.info_str.ptr, + dlg->local.info_str.slen, 0); if (!dlg->local.info->uri) { status = PJSIP_EINVALIDURI; goto on_error; @@ -164,8 +187,10 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uac( pjsip_user_agent *ua, /* Init remote info. */ dlg->remote.info = pjsip_to_hdr_create(dlg->pool); - pj_strdup_with_null(dlg->pool, &tmp, remote_uri); - dlg->remote.info->uri = pjsip_parse_uri(dlg->pool, tmp.ptr, tmp.slen, 0); + pj_strdup_with_null(dlg->pool, &dlg->remote.info_str, remote_uri); + dlg->remote.info->uri = pjsip_parse_uri(dlg->pool, + dlg->remote.info_str.ptr, + dlg->remote.info_str.slen, 0); if (!dlg->remote.info->uri) { status = PJSIP_EINVALIDURI; goto on_error; @@ -225,6 +250,9 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua, pjsip_hdr *contact_hdr; pjsip_rr_hdr *rr; pjsip_transaction *tsx = NULL; + pj_str_t tmp; + enum { TMP_LEN=128}; + pj_ssize_t len; pjsip_dialog *dlg; /* Check arguments. */ @@ -249,6 +277,11 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua, if (status != PJ_SUCCESS) return status; + /* Temprary string for getting the string representation of + * both local and remote URI. + */ + tmp.ptr = pj_pool_alloc(rdata->tp_info.pool, TMP_LEN); + /* Init local info from the To header. */ dlg->local.info = pjsip_hdr_clone(dlg->pool, rdata->msg_info.to); pjsip_fromto_hdr_set_from(dlg->local.info); @@ -256,10 +289,36 @@ PJ_DEF(pj_status_t) pjsip_dlg_create_uas( pjsip_user_agent *ua, /* Generate local tag. */ pj_create_unique_string(dlg->pool, &dlg->local.info->tag); + + /* Print the local info. */ + len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + dlg->local.info->uri, tmp.ptr, TMP_LEN); + if (len < 1) { + pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->"); + tmp.slen = pj_ansi_strlen(tmp.ptr); + } else + tmp.slen = len; + + /* Save the local info. */ + pj_strdup(dlg->pool, &dlg->local.info_str, &tmp); + /* Calculate hash value of local tag. */ dlg->local.tag_hval = pj_hash_calc(0, dlg->local.info->tag.ptr, dlg->local.info->tag.slen); + /* Print the local info. */ + len = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, + dlg->local.info->uri, tmp.ptr, TMP_LEN); + if (len < 1) { + pj_ansi_strcpy(tmp.ptr, "<-error: uri too long->"); + tmp.slen = pj_ansi_strlen(tmp.ptr); + } else + tmp.slen = len; + + /* Save the local info. */ + pj_strdup(dlg->pool, &dlg->remote.info_str, &tmp); + + /* Randomize local cseq */ dlg->local.first_cseq = pj_rand() % 0x7FFFFFFFL; dlg->local.cseq = dlg->local.first_cseq; @@ -1085,6 +1144,8 @@ PJ_DEF(pj_status_t) pjsip_dlg_send_response( pjsip_dialog *dlg, pjsip_transaction *tsx, pjsip_tx_data *tdata) { + pj_status_t status; + /* Sanity check. */ PJ_ASSERT_RETURN(dlg && tsx && tdata && tdata->msg, PJ_EINVAL); PJ_ASSERT_RETURN(tdata->msg->type == PJSIP_RESPONSE_MSG, @@ -1107,7 +1168,14 @@ PJ_DEF(pj_status_t) pjsip_dlg_send_response( pjsip_dialog *dlg, PJ_EINVALIDOP); #endif - return pjsip_tsx_send_msg(tsx, tdata); + /* Must acquire dialog first, to prevent deadlock */ + pjsip_dlg_inc_lock(dlg); + + status = pjsip_tsx_send_msg(tsx, tdata); + + pjsip_dlg_dec_lock(dlg); + + return status; } diff --git a/pjsip/src/pjsip/sip_endpoint.c b/pjsip/src/pjsip/sip_endpoint.c index 30d6febe..95273b3a 100644 --- a/pjsip/src/pjsip/sip_endpoint.c +++ b/pjsip/src/pjsip/sip_endpoint.c @@ -325,6 +325,8 @@ PJ_DEF(pj_status_t) pjsip_endpt_add_capability( pjsip_endpoint *endpt, htype==PJSIP_H_SUPPORTED, PJ_EINVAL); + PJ_UNUSED_ARG(mod); + /* Find the header. */ hdr = (pjsip_generic_array_hdr*) pjsip_endpt_get_capability(endpt, htype, hname); diff --git a/pjsip/src/pjsip/sip_msg.c b/pjsip/src/pjsip/sip_msg.c index b3a3438b..e1ce1fb6 100644 --- a/pjsip/src/pjsip/sip_msg.c +++ b/pjsip/src/pjsip/sip_msg.c @@ -1746,6 +1746,18 @@ PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t return msg_body->len; } +PJ_DEF(void*) pjsip_clone_text_data( pj_pool_t *pool, const void *data, + unsigned len) +{ + char *newdata = ""; + + if (len) { + newdata = pj_pool_alloc(pool, len); + pj_memcpy(newdata, data, len); + } + return newdata; +} + PJ_DEF(pj_status_t) pjsip_msg_body_clone( pj_pool_t *pool, pjsip_msg_body *dst_body, const pjsip_msg_body *src_body ) diff --git a/pjsip/src/pjsua/main.c b/pjsip/src/pjsua/main.c index 467c5a5a..4068e77f 100644 --- a/pjsip/src/pjsua/main.c +++ b/pjsip/src/pjsua/main.c @@ -113,8 +113,8 @@ static void keystroke_help(void) pj_snprintf(reg_status, sizeof(reg_status), "%s (%.*s;expires=%d)", pjsip_get_status_text(pjsua.regc_last_code)->ptr, - (int)info.server_uri.slen, - info.server_uri.ptr, + (int)info.client_uri.slen, + info.client_uri.ptr, info.next_reg); } else { @@ -131,16 +131,16 @@ static void keystroke_help(void) puts("| Call Commands: | IM & Presence: | Misc: |"); puts("| | | |"); puts("| m Make new call | i Send IM | o Send OPTIONS |"); - puts("| a Answer call | s Subscribe presence | R (Re-)register |"); - puts("| h Hangup call | u Unsubscribe presence | r Unregister |"); - puts("| ] Select next dialog | t Toggle Online status | d Dump status |"); + puts("| a Answer call | s Subscribe presence | rr (Re-)register |"); + puts("| h Hangup call | u Unsubscribe presence | ru Unregister |"); + puts("| ] Select next dialog | t ToGgle Online status | d Dump status |"); puts("| [ Select previous dialog | | |"); - puts("+-----------------------------------------------------------------------------+"); - puts("| Conference Command |"); - puts("| cl List ports |"); - puts("| cc Connect port |"); - puts("| cd Disconnect port |"); - puts("+-----------------------------------------------------------------------------+"); + puts("| +--------------------------+-------------------+"); + puts("| H Hold call | Conference Command | |"); + puts("| v re-inVite (release hold) | cl List ports | |"); + puts("| x Xfer call | cc Connect port | |"); + puts("| | cd Disconnect port | |"); + puts("+------------------------------+--------------------------+-------------------+"); puts("| q QUIT |"); puts("+=============================================================================+"); printf(">>> "); @@ -283,7 +283,6 @@ static void ui_console_main(void) { char menuin[10]; char buf[128]; - pjsip_inv_session *inv; struct input_result result; //keystroke_help(); @@ -305,9 +304,9 @@ static void ui_console_main(void) if (result.nb_result == -1) puts("You can't do that with make call!"); else - pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, &inv); + pjsua_invite(pjsua.buddies[result.nb_result].uri.ptr, NULL); } else if (result.uri_result) - pjsua_invite(result.uri_result, &inv); + pjsua_invite(result.uri_result, NULL); break; @@ -353,43 +352,100 @@ static void ui_console_main(void) continue; } else { - pj_status_t status; - pjsip_tx_data *tdata; + pjsua_inv_hangup(inv_session, PJSIP_SC_DECLINE); + } + break; - status = pjsip_inv_end_session(inv_session->inv, - PJSIP_SC_DECLINE, NULL, &tdata); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Failed to create end session message", - status); - continue; - } + case ']': + case '[': + /* + * Cycle next/prev dialog. + */ + if (menuin[0] == ']') { + inv_session = inv_session->next; + if (inv_session == &pjsua.inv_list) + inv_session = pjsua.inv_list.next; - status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, - "Failed to send end session message", - status); - continue; + } else { + inv_session = inv_session->prev; + if (inv_session == &pjsua.inv_list) + inv_session = pjsua.inv_list.prev; + } + + if (inv_session != &pjsua.inv_list) { + char url[PJSIP_MAX_URL_SIZE]; + int len; + + len = pjsip_uri_print(0, inv_session->inv->dlg->remote.info->uri, + url, sizeof(url)-1); + if (len < 1) { + pj_ansi_strcpy(url, ""); + } else { + url[len] = '\0'; } + + PJ_LOG(3,(THIS_FILE,"Current dialog: %s", url)); + + } else { + PJ_LOG(3,(THIS_FILE,"No current dialog")); } break; - case ']': - inv_session = inv_session->next; - if (inv_session == &pjsua.inv_list) - inv_session = pjsua.inv_list.next; + case 'H': + /* + * Hold call. + */ + if (inv_session != &pjsua.inv_list) { + + pjsua_inv_set_hold(inv_session); + + } else { + PJ_LOG(3,(THIS_FILE, "No current call")); + } break; - case '[': - inv_session = inv_session->prev; - if (inv_session == &pjsua.inv_list) - inv_session = pjsua.inv_list.prev; + case 'v': + /* + * Send re-INVITE (to release hold, etc). + */ + if (inv_session != &pjsua.inv_list) { + + pjsua_inv_reinvite(inv_session); + + } else { + PJ_LOG(3,(THIS_FILE, "No current call")); + } + break; + + case 'x': + /* + * Transfer call. + */ + if (inv_session == &pjsua.inv_list) { + + PJ_LOG(3,(THIS_FILE, "No current call")); + + } else { + ui_input_url("Transfer to URL", buf, sizeof(buf), &result); + if (result.nb_result != NO_NB) { + if (result.nb_result == -1) + puts("You can't do that with transfer call!"); + else + pjsua_inv_xfer_call( inv_session, + pjsua.buddies[result.nb_result].uri.ptr); + + } else if (result.uri_result) { + pjsua_inv_xfer_call( inv_session, result.uri_result); + } + } break; case 's': case 'u': - ui_input_url("Subscribe presence of", buf, sizeof(buf), &result); + /* + * Subscribe/unsubscribe presence. + */ + ui_input_url("(un)Subscribe presence of", buf, sizeof(buf), &result); if (result.nb_result != NO_NB) { if (result.nb_result == -1) { unsigned i; @@ -402,19 +458,29 @@ static void ui_console_main(void) pjsua_pres_refresh(); } else if (result.uri_result) { - puts("Sorry, can only subscribe to buddy's presence, not arbitrary URL (for now)"); + puts("Sorry, can only subscribe to buddy's presence, " + "not arbitrary URL (for now)"); } break; - case 'R': - pjsua_regc_update(PJ_TRUE); - break; - case 'r': - pjsua_regc_update(PJ_FALSE); + switch (menuin[1]) { + case 'r': + /* + * Re-Register. + */ + pjsua_regc_update(PJ_TRUE); + break; + case 'u': + /* + * Unregister + */ + pjsua_regc_update(PJ_FALSE); + break; + } break; - + case 't': pjsua.online_status = !pjsua.online_status; pjsua_pres_refresh(); @@ -430,16 +496,31 @@ static void ui_console_main(void) { char src_port[10], dst_port[10]; pj_status_t status; + const char *src_title, *dst_title; - if (!simple_input("Connect src port #:", src_port, sizeof(src_port))) + conf_list(); + + src_title = (menuin[1]=='c'? + "Connect src port #": + "Disconnect src port #"); + dst_title = (menuin[1]=='c'? + "To dst port #": + "From dst port #"); + + if (!simple_input(src_title, src_port, sizeof(src_port))) break; - if (!simple_input("To dst port #:", dst_port, sizeof(dst_port))) + + if (!simple_input(dst_title, dst_port, sizeof(dst_port))) break; if (menuin[1]=='c') { - status = pjmedia_conf_connect_port(pjsua.mconf, atoi(src_port), atoi(dst_port)); + status = pjmedia_conf_connect_port(pjsua.mconf, + atoi(src_port), + atoi(dst_port)); } else { - status = pjmedia_conf_disconnect_port(pjsua.mconf, atoi(src_port), atoi(dst_port)); + status = pjmedia_conf_disconnect_port(pjsua.mconf, + atoi(src_port), + atoi(dst_port)); } if (status == PJ_SUCCESS) { puts("Success"); diff --git a/pjsip/src/pjsua/pjsua.h b/pjsip/src/pjsua/pjsua.h index 3afde354..e731b4e4 100644 --- a/pjsip/src/pjsua/pjsua.h +++ b/pjsip/src/pjsua/pjsua.h @@ -68,6 +68,8 @@ struct pjsua_inv_data pjmedia_session *session; /**< The media session. */ unsigned conf_slot; /**< Slot # in conference bridge. */ unsigned call_slot; /**< RTP media index in med_sock_use[] */ + pjsip_evsub *xfer_sub; /**< Xfer server subscription, if this + call was triggered by xfer. */ }; @@ -254,7 +256,7 @@ pj_status_t pjsua_destroy(void); * Make outgoing call. */ pj_status_t pjsua_invite(const char *cstr_dest_uri, - pjsip_inv_session **p_inv); + struct pjsua_inv_data **p_inv_data); /** @@ -263,6 +265,31 @@ pj_status_t pjsua_invite(const char *cstr_dest_uri, pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata); +/** + * Hangup call. + */ +void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code); + + +/** + * Put call on-hold. + */ +void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session); + + +/** + * Send re-INVITE (to release hold). + */ +void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session); + + +/** + * Transfer call. + */ +void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session, + const char *dest); + + /** * Callback to be called by session when invite session's state has changed. */ @@ -283,6 +310,19 @@ void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e); */ void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status); +/** + * Callback called when invite session received new offer. + */ +void pjsua_inv_on_rx_offer( pjsip_inv_session *inv, + const pjmedia_sdp_session *offer); + +/** + * Callback to receive transaction state inside invite session or dialog + * (e.g. REFER, MESSAGE). + */ +void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e); /** * Terminate all calls. diff --git a/pjsip/src/pjsua/pjsua_core.c b/pjsip/src/pjsua/pjsua_core.c index 59386d13..1e81d91e 100644 --- a/pjsip/src/pjsua/pjsua_core.c +++ b/pjsip/src/pjsua/pjsua_core.c @@ -141,7 +141,7 @@ static pj_status_t init_sockets(pj_bool_t sip, enum { RTP_START_PORT = 4000, RTP_RANDOM_START = 2, - RTP_RETRY = 10 + RTP_RETRY = 20 }; enum { SIP_SOCK, @@ -392,6 +392,9 @@ static pj_status_t init_stack(void) inv_cb.on_state_changed = &pjsua_inv_on_state_changed; inv_cb.on_new_session = &pjsua_inv_on_new_session; inv_cb.on_media_update = &pjsua_inv_on_media_update; + inv_cb.on_rx_offer = &pjsua_inv_on_rx_offer; + inv_cb.on_tsx_state_changed = &pjsua_inv_on_tsx_state_changed; + /* Initialize invite session module: */ status = pjsip_inv_usage_init(pjsua.endpt, &pjsua.mod, &inv_cb); @@ -479,6 +482,9 @@ pj_status_t pjsua_init(void) pjsip_pres_init_module( pjsua.endpt, pjsip_evsub_instance()); + /* Init xfer/REFER module */ + + pjsip_xfer_init_module( pjsua.endpt ); /* Init pjsua presence handler: */ @@ -751,6 +757,10 @@ pj_status_t pjsua_destroy(void) PJ_LOG(4,(THIS_FILE, "Shutting down...")); busy_sleep(1000); + /* Destroy conference bridge. */ + if (pjsua.mconf) + pjmedia_conf_destroy(pjsua.mconf); + /* Shutdown pjmedia-codec: */ pjmedia_codec_deinit(); diff --git a/pjsip/src/pjsua/pjsua_inv.c b/pjsip/src/pjsua/pjsua_inv.c index 434d0c70..34bf651f 100644 --- a/pjsip/src/pjsua/pjsua_inv.c +++ b/pjsip/src/pjsua/pjsua_inv.c @@ -33,7 +33,7 @@ * Make outgoing call. */ pj_status_t pjsua_invite(const char *cstr_dest_uri, - pjsip_inv_session **p_inv) + struct pjsua_inv_data **p_inv_data) { pj_str_t dest_uri; pjsip_dialog *dlg; @@ -136,8 +136,8 @@ pj_status_t pjsua_invite(const char *cstr_dest_uri, /* Done. */ - - *p_inv = inv; + if (p_inv_data) + *p_inv_data = inv_data; return PJ_SUCCESS; @@ -158,127 +158,145 @@ pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata) pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); pjsip_msg *msg = rdata->msg_info.msg; + pjsip_tx_data *response = NULL; + unsigned options = 0; + pjsip_inv_session *inv; + struct pjsua_inv_data *inv_data; + pjmedia_sdp_session *answer; + int med_sk_index; + pj_status_t status; - /* - * Handle incoming INVITE outside dialog. + /* Don't want to handle anything but INVITE */ + if (msg->line.req.method.id != PJSIP_INVITE_METHOD) + return PJ_FALSE; + + /* Don't want to handle anything that's already associated with + * existing dialog or transaction. */ - if (dlg == NULL && tsx == NULL && - msg->line.req.method.id == PJSIP_INVITE_METHOD) - { - pj_status_t status; - pjsip_tx_data *response = NULL; - unsigned options = 0; + if (dlg || tsx) + return PJ_FALSE; - /* Verify that we can handle the request. */ - status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, - pjsua.endpt, &response); - if (status != PJ_SUCCESS) { - /* - * No we can't handle the incoming INVITE request. - */ + /* Verify that we can handle the request. */ + status = pjsip_inv_verify_request(rdata, &options, NULL, NULL, + pjsua.endpt, &response); + if (status != PJ_SUCCESS) { - if (response) { - pjsip_response_addr res_addr; + /* + * No we can't handle the incoming INVITE request. + */ - pjsip_get_response_addr(response->pool, rdata, &res_addr); - pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, + if (response) { + pjsip_response_addr res_addr; + + pjsip_get_response_addr(response->pool, rdata, &res_addr); + pjsip_endpt_send_response(pjsua.endpt, &res_addr, response, + NULL, NULL); + + } else { + + /* Respond with 500 (Internal Server Error) */ + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, NULL, NULL); + } - } else { + return PJ_TRUE; + } - /* Respond with 500 (Internal Server Error) */ - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, - NULL, NULL); - } - } else { - /* - * Yes we can handle the incoming INVITE request. - */ - pjsip_inv_session *inv; - struct pjsua_inv_data *inv_data; - pjmedia_sdp_session *answer; - int med_sk_index; + /* + * Yes we can handle the incoming INVITE request. + */ + /* Find free call slot. */ + for (med_sk_index=0; med_sk_indextp_info.pool, + 1, &pjsua.med_sock_info[med_sk_index], + &answer ); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + NULL, NULL); - status = pjmedia_endpt_create_sdp( pjsua.med_endpt, rdata->tp_info.pool, - 1, &pjsua.med_sock_info[med_sk_index], - &answer ); - if (status != PJ_SUCCESS) { + /* Free call socket. */ + pjsua.med_sock_use[med_sk_index] = 0; + return PJ_TRUE; + } - pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, - NULL, NULL); - pjsua.med_sock_use[med_sk_index] = 0; - return PJ_TRUE; - } + /* Create dialog: */ - /* Create dialog: */ + status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, + &pjsua.contact_uri, &dlg); + if (status != PJ_SUCCESS) { + pjsip_endpt_respond_stateless(pjsua.endpt, rdata, 500, NULL, + NULL, NULL); - status = pjsip_dlg_create_uas( pjsip_ua_instance(), rdata, - &pjsua.contact_uri, &dlg); - if (status != PJ_SUCCESS) { - pjsua.med_sock_use[med_sk_index] = 0; - return PJ_TRUE; - } + /* Free call socket. */ + pjsua.med_sock_use[med_sk_index] = 0; + return PJ_TRUE; + } - /* Create invite session: */ + /* Create invite session: */ - status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv); - if (status != PJ_SUCCESS) { + status = pjsip_inv_create_uas( dlg, rdata, answer, 0, &inv); + if (status != PJ_SUCCESS) { - status = pjsip_dlg_create_response( dlg, rdata, 500, NULL, - &response); - if (status == PJ_SUCCESS) - status = pjsip_dlg_send_response(dlg, - pjsip_rdata_get_tsx(rdata), - response); - pjsua.med_sock_use[med_sk_index] = 0; - return PJ_TRUE; + pjsip_dlg_respond(dlg, rdata, 500, NULL); - } + /* Free call socket. */ + pjsua.med_sock_use[med_sk_index] = 0; + + // TODO: Need to delete dialog + return PJ_TRUE; + } - /* Create and attach pjsua data to the dialog: */ + /* Create and attach pjsua data to the dialog: */ - inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data)); - inv_data->inv = inv; - inv_data->call_slot = inv_data->call_slot = med_sk_index; - dlg->mod_data[pjsua.mod.id] = inv_data; - inv->mod_data[pjsua.mod.id] = inv_data; + inv_data = pj_pool_zalloc(dlg->pool, sizeof(struct pjsua_inv_data)); + inv_data->inv = inv; + inv_data->call_slot = inv_data->call_slot = med_sk_index; + dlg->mod_data[pjsua.mod.id] = inv_data; + inv->mod_data[pjsua.mod.id] = inv_data; - pj_list_push_back(&pjsua.inv_list, inv_data); + pj_list_push_back(&pjsua.inv_list, inv_data); - /* Answer with 100 (using the dialog, not invite): */ + /* Answer with 100 (using the dialog, not invite): */ - status = pjsip_dlg_create_response(dlg, rdata, 100, NULL, &response); - if (status == PJ_SUCCESS) - status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), response); - } + status = pjsip_dlg_create_response(dlg, rdata, 100, NULL, &response); + if (status != PJ_SUCCESS) { + + pjsip_dlg_respond(dlg, rdata, 500, NULL); - /* This INVITE request has been handled. */ - return PJ_TRUE; + /* Free call socket. */ + pjsua.med_sock_use[med_sk_index] = 0; + + // TODO: Need to delete dialog + + } else { + status = pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), + response); } - return PJ_FALSE; + /* This INVITE request has been handled. */ + return PJ_TRUE; } @@ -288,12 +306,66 @@ pj_bool_t pjsua_inv_on_incoming(pjsip_rx_data *rdata) */ void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) { + struct pjsua_inv_data *inv_data; + + inv_data = inv->dlg->mod_data[pjsua.mod.id]; + + /* If this is an outgoing INVITE that was created because of + * REFER/transfer, send NOTIFY to transferer. + */ + if (inv_data && inv_data->xfer_sub && e->type==PJSIP_EVENT_TSX_STATE) + { + int st_code = -1; + pjsip_evsub_state ev_state = PJSIP_EVSUB_STATE_ACTIVE; + + + switch (inv->state) { + case PJSIP_INV_STATE_NULL: + case PJSIP_INV_STATE_CALLING: + /* Do nothing */ + break; + + case PJSIP_INV_STATE_EARLY: + case PJSIP_INV_STATE_CONNECTING: + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_ACTIVE; + break; + + case PJSIP_INV_STATE_CONFIRMED: + /* When state is confirmed, send the final 200/OK and terminate + * subscription. + */ + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + break; + + case PJSIP_INV_STATE_DISCONNECTED: + st_code = e->body.tsx_state.tsx->status_code; + ev_state = PJSIP_EVSUB_STATE_TERMINATED; + break; + } + + if (st_code != -1) { + pjsip_tx_data *tdata; + pj_status_t status; + + status = pjsip_xfer_notify( inv_data->xfer_sub, + ev_state, st_code, + NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY", status); + } else { + status = pjsip_xfer_send_request(inv_data->xfer_sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY", status); + } + } + } + } + /* Destroy media session when invite session is disconnected. */ if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { - struct pjsua_inv_data *inv_data; - - inv_data = inv->dlg->mod_data[pjsua.mod.id]; pj_assert(inv_data != NULL); @@ -317,6 +389,158 @@ void pjsua_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e) } +/* + * Callback called by event framework when the xfer subscription state + * has changed. + */ +static void xfer_on_evsub_state( pjsip_evsub *sub, pjsip_event *event) +{ + + PJ_UNUSED_ARG(event); + + /* + * We're only interested when subscription is terminated, to + * clear the xfer_sub member of the inv_data. + */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + struct pjsua_inv_data *inv_data; + + inv_data = pjsip_evsub_get_mod_data(sub, pjsua.mod.id); + if (!inv_data) + return; + + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, NULL); + inv_data->xfer_sub = NULL; + + PJ_LOG(3,(THIS_FILE, "Xfer subscription terminated")); + } +} + + +/* + * Follow transfer (REFER) request. + */ +static void on_call_transfered( pjsip_inv_session *inv, + pjsip_rx_data *rdata ) +{ + pj_status_t status; + pjsip_tx_data *tdata; + struct pjsua_inv_data *inv_data; + const pj_str_t str_refer_to = { "Refer-To", 8}; + pjsip_generic_string_hdr *refer_to; + char *uri; + struct pjsip_evsub_user xfer_cb; + pjsip_evsub *sub; + + /* Find the Refer-To header */ + refer_to = (pjsip_generic_string_hdr*) + pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL); + + if (refer_to == NULL) { + /* Invalid Request. + * No Refer-To header! + */ + PJ_LOG(4,(THIS_FILE, "Received REFER without Refer-To header!")); + pjsip_dlg_respond( inv->dlg, rdata, 400, NULL); + return; + } + + PJ_LOG(3,(THIS_FILE, "Call to %.*s is being transfered to %.*s", + (int)inv->dlg->remote.info_str.slen, + inv->dlg->remote.info_str.ptr, + (int)refer_to->hvalue.slen, + refer_to->hvalue.ptr)); + + /* Init callback */ + pj_memset(&xfer_cb, 0, sizeof(xfer_cb)); + xfer_cb.on_evsub_state = &xfer_on_evsub_state; + + /* Create transferee event subscription */ + status = pjsip_xfer_create_uas( inv->dlg, &xfer_cb, rdata, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer uas", status); + pjsip_dlg_respond( inv->dlg, rdata, 500, NULL); + return; + } + + /* Accept the REFER request, send 200 (OK). */ + pjsip_xfer_accept(sub, rdata, 200, NULL); + + /* Create initial NOTIFY request */ + status = pjsip_xfer_notify( sub, PJSIP_EVSUB_STATE_ACTIVE, + 100, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", status); + return; + } + + /* Send initial NOTIFY request */ + status = pjsip_xfer_send_request( sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", status); + return; + } + + /* We're cheating here. + * We need to get a null terminated string from a pj_str_t. + * So grab the pointer from the hvalue and NULL terminate it, knowing + * that the NULL position will be occupied by a newline. + */ + uri = refer_to->hvalue.ptr; + uri[refer_to->hvalue.slen] = '\0'; + + /* Now make the outgoing call. */ + status = pjsua_invite(uri, &inv_data); + if (status != PJ_SUCCESS) { + + /* Notify xferer about the error */ + status = pjsip_xfer_notify(sub, PJSIP_EVSUB_STATE_TERMINATED, + 500, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create NOTIFY to REFER", + status); + return; + } + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send NOTIFY to REFER", + status); + return; + } + return; + } + + /* Put the server subscription in inv_data. + * Subsequent state changed in pjsua_inv_on_state_changed() will be + * reported back to the server subscription. + */ + inv_data->xfer_sub = sub; + + /* Put the invite_data in the subscription. */ + pjsip_evsub_set_mod_data(sub, pjsua.mod.id, inv_data); +} + + +/* + * This callback is called when transaction state has changed in INVITE + * session. We use this to trap incoming REFER request. + */ +void pjsua_inv_on_tsx_state_changed(pjsip_inv_session *inv, + pjsip_transaction *tsx, + pjsip_event *e) +{ + if (tsx->role==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_refer_method)==0) + { + /* + * Incoming REFER request. + */ + on_call_transfered(inv, e->body.tsx_state.src.rdata); + } +} + + /* * This callback is called by invite session framework when UAC session * has forked. @@ -330,6 +554,109 @@ void pjsua_inv_on_new_session(pjsip_inv_session *inv, pjsip_event *e) } +/* + * Create inactive SDP for call hold. + */ +static pj_status_t create_inactive_sdp(struct pjsua_inv_data *inv_session, + pjmedia_sdp_session **p_answer) +{ + pj_status_t status; + pjmedia_sdp_conn *conn; + pjmedia_sdp_attr *attr; + pjmedia_sdp_session *sdp; + + /* Create new offer */ + status = pjmedia_endpt_create_sdp(pjsua.med_endpt, pjsua.pool, 1, + &pjsua.med_sock_info[inv_session->call_slot], + &sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + return status; + } + + /* Get SDP media connection line */ + conn = sdp->media[0]->conn; + if (!conn) + conn = sdp->conn; + + /* Modify address */ + conn->addr = pj_str("0.0.0.0"); + + /* Remove existing directions attributes */ + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendrecv"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "sendonly"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "recvonly"); + pjmedia_sdp_media_remove_all_attr(sdp->media[0], "inactive"); + + /* Add inactive attribute */ + attr = pjmedia_sdp_attr_create(pjsua.pool, "inactive", NULL); + pjmedia_sdp_media_add_attr(sdp->media[0], attr); + + *p_answer = sdp; + + return status; +} + +/* + * Called when session received new offer. + */ +void pjsua_inv_on_rx_offer( pjsip_inv_session *inv, + const pjmedia_sdp_session *offer) +{ + struct pjsua_inv_data *inv_data; + pjmedia_sdp_conn *conn; + pjmedia_sdp_session *answer; + pj_bool_t is_remote_active; + pj_status_t status; + + inv_data = inv->dlg->mod_data[pjsua.mod.id]; + + /* + * See if remote is offering active media (i.e. not on-hold) + */ + is_remote_active = PJ_TRUE; + + conn = offer->media[0]->conn; + if (!conn) + conn = offer->conn; + + if (pj_strcmp2(&conn->addr, "0.0.0.0")==0 || + pj_strcmp2(&conn->addr, "0")==0) + { + is_remote_active = PJ_FALSE; + + } + else if (pjmedia_sdp_media_find_attr2(offer->media[0], "inactive", NULL)) + { + is_remote_active = PJ_FALSE; + } + + PJ_LOG(4,(THIS_FILE, "Received SDP offer, remote media is %s", + (is_remote_active ? "active" : "inactive"))); + + /* Supply candidate answer */ + if (is_remote_active) { + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1, + &pjsua.med_sock_info[inv_data->call_slot], + &answer); + } else { + status = create_inactive_sdp( inv_data, &answer ); + } + + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create local SDP", status); + return; + } + + status = pjsip_inv_set_sdp_answer(inv, answer); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to set answer", status); + return; + } + +} + + /* * Callback to be called when SDP offer/answer negotiation has just completed * in the session. This function will start/update media if negotiation @@ -340,6 +667,9 @@ void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status) struct pjsua_inv_data *inv_data; const pjmedia_sdp_session *local_sdp; const pjmedia_sdp_session *remote_sdp; + pjmedia_port *media_port; + pj_str_t port_name; + char tmp[PJSIP_MAX_URL_SIZE]; if (status != PJ_SUCCESS) { @@ -380,51 +710,243 @@ void pjsua_inv_on_media_update(pjsip_inv_session *inv, pj_status_t status) /* Create new media session. * The media session is active immediately. */ + if (pjsua.null_audio) + return; + + status = pjmedia_session_create( pjsua.med_endpt, 1, + &pjsua.med_sock_info[inv_data->call_slot], + local_sdp, remote_sdp, + &inv_data->session ); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create media session", + status); + return; + } - if (!pjsua.null_audio) { - pjmedia_port *media_port; - pj_str_t port_name; - char tmp[PJSIP_MAX_URL_SIZE]; - status = pjmedia_session_create( pjsua.med_endpt, 1, - &pjsua.med_sock_info[inv_data->call_slot], - local_sdp, remote_sdp, - &inv_data->session ); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create media session", - status); - return; - } + /* Get the port interface of the first stream in the session. + * We need the port interface to add to the conference bridge. + */ + pjmedia_session_get_port(inv_data->session, 0, &media_port); - pjmedia_session_get_port(inv_data->session, 0, &media_port); - port_name.ptr = tmp; - port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, - inv_data->inv->dlg->remote.info->uri, - tmp, sizeof(tmp)); - if (port_name.slen < 1) { - port_name = pj_str("call"); - } - status = pjmedia_conf_add_port( pjsua.mconf, inv->pool, - media_port, - &port_name, - &inv_data->conf_slot); - if (status != PJ_SUCCESS) { - pjsua_perror(THIS_FILE, "Unable to create conference slot", - status); - pjmedia_session_destroy(inv_data->session); - inv_data->session = NULL; - return; + /* + * Add the call to conference bridge. + */ + port_name.ptr = tmp; + port_name.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, + inv_data->inv->dlg->remote.info->uri, + tmp, sizeof(tmp)); + if (port_name.slen < 1) { + port_name = pj_str("call"); + } + status = pjmedia_conf_add_port( pjsua.mconf, inv->pool, + media_port, + &port_name, + &inv_data->conf_slot); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create conference slot", + status); + pjmedia_session_destroy(inv_data->session); + inv_data->session = NULL; + return; + } + + /* Connect new call to the sound device port (port zero) in the + * main conference bridge. + */ + pjmedia_conf_connect_port( pjsua.mconf, 0, inv_data->conf_slot); + pjmedia_conf_connect_port( pjsua.mconf, inv_data->conf_slot, 0); + + /* Done. */ + { + struct pjmedia_session_info sess_info; + char info[80]; + int info_len = 0; + unsigned i; + + pjmedia_session_get_info(inv_data->session, &sess_info); + for (i=0; idir) { + case PJMEDIA_DIR_NONE: + dir = "inactive"; + break; + case PJMEDIA_DIR_ENCODING: + dir = "sendonly"; + break; + case PJMEDIA_DIR_DECODING: + dir = "recvonly"; + break; + case PJMEDIA_DIR_ENCODING_DECODING: + dir = "sendrecv"; + break; + default: + dir = "unknown"; + break; + } + len = pj_ansi_sprintf( info+info_len, + ", stream #%d: %.*s (%s)", i, + (int)strm_info->fmt.encoding_name.slen, + (int)strm_info->fmt.encoding_name.ptr, + dir); + if (len > 0) + info_len += len; } + PJ_LOG(3,(THIS_FILE,"Media started%s", info)); + } +} + + +/* + * Hangup call. + */ +void pjsua_inv_hangup(struct pjsua_inv_data *inv_session, int code) +{ + pj_status_t status; + pjsip_tx_data *tdata; + + status = pjsip_inv_end_session(inv_session->inv, + code, NULL, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to create end session message", + status); + return; + } + + status = pjsip_inv_send_msg(inv_session->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, + "Failed to send end session message", + status); + return; + } +} + + +/* + * Put call on-Hold. + */ +void pjsua_inv_set_hold(struct pjsua_inv_data *inv_session) +{ + pjmedia_sdp_session *sdp; + pjsip_inv_session *inv = inv_session->inv; + pjsip_tx_data *tdata; + pj_status_t status; + + if (inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not hold call that is not confirmed")); + return; + } - pjmedia_conf_connect_port( pjsua.mconf, 0, inv_data->conf_slot); - pjmedia_conf_connect_port( pjsua.mconf, inv_data->conf_slot, 0); + status = create_inactive_sdp(inv_session, &sdp); + if (status != PJ_SUCCESS) + return; + + /* Send re-INVITE with new offer */ + status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + return; + } - PJ_LOG(3,(THIS_FILE,"Media has been started successfully")); + status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + return; } } +/* + * re-INVITE. + */ +void pjsua_inv_reinvite(struct pjsua_inv_data *inv_session) +{ + pjmedia_sdp_session *sdp; + pjsip_tx_data *tdata; + pjsip_inv_session *inv = inv_session->inv; + pj_status_t status; + + + if (inv->state != PJSIP_INV_STATE_CONFIRMED) { + PJ_LOG(3,(THIS_FILE, "Can not re-INVITE call that is not confirmed")); + return; + } + + /* Create SDP */ + status = pjmedia_endpt_create_sdp( pjsua.med_endpt, inv->pool, 1, + &pjsua.med_sock_info[inv_session->call_slot], + &sdp); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to get SDP from media endpoint", status); + return; + } + + /* Send re-INVITE with new offer */ + status = pjsip_inv_reinvite( inv_session->inv, NULL, sdp, &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create re-INVITE", status); + return; + } + + status = pjsip_inv_send_msg( inv_session->inv, tdata, NULL); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send re-INVITE", status); + return; + } +} + + +/* + * Transfer call. + */ +void pjsua_inv_xfer_call(struct pjsua_inv_data *inv_session, + const char *dest) +{ + pjsip_evsub *sub; + pjsip_tx_data *tdata; + pj_str_t tmp; + pj_status_t status; + + + /* Create xfer client subscription. + * We're not interested in knowing the transfer result, so we + * put NULL as the callback. + */ + status = pjsip_xfer_create_uac(inv_session->inv->dlg, NULL, &sub); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create xfer", status); + return; + } + + /* + * Create REFER request. + */ + status = pjsip_xfer_initiate(sub, pj_cstr(&tmp, dest), &tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to create REFER request", status); + return; + } + + /* Send. */ + status = pjsip_xfer_send_request(sub, tdata); + if (status != PJ_SUCCESS) { + pjsua_perror(THIS_FILE, "Unable to send REFER request", status); + return; + } + + /* For simplicity (that's what this program is intended to be!), + * leave the original invite session as it is. More advanced application + * may want to hold the INVITE, or terminate the invite, or whatever. + */ +} + + /* * Terminate all calls. */ @@ -445,3 +967,4 @@ void pjsua_inv_shutdown() } } + -- cgit v1.2.3