From e6a5b770072500cf89a9cc10e4b99a972a6787b8 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sat, 11 Nov 2006 16:16:04 +0000 Subject: Attended call transfer implementation. The changes involves: - Added support for SIP Replaces extension (RFC 3891) - Added pjsua_call_xfer_replaces() to perform attended call transfer. - PJSUA checks and process Replaces header in incoming calls - Added pjsip_ua_find_dialog() API. - Added pjsip_endpt_has_capability() API. - Added pjsip_endpt_send_response2() API. - etc. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@797 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/include/pjsip-ua/sip_replaces.h | 300 ++++++++++++++++++++++++++++++++++ pjsip/include/pjsip-ua/sip_xfer.h | 31 +++- pjsip/include/pjsip/sip_endpoint.h | 25 +++ pjsip/include/pjsip/sip_ua_layer.h | 22 +++ pjsip/include/pjsip/sip_util.h | 25 +++ pjsip/include/pjsip_ua.h | 1 + pjsip/include/pjsua-lib/pjsua.h | 67 +++++++- 7 files changed, 461 insertions(+), 10 deletions(-) create mode 100644 pjsip/include/pjsip-ua/sip_replaces.h (limited to 'pjsip/include') diff --git a/pjsip/include/pjsip-ua/sip_replaces.h b/pjsip/include/pjsip-ua/sip_replaces.h new file mode 100644 index 00000000..38c8aa99 --- /dev/null +++ b/pjsip/include/pjsip-ua/sip_replaces.h @@ -0,0 +1,300 @@ +/* $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 + */ +#ifndef __PJSIP_REPLACES_H__ +#define __PJSIP_REPLACES_H__ + + +/** + * @file sip_replaces.h + * @brief SIP Replaces support (RFC 3891 - SIP "Replaces" Header) + */ +#include + +/** + * @defgroup PJSIP_REPLACES SIP Replaces support (RFC 3891 - "Replaces" Header) + * @ingroup PJSIP_HIGH_UA + * @brief SIP Replaces support (RFC 3891 - "Replaces" Header) + * @{ + * + * This module implements support for Replaces header in PJSIP. The Replaces + * specification is written in RFC 3891 - The Session Initiation Protocol (SIP) + * "Replaces" Header, and can be used to enable a variety of features, + * for example: "Attended Transfer" and "Call Pickup". + * + * + * + * \section PJSIP_REPLACES_USING_SEC Using PJSIP Replaces Support + * + * \subsection PJSIP_REPLACES_INIT_SUBSEC Initialization + * + * Application needs to call #pjsip_replaces_init_module() during application + * initialization stage to register "replaces" support in PJSIP. + * + * + * + * \subsection PJSIP_REPLACES_UAC_SUBSEC UAC Behavior: Sending a Replaces Header + * + * A User Agent that wishes to replace a single existing early or + * confirmed dialog with a new dialog of its own, MAY send the target + * User Agent an INVITE request containing a Replaces header field. The + * User Agent Client (UAC) places the Call-ID, to-tag, and from-tag + * information for the target dialog in a single Replaces header field + * and sends the new INVITE to the target. + * + * To initiate outgoing INVITE request with Replaces header, application + * would create the INVITE request with #pjsip_inv_invite(), then adds + * #pjsip_replaces_hdr instance into the request, filling up the Call-ID, + * To-tag, and From-tag properties of the header with the identification + * of the dialog to be replaced. Application may also optionally + * set the \a early_only property of the header to indicate that it only + * wants to replace early dialog. + * + * Note that when the outgoing INVITE request (with Replaces) is initiated + * from an incoming REFER request (as in Attended Call Transfer case), + * this process should be done rather more automatically by PJSIP. Upon + * receiving incoming incoming REFER request, normally these processes + * will be performed: + * - Application finds \a Refer-To header, + * - Application creates outgoing dialog/invite session, specifying + * the URI in the \a Refer-To header as the initial remote target, + * - The URI in the \a Refer-To header may contain header parameters such + * as \a Replaces and \a Require headers. + * - The dialog keeps the header fields in the header parameters + * of the URI, and the invite session would add these headers into + * the outgoing INVITE request. Because of this, the outgoing + * INVITE request will contain the \a Replaces and \a Require headers. + * + * + * For more information, please see the implementation of + * #pjsua_call_xfer_replaces() in \ref PJSUA_LIB source code. + * + * + * \subsection PJSIP_REPLACES_UAS_SUBSEC UAS Behavior: Receiving a Replaces Header + * + * The Replaces header contains information used to match an existing + * SIP dialog (call-id, to-tag, and from-tag). Upon receiving an INVITE + * with a Replaces header, the User Agent (UA) attempts to match this + * information with a confirmed or early dialog. + * + * In PJSIP, if application wants to process the Replaces header in the + * incoming INVITE request, it should call #pjsip_replaces_verify_request() + * before creating the INVITE session. The #pjsip_replaces_verify_request() + * function checks and verifies the request to see if Replaces request + * can be processed. To be more specific, it performs the following + * verification: + * - checks that Replaces header is present. If not, the function will + * return PJ_SUCCESS without doing anything. + * - checks that no duplicate Replaces headers are present, or otherwise + * it will return 400 "Bad Request" response. + * - checks for matching dialog and verifies that the invite session has + * the correct state, and may return 481 "Call/Transaction Does Not Exist", + * 603 "Declined", or 486 "Busy Here" according to the processing rules + * specified in RFC 3891. + * - if matching dialog with correct state is found, it will give PJ_SUCCESS + * status and return the matching dialog back to the application. + * + * The following pseudocode illustrates how application can process the + * incoming INVITE if it wants to support Replaces extension: + * + \code + // Incoming INVITE request handler + pj_bool_t on_rx_invite(pjsip_rx_data *rdata) + { + pjsip_dialog *dlg, *replaced_dlg; + pjsip_inv_session *inv; + pjsip_tx_data *response; + pj_status_t status; + + // Check whether Replaces header is present in the request and process accordingly. + // + status = pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE, &response); + if (status != PJ_SUCCESS) { + // Something wrong with Replaces request. + // + if (response) { + pjsip_endpt_send_response(endpt, rdata, response, NULL, NULL); + } else { + // Respond with 500 (Internal Server Error) + pjsip_endpt_respond_stateless(endpt, rdata, 500, NULL, NULL, NULL); + } + } + + // Create UAS Invite session as usual. + // + status = pjsip_dlg_create_uas(.., rdata, .., &dlg); + .. + status = pjsip_inv_create_uas(dlg, .., &inv); + + // Send initial 100 "Trying" to the INVITE request + // + status = pjsip_inv_initial_answer(inv, rdata, 100, ..., &response); + if (status == PJ_SUCCESS) + pjsip_inv_send_msg(inv, response); + + + // This is where processing is different between normal call + // (without Replaces) and call with Replaces. + // + if (replaced_dlg) { + pjsip_inv_session *replaced_inv; + + // Always answer the new INVITE with 200, regardless whether + // the replaced call is in early or confirmed state. + // + status = pjsip_inv_answer(inv, 200, NULL, NULL, &response); + if (status == PJ_SUCCESS) + pjsip_inv_send_msg(inv, response); + + + // Get the INVITE session associated with the replaced dialog. + // + replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg); + + + // Disconnect the "replaced" INVITE session. + // + status = pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL, &tdata); + if (status == PJ_SUCCESS && tdata) + status = pjsip_inv_send_msg(replaced_inv, tdata); + + + // It's up to application to associate the new INVITE session + // with the old (now terminated) session. For example, application + // may assign the same User Interface object for the new INVITE + // session. + + } else { + // Process normal INVITE without Replaces. + ... + } + } + + \endcode + * + * + * For a complete sample implementation, please see \a pjsua_call_on_incoming() + * function of \ref PJSUA_LIB in \a pjsua_call.c file. + * + * + * \section PJSIP_REPLACES_REFERENCE References + * + * References: + * - RFC 3891: The Session + * Initiation Protocol (SIP) "Replaces" Header + * - \ref PJSUA_XFER + */ + +PJ_BEGIN_DECL + + +/** + * Declaration of SIP Replaces header (RFC 3891). + */ +typedef struct pjsip_replaces_hdr +{ + /** Standard header field. */ + PJSIP_DECL_HDR_MEMBER(struct pjsip_replaces_hdr); + + /** Call-Id */ + pj_str_t call_id; + + /** to-tag */ + pj_str_t to_tag; + + /** from-tag */ + pj_str_t from_tag; + + /** early-only? */ + pj_bool_t early_only; + + /** Other parameters */ + pjsip_param other_param; + +} pjsip_replaces_hdr; + + + +/** + * Initialize Replaces support in PJSIP. This would, among other things, + * register the header parser for Replaces header. + * + * @param endpt The endpoint instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjsip_replaces_init_module(pjsip_endpoint *endpt); + + +/** + * Create Replaces header. + * + * @param pool Pool to allocate the header instance from. + * + * @return An empty Replaces header instance. + */ +PJ_DECL(pjsip_replaces_hdr*) pjsip_replaces_hdr_create(pj_pool_t *pool); + + +/** + * Verify that incoming request with Replaces header can be processed. + * This function will perform all necessary checks according to RFC 3891 + * Section 3 "User Agent Server Behavior: Receiving a Replaces Header". + * + * @param rdata The incoming request to be verified. + * @param p_dlg On return, it will be filled with the matching + * dialog. + * @param lock_dlg Specifies whether this function should acquire lock + * to the matching dialog. If yes (and should be yes!), + * then application will need to release the dialog's + * lock with #pjsip_dlg_dec_lock() when the function + * returns PJ_SUCCESS and the \a p_dlg parameter is filled + * with the dialog instance. + * @param p_tdata Upon error, it will be filled with the final response + * to be sent to the request sender. + * + * @return The function returns the following: + * - If the request doesn't contain Replaces header, the + * function returns PJ_SUCCESS and \a p_dlg parameter + * will be set to NULL. + * - If the request contains Replaces header and a valid, + * matching dialog is found, the function returns + * PJ_SUCCESS and \a p_dlg parameter will be set to the + * matching dialog instance. + * - Upon error condition (as described by RFC 3891), the + * function returns non-PJ_SUCCESS, and \a p_tdata + * parameter SHOULD be set with a final response message + * to be sent to the sender of the request. + */ +PJ_DECL(pj_status_t) pjsip_replaces_verify_request(pjsip_rx_data *rdata, + pjsip_dialog **p_dlg, + pj_bool_t lock_dlg, + pjsip_tx_data **p_tdata); + + + +PJ_END_DECL + + +/** + * @} + */ + + +#endif /* __PJSIP_REPLACES_H__ */ + diff --git a/pjsip/include/pjsip-ua/sip_xfer.h b/pjsip/include/pjsip-ua/sip_xfer.h index 56809141..53d98e90 100644 --- a/pjsip/include/pjsip-ua/sip_xfer.h +++ b/pjsip/include/pjsip-ua/sip_xfer.h @@ -29,17 +29,32 @@ #include /** - * @defgroup PJSUA_XFER Call Transfer + * @defgroup PJSUA_XFER SIP REFER (RFC 3515) for Call Transfer etc. * @ingroup PJSIP_HIGH_UA - * @brief Provides call transfer functionality. + * @brief SIP REFER dialog usage (call transfer, etc.) * @{ * - * This implements call transfer functionality to INVITE sessions. The call - * transfer functionality uses SIP Event Subscription framework for - * managing call transfer status. - * - * Application must link with pjsip-ua AND pjsip-simple static - * libraries to use call transfer functionality. + * This describes a generic handling of SIP REFER request. The SIP REFER + * request is described in RFC 3515, and commonly used to perform call + * transfer functionality. Other types of SIP REFER usages are described + * in draft-worley-sip-many-refers-00 draft, for example: + * - Remote Dial: where UAC sends REFER to instruct REFER recipient to + * initiate an INVITE session to some target. + * + * A REFER request can be sent inside or outside existing dialog context, + * although for call transfer case, it is more common to send REFER inside + * existing INVITE session context. PJSIP supports both sending REFER request + * inside or outside dialog context. + * + * The REFER framework uses @ref PJSIP_EVENT_NOT to manage the event + * subscription created by the REFER request. Because of this, application + * must link with pjsip-ua AND pjsip-simple static libraries + * to use REFER functionality. + * + * Reference: + * - RFC 3515: The Session + * Initiation Protocol (SIP) Refer Method + * - @ref PJSIP_EVENT_NOT */ diff --git a/pjsip/include/pjsip/sip_endpoint.h b/pjsip/include/pjsip/sip_endpoint.h index 6e166090..34413d7c 100644 --- a/pjsip/include/pjsip/sip_endpoint.h +++ b/pjsip/include/pjsip/sip_endpoint.h @@ -402,6 +402,31 @@ PJ_DECL(const pjsip_hdr*) pjsip_endpt_get_capability( pjsip_endpoint *endpt, const pj_str_t *hname); +/** + * Check if we have the specified capability. + * + * @param endpt The endpoint. + * @param htype The header type to be retrieved, which value may be: + * - PJSIP_H_ACCEPT + * - PJSIP_H_ALLOW + * - PJSIP_H_SUPPORTED + * @param hname If htype specifies PJSIP_H_OTHER, then the header name + * must be supplied in this argument. Otherwise the value + * must be set to NULL. + * @param token The capability token to check. For example, if \a htype + * is PJSIP_H_ALLOW, then \a token specifies the method + * names; if \a htype is PJSIP_H_SUPPORTED, then \a token + * specifies the extension names such as "100rel". + * + * @return PJ_TRUE if the specified capability is supported, + * otherwise PJ_FALSE.. + */ +PJ_DECL(pj_bool_t) pjsip_endpt_has_capability( pjsip_endpoint *endpt, + int htype, + const pj_str_t *hname, + const pj_str_t *token); + + /** * Add or register new capabilities as indicated by the tags to the * appropriate header fields in the endpoint. diff --git a/pjsip/include/pjsip/sip_ua_layer.h b/pjsip/include/pjsip/sip_ua_layer.h index c4e2d7c7..26dea965 100644 --- a/pjsip/include/pjsip/sip_ua_layer.h +++ b/pjsip/include/pjsip/sip_ua_layer.h @@ -80,6 +80,28 @@ PJ_DECL(pj_status_t) pjsip_ua_init_module(pjsip_endpoint *endpt, PJ_DECL(pjsip_user_agent*) pjsip_ua_instance(void); +/** + * Find a dialog with the specified Call-ID and tags properties. This + * function may optionally lock the matching dialog instance before + * returning it back to the caller. + * + * @param call_id The call ID to be matched. + * @param local_tag The local tag to be matched. + * @param remote_tag The remote tag to be matched. + * @param lock_dialog If non-zero, instruct the function to lock the + * matching dialog with #pjsip_dlg_inc_lock(). + * Application is responsible to release the dialog's + * lock after it has finished manipulating the dialog, + * by calling #pjsip_dlg_dec_lock(). + * + * @return The matching dialog instance, or NULL if no matching + * dialog is found. + */ +PJ_DECL(pjsip_dialog*) pjsip_ua_find_dialog(const pj_str_t *call_id, + const pj_str_t *local_tag, + const pj_str_t *remote_tag, + pj_bool_t lock_dialog); + /** * Destroy the user agent layer. * diff --git a/pjsip/include/pjsip/sip_util.h b/pjsip/include/pjsip/sip_util.h index 2b77e469..8a030fd6 100644 --- a/pjsip/include/pjsip/sip_util.h +++ b/pjsip/include/pjsip/sip_util.h @@ -332,6 +332,31 @@ PJ_DECL(pj_status_t) pjsip_endpt_send_response( pjsip_endpoint *endpt, pj_ssize_t sent, pj_bool_t *cont)); +/** + * This is a convenient function which wraps #pjsip_get_response_addr() and + * #pjsip_endpt_send_response() in a single function. + * + * @param endpt The endpoint instance. + * @param rdata The original request to be responded. + * @param tdata The response message to be sent. + * @param token Token to be passed back when the callback is called. + * @param cb Optional callback to notify the transmission status + * to application, and to inform whether next address or + * transport will be tried. + * + * @return PJ_SUCCESS if response has been successfully created and + * sent to transport layer, or a non-zero error code. + * However, even when it returns PJ_SUCCESS, there is no + * guarantee that the response has been successfully sent. + */ +PJ_DECL(pj_status_t) pjsip_endpt_send_response2(pjsip_endpoint *endpt, + pjsip_rx_data *rdata, + pjsip_tx_data *tdata, + void *token, + void (*cb)(pjsip_send_state*, + pj_ssize_t sent, + pj_bool_t *cont)); + /** * This composite function sends response message statelessly to an incoming * request message. Internally it calls #pjsip_endpt_create_response() and diff --git a/pjsip/include/pjsip_ua.h b/pjsip/include/pjsip_ua.h index 205ef757..d3dda395 100644 --- a/pjsip/include/pjsip_ua.h +++ b/pjsip/include/pjsip_ua.h @@ -21,6 +21,7 @@ #include #include +#include #include diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index efdf9f4c..12503780 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -336,6 +336,38 @@ typedef struct pjsua_callback pj_bool_t final, pj_bool_t *p_cont); + /** + * Notify application about incoming INVITE with Replaces header. + * Application may reject the request by setting non-2xx code. + * + * @param call_id The call ID to be replaced. + * @param rdata The incoming INVITE request to replace the call. + * @param st_code Status code to be set by application. Application + * should only return a final status (200-699). + * @param st_text Optional status text to be set by application. + */ + void (*on_call_replace_request)(pjsua_call_id call_id, + pjsip_rx_data *rdata, + int *st_code, + pj_str_t *st_text); + + /** + * Notify application that an existing call has been replaced with + * a new call. This happens when PJSUA-API receives incoming INVITE + * request with Replaces header. + * + * After this callback is called, normally PJSUA-API will disconnect + * \a old_call_id and establish \a new_call_id. + * + * @param old_call_id Existing call which to be replaced with the + * new call. + * @param new_call_id The new call. + * @param rdata The incoming INVITE with Replaces request. + */ + void (*on_call_replaced)(pjsua_call_id old_call_id, + pjsua_call_id new_call_id); + + /** * Notify application when registration status has changed. * Application may then query the account info to get the @@ -1752,9 +1784,11 @@ PJ_DECL(pj_status_t) pjsua_call_reinvite(pjsua_call_id call_id, /** - * Initiate call transfer to the specified address. + * Initiate call transfer to the specified address. This function will send + * REFER request to instruct remote call party to initiate a new INVITE + * session to the specified destination/target. * - * @param call_id Call identification. + * @param call_id The call id to be transfered. * @param dest Address of new target to be contacted. * @param msg_data Optional message components to be sent with * the request. @@ -1765,6 +1799,35 @@ PJ_DECL(pj_status_t) pjsua_call_xfer(pjsua_call_id call_id, const pj_str_t *dest, const pjsua_msg_data *msg_data); +/** + * Flag to indicate that "Require: replaces" should not be put in the + * outgoing INVITE request caused by REFER request created by + * #pjsua_call_xfer_replaces(). + */ +#define PJSUA_XFER_NO_REQUIRE_REPLACES 1 + +/** + * Initiate attended call transfer. This function will send REFER request + * to instruct remote call party to initiate new INVITE session to the URL + * of \a dest_call_id. The party at \a dest_call_id then should "replace" + * the call with us with the new call from the REFER recipient. + * + * @param call_id The call id to be transfered. + * @param dest_call_id The call id to be replaced. + * @param options Application may specify PJSUA_XFER_NO_REQUIRE_REPLACES + * to suppress the inclusion of "Require: replaces" in + * the outgoing INVITE request created by the REFER + * request. + * @param msg_data Optional message components to be sent with + * the request. + * + * @return PJ_SUCCESS on success, or the appropriate error code. + */ +PJ_DECL(pj_status_t) pjsua_call_xfer_replaces(pjsua_call_id call_id, + pjsua_call_id dest_call_id, + unsigned options, + const pjsua_msg_data *msg_data); + /** * Send DTMF digits to remote using RFC 2833 payload formats. * -- cgit v1.2.3