diff options
author | Benny Prijono <bennylp@teluu.com> | 2006-02-21 23:47:00 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2006-02-21 23:47:00 +0000 |
commit | fcba4d392ea03e8ac4cfde87d8efd7999ff4a38c (patch) | |
tree | 285e65a630c72b80dd533ec88d4d8ef9b1aa7029 /pjsip/src/pjsip-ua | |
parent | 5c7386b0e38e69ae6b275b1048d59e7ec4eaf6bf (diff) |
Implemented major feature: call hold and transfer
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@212 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjsip/src/pjsip-ua')
-rw-r--r-- | pjsip/src/pjsip-ua/sip_inv.c | 396 | ||||
-rw-r--r-- | pjsip/src/pjsip-ua/sip_xfer.c | 614 |
2 files changed, 950 insertions, 60 deletions
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 <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjsip-ua/sip_xfer.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_errno.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_transport.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/string.h> + + +/* + * 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); + } +} + |